]> git.ktnx.net Git - mobile-ledger.git/commitdiff
rework pattern management machinery
authorDamyan Ivanov <dam+mobileledger@ktnx.net>
Sat, 30 Jan 2021 11:56:08 +0000 (11:56 +0000)
committerDamyan Ivanov <dam+mobileledger@ktnx.net>
Sat, 30 Jan 2021 11:56:08 +0000 (11:56 +0000)
adding and editing of patterns mostly works (amounts not yet)

room database instantiation moved from App to DB (properly synchronised)

view holders don't hold references to corresponding items or positions
instead, they retrieve the item via getAdapterPosition() as recommended

account names/comments are stored in the DB

fix crash when a pattern with null test text is loaded

15 files changed:
app/src/main/java/net/ktnx/mobileledger/App.java
app/src/main/java/net/ktnx/mobileledger/dao/PatternHeaderDAO.java
app/src/main/java/net/ktnx/mobileledger/db/DB.java
app/src/main/java/net/ktnx/mobileledger/db/PatternAccount.java
app/src/main/java/net/ktnx/mobileledger/db/PatternHeader.java
app/src/main/java/net/ktnx/mobileledger/db/PatternWithAccounts.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/model/PatternDetailsItem.java
app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternDetailsAdapter.java
app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternDetailsFragment.java
app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternDetailsViewModel.java
app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternListFragment.java
app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternViewHolder.java
app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternsActivity.java
app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternsRecyclerViewAdapter.java
app/src/main/java/net/ktnx/mobileledger/utils/Misc.java

index 7935beba307d7ffe4ff1eef8dcbef45a38535e2d..a492e8a418ea1cfeb0c20e0d3e514d032ae55fd9 100644 (file)
@@ -23,9 +23,6 @@ import android.content.res.Resources;
 import android.database.sqlite.SQLiteDatabase;
 import android.util.Log;
 
-import androidx.room.Room;
-
-import net.ktnx.mobileledger.db.DB;
 import net.ktnx.mobileledger.model.Data;
 import net.ktnx.mobileledger.ui.profiles.ProfileDetailModel;
 import net.ktnx.mobileledger.utils.Globals;
@@ -45,22 +42,12 @@ public class App extends Application {
     private static ProfileDetailModel profileModel;
     private MobileLedgerDatabase dbHelper;
     private boolean monthNamesPrepared = false;
-    private DB roomDatabase;
     public static SQLiteDatabase getDatabase() {
         if (instance == null)
             throw new RuntimeException("Application not created yet");
 
         return instance.getDB();
     }
-    public static DB getRoomDB() {
-        if (instance == null)
-            throw new RuntimeException("Application not created yet");
-
-        return instance.getRoomDatabase();
-    }
-    public DB getRoomDatabase(){
-        return roomDatabase;
-    }
     public static void prepareMonthNames() {
         instance.prepareMonthNames(false);
     }
@@ -99,8 +86,6 @@ public class App extends Application {
         Logger.debug("flow", "App onCreate()");
         instance = this;
         super.onCreate();
-        roomDatabase = Room.databaseBuilder(this, DB.class, MobileLedgerDatabase.DB_NAME)
-                              .build();
         Data.refreshCurrencyData(Locale.getDefault());
         Authenticator.setDefault(new Authenticator() {
             @Override
index 4f4dcd7a8c5f6c7ada3ce4897430cb77dd640573..43f688aab2a1b7bd8d994b06f0e9800f744ce0a4 100644 (file)
@@ -23,9 +23,11 @@ import androidx.room.Delete;
 import androidx.room.Insert;
 import androidx.room.OnConflictStrategy;
 import androidx.room.Query;
+import androidx.room.Transaction;
 import androidx.room.Update;
 
 import net.ktnx.mobileledger.db.PatternHeader;
+import net.ktnx.mobileledger.db.PatternWithAccounts;
 
 import java.util.List;
 
@@ -46,8 +48,7 @@ public interface PatternHeaderDAO {
     @Query("SELECT * FROM patterns WHERE id = :id")
     LiveData<PatternHeader> getPattern(Long id);
 
-//    not useful for now
-//    @Transaction
-//    @Query("SELECT * FROM patterns")
-//    List<PatternWithAccounts> getPatternsWithAccounts();
+    @Transaction
+    @Query("SELECT * FROM patterns WHERE id = :id")
+    LiveData<PatternWithAccounts> getPatternWithAccounts(Long id);
 }
index eb9c090a5a8c8b6e1a7eee59838ade65061cbd15..2dbc7c24fbf8e6ce92de59de116677d403528514 100644 (file)
 package net.ktnx.mobileledger.db;
 
 import androidx.room.Database;
+import androidx.room.Room;
 import androidx.room.RoomDatabase;
 
+import net.ktnx.mobileledger.App;
 import net.ktnx.mobileledger.dao.CurrencyDAO;
 import net.ktnx.mobileledger.dao.PatternAccountDAO;
 import net.ktnx.mobileledger.dao.PatternHeaderDAO;
+import net.ktnx.mobileledger.utils.MobileLedgerDatabase;
 
 @Database(version = 51, entities = {PatternHeader.class, PatternAccount.class, Currency.class})
 abstract public class DB extends RoomDatabase {
+    private static DB instance;
+    public static DB get() {
+        if (instance != null)
+            return instance;
+        synchronized (DB.class) {
+            if (instance != null)
+                return instance;
+
+            return instance =
+                    Room.databaseBuilder(App.instance, DB.class, MobileLedgerDatabase.DB_NAME)
+                        .build();
+        }
+    }
     public abstract PatternHeaderDAO getPatternDAO();
     public abstract PatternAccountDAO getPatternAccountDAO();
     public abstract CurrencyDAO getCurrencyDAO();
index fe8769bfde78748aafd9de700af1d6715a0362b6..2b91c5d10b5903e8c81a277d21f9cb0829dc172a 100644 (file)
@@ -73,6 +73,9 @@ public class PatternAccount extends PatternBase {
     public void setPosition(@NonNull Long position) {
         this.position = position;
     }
+    public void setPosition(int position) {
+        this.position = (long) position;
+    }
     public Integer getAccountNameMatchGroup() {
         return accountNameMatchGroup;
     }
index 091f1f2e786dfe3af7fe93ee02dc2de860ab8b6a..c6223c4f8bbcd54cd4c0171fd54d93d2ffda59f2 100644 (file)
@@ -1,11 +1,14 @@
 package net.ktnx.mobileledger.db;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.room.ColumnInfo;
 import androidx.room.Entity;
 import androidx.room.Index;
 import androidx.room.PrimaryKey;
 
+import net.ktnx.mobileledger.utils.Misc;
+
 import org.jetbrains.annotations.NotNull;
 
 @Entity(tableName = "patterns",
@@ -135,4 +138,27 @@ public class PatternHeader extends PatternBase {
     public void setDateDayMatchGroup(Integer dateDayMatchGroup) {
         this.dateDayMatchGroup = dateDayMatchGroup;
     }
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (obj == null)
+            return false;
+        if (!(obj instanceof PatternHeader))
+            return false;
+
+        PatternHeader o = (PatternHeader) obj;
+
+        return Misc.equalLongs(id, o.id) && Misc.equalStrings(name, o.name) &&
+               Misc.equalStrings(regularExpression, o.regularExpression) &&
+               Misc.equalStrings(transactionDescription, o.transactionDescription) &&
+               Misc.equalStrings(transactionComment, o.transactionComment) &&
+               Misc.equalIntegers(transactionDescriptionMatchGroup,
+                       o.transactionDescriptionMatchGroup) &&
+               Misc.equalIntegers(transactionCommentMatchGroup, o.transactionCommentMatchGroup) &&
+               Misc.equalIntegers(dateDay, o.dateDay) &&
+               Misc.equalIntegers(dateDayMatchGroup, o.dateDayMatchGroup) &&
+               Misc.equalIntegers(dateMonth, o.dateMonth) &&
+               Misc.equalIntegers(dateMonthMatchGroup, o.dateMonthMatchGroup) &&
+               Misc.equalIntegers(dateYear, o.dateYear) &&
+               Misc.equalIntegers(dateYearMatchGroup, o.dateYearMatchGroup);
+    }
 }
diff --git a/app/src/main/java/net/ktnx/mobileledger/db/PatternWithAccounts.java b/app/src/main/java/net/ktnx/mobileledger/db/PatternWithAccounts.java
new file mode 100644 (file)
index 0000000..8992fe1
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright © 2021 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.db;
+
+import androidx.room.Embedded;
+import androidx.room.Relation;
+
+import java.util.List;
+
+public class PatternWithAccounts {
+    @Embedded
+    public PatternHeader header;
+    @Relation(parentColumn = "id", entityColumn = "pattern_id")
+    public List<PatternAccount> accounts;
+
+    public Long getId() {
+        return header.getId();
+    }
+}
index 82b1bb45b7f152c8656e59ef622e22033d368663..43c77f72e5ff7416041a20383df421c71c4be4b0 100644 (file)
@@ -36,13 +36,11 @@ import java.util.regex.PatternSyntaxException;
 
 abstract public class PatternDetailsItem {
     private final Type type;
-    protected long id;
-    protected long position;
+    protected Long id;
+    protected Long position;
 
-    protected PatternDetailsItem(Type type, long id, long position) {
+    protected PatternDetailsItem(Type type) {
         this.type = type;
-        this.id = (id <= 0) ? -position - 2 : id;
-        this.position = position;
     }
     @Contract(" -> new")
     public static @NotNull PatternDetailsItem.Header createHeader() {
@@ -51,17 +49,18 @@ abstract public class PatternDetailsItem {
     public static @NotNull PatternDetailsItem.Header createHeader(Header origin) {
         return new Header(origin);
     }
-    @Contract("-> new")
-    public static @NotNull PatternDetailsItem.AccountRow createAccountRow(long position) {
-        return new AccountRow(-1, position);
+    @Contract("-> new")
+    public static @NotNull PatternDetailsItem.AccountRow createAccountRow() {
+        return new AccountRow();
     }
     public static PatternDetailsItem fromRoomObject(PatternBase p) {
         if (p instanceof PatternHeader) {
             PatternHeader ph = (PatternHeader) p;
             Header header = createHeader();
+            header.setId(ph.getId());
             header.setName(ph.getName());
             header.setPattern(ph.getRegularExpression());
-            header.setTestText(null);
+            header.setTestText(ph.getTestText());
             header.setTransactionDescription(ph.getTransactionDescription());
             header.setTransactionComment(ph.getTransactionComment());
             header.setDateDayMatchGroup(ph.getDateDayMatchGroup());
@@ -72,27 +71,30 @@ abstract public class PatternDetailsItem {
         }
         else if (p instanceof PatternAccount) {
             PatternAccount pa = (PatternAccount) p;
-            AccountRow acc = createAccountRow(pa.getPosition());
+            AccountRow acc = createAccountRow();
+            acc.setId(pa.getId());
 
-            if (Misc.emptyIsNull(pa.getAccountName()) != null)
-                acc.setAccountName(pa.getAccountName());
+            if (pa.getAccountNameMatchGroup() == null)
+                acc.setAccountName(Misc.nullIsEmpty(pa.getAccountName()));
             else
                 acc.setAccountNameMatchGroup(pa.getAccountNameMatchGroup());
 
-            if (Misc.emptyIsNull(pa.getAccountComment()) == null)
-                acc.setAccountCommentMatchGroup(pa.getAccountCommentMatchGroup());
+            if (pa.getAccountCommentMatchGroup() == null)
+                acc.setAccountComment(Misc.nullIsEmpty(pa.getAccountComment()));
             else
-                acc.setAccountComment(pa.getAccountComment());
+                acc.setAccountCommentMatchGroup(pa.getAccountCommentMatchGroup());
 
-            if (pa.getCurrency() == null) {
-                acc.setCurrencyMatchGroup(pa.getCurrencyMatchGroup());
-            }
-            else {
-                acc.setCurrency(Currency.loadById(pa.getCurrency()));
+            if (pa.getCurrencyMatchGroup() == null) {
+                final Integer currencyId = pa.getCurrency();
+                if (currencyId != null && currencyId > 0)
+                    acc.setCurrency(Currency.loadById(currencyId));
             }
+            else
+                acc.setCurrencyMatchGroup(pa.getCurrencyMatchGroup());
 
-            if (pa.getAmount() == null)
-                acc.setAmountMatchGroup(pa.getAmountMatchGroup());
+            final Integer amountMatchGroup = pa.getAmountMatchGroup();
+            if (amountMatchGroup != null && amountMatchGroup > 0)
+                acc.setAmountMatchGroup(amountMatchGroup);
             else
                 acc.setAmount(pa.getAmount());
 
@@ -128,13 +130,16 @@ abstract public class PatternDetailsItem {
     public long getId() {
         return id;
     }
-    public void setId(int id) {
+    public void setId(Long id) {
         this.id = id;
     }
+    public void setId(int id) {
+        this.id = (long) id;
+    }
     public long getPosition() {
         return position;
     }
-    public void setPosition(int position) {
+    public void setPosition(Long position) {
         this.position = position;
     }
     abstract public String getProblem(@NonNull Resources r, int patternGroupCount);
@@ -166,18 +171,18 @@ abstract public class PatternDetailsItem {
             matchGroup = origin.matchGroup;
         }
         @NonNull
-        public static PossiblyMatchedValue<Integer> withLiteralInt(int initialValue) {
+        public static PossiblyMatchedValue<Integer> withLiteralInt(Integer initialValue) {
             PossiblyMatchedValue<Integer> result = new PossiblyMatchedValue<>();
             result.setValue(initialValue);
             return result;
         }
         @NonNull
-        public static PossiblyMatchedValue<Float> withLiteralFloat(float initialValue) {
+        public static PossiblyMatchedValue<Float> withLiteralFloat(Float initialValue) {
             PossiblyMatchedValue<Float> result = new PossiblyMatchedValue<>();
             result.setValue(initialValue);
             return result;
         }
-        public static PossiblyMatchedValue<Short> withLiteralShort(short initialValue) {
+        public static PossiblyMatchedValue<Short> withLiteralShort(Short initialValue) {
             PossiblyMatchedValue<Short> result = new PossiblyMatchedValue<>();
             result.setValue(initialValue);
             return result;
@@ -212,8 +217,11 @@ abstract public class PatternDetailsItem {
         public boolean equals(PossiblyMatchedValue<T> other) {
             if (!other.literalValue == literalValue)
                 return false;
-            if (literalValue)
+            if (literalValue) {
+                if (value == null)
+                    return other.value == null;
                 return value.equals(other.value);
+            }
             else
                 return matchGroup == other.matchGroup;
         }
@@ -235,8 +243,8 @@ abstract public class PatternDetailsItem {
         private final PossiblyMatchedValue<Float> amount =
                 PossiblyMatchedValue.withLiteralFloat(0f);
         private final PossiblyMatchedValue<Currency> currency = new PossiblyMatchedValue<>();
-        private AccountRow(long id, long position) {
-            super(Type.ACCOUNT_ITEM, id, position);
+        private AccountRow() {
+            super(Type.ACCOUNT_ITEM);
         }
         public int getAccountCommentMatchGroup() {
             return accountComment.getMatchGroup();
@@ -284,10 +292,10 @@ abstract public class PatternDetailsItem {
         public void setAmountMatchGroup(int group) {
             amount.setMatchGroup(group);
         }
-        public float getAmount() {
+        public Float getAmount() {
             return amount.getValue();
         }
-        public void setAmount(float amount) {
+        public void setAmount(Float amount) {
             this.amount.setValue(amount);
         }
         public String getProblem(@NonNull Resources r, int patternGroupCount) {
@@ -316,7 +324,7 @@ abstract public class PatternDetailsItem {
             accountComment.switchToLiteral();
         }
         public PatternAccount toDBO(@NonNull Long patternId) {
-            PatternAccount result = new PatternAccount((id <= 0L) ? null : id, patternId, position);
+            PatternAccount result = new PatternAccount(id, patternId, position);
 
             if (accountName.hasLiteralValue())
                 result.setAccountName(accountName.getValue());
@@ -347,17 +355,15 @@ abstract public class PatternDetailsItem {
                 PossiblyMatchedValue.withLiteralString("");
         private PossiblyMatchedValue<String> transactionComment =
                 PossiblyMatchedValue.withLiteralString("");
-        private PossiblyMatchedValue<Short> dateYear =
-                PossiblyMatchedValue.withLiteralShort((short) 0);
-        private PossiblyMatchedValue<Short> dateMonth =
-                PossiblyMatchedValue.withLiteralShort((short) 0);
-        private PossiblyMatchedValue<Short> dateDay =
-                PossiblyMatchedValue.withLiteralShort((short) 0);
+        private PossiblyMatchedValue<Integer> dateYear = PossiblyMatchedValue.withLiteralInt(null);
+        private PossiblyMatchedValue<Integer> dateMonth = PossiblyMatchedValue.withLiteralInt(null);
+        private PossiblyMatchedValue<Integer> dateDay = PossiblyMatchedValue.withLiteralInt(null);
         private Header() {
-            super(Type.HEADER, -1, -1);
+            super(Type.HEADER);
         }
         public Header(Header origin) {
             this();
+            id = origin.id;
             name = origin.name;
             testText = origin.testText;
             setPattern(origin.pattern);
@@ -418,22 +424,22 @@ abstract public class PatternDetailsItem {
         public void setTransactionComment(String transactionComment) {
             this.transactionComment.setValue(transactionComment);
         }
-        public short getDateYear() {
+        public Integer getDateYear() {
             return dateYear.getValue();
         }
-        public void setDateYear(short dateYear) {
+        public void setDateYear(Integer dateYear) {
             this.dateYear.setValue(dateYear);
         }
-        public short getDateMonth() {
+        public Integer getDateMonth() {
             return dateMonth.getValue();
         }
-        public void setDateMonth(short dateMonth) {
+        public void setDateMonth(Integer dateMonth) {
             this.dateMonth.setValue(dateMonth);
         }
-        public short getDateDay() {
+        public Integer getDateDay() {
             return dateDay.getValue();
         }
-        public void setDateDay(short dateDay) {
+        public void setDateDay(Integer dateDay) {
             this.dateDay.setValue(dateDay);
         }
         public int getDateYearMatchGroup() {
@@ -539,8 +545,11 @@ abstract public class PatternDetailsItem {
         }
         public void switchToLiteralDateDay() { dateDay.switchToLiteral(); }
         public PatternHeader toDBO() {
-            PatternHeader result =
-                    new PatternHeader((id <= 0) ? null : id, name, position, pattern);
+            PatternHeader result = new PatternHeader(id, name, pattern);
+
+            if (Misc.emptyIsNull(testText) != null)
+                result.setTestText(testText);
+
             if (transactionDescription.hasLiteralValue())
                 result.setTransactionDescription(transactionDescription.getValue());
             else
@@ -551,6 +560,21 @@ abstract public class PatternDetailsItem {
             else
                 result.setTransactionCommentMatchGroup(transactionComment.getMatchGroup());
 
+            if (dateYear.hasLiteralValue())
+                result.setDateYear(dateYear.getValue());
+            else
+                result.setDateYearMatchGroup(dateYear.getMatchGroup());
+
+            if (dateMonth.hasLiteralValue())
+                result.setDateMonth(dateMonth.getValue());
+            else
+                result.setDateMonthMatchGroup(dateMonth.getMatchGroup());
+
+            if (dateDay.hasLiteralValue())
+                result.setDateDay(dateDay.getValue());
+            else
+                result.setDateDayMatchGroup(dateDay.getMatchGroup());
+
             return result;
         }
     }
index 5b6ab814c4930bd8103dfa276ce89a86b2cd0901..e98a28f7596393447dc479518eff01b40505c84d 100644 (file)
@@ -17,7 +17,6 @@
 
 package net.ktnx.mobileledger.ui.patterns;
 
-import android.annotation.SuppressLint;
 import android.text.Editable;
 import android.text.TextWatcher;
 import android.view.LayoutInflater;
@@ -39,11 +38,13 @@ import net.ktnx.mobileledger.model.PatternDetailsItem;
 import net.ktnx.mobileledger.ui.PatternDetailSourceSelectorFragment;
 import net.ktnx.mobileledger.ui.QRScanAbleFragment;
 import net.ktnx.mobileledger.utils.Logger;
+import net.ktnx.mobileledger.utils.Misc;
+
+import org.jetbrains.annotations.NotNull;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
-import java.util.Objects;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -59,18 +60,20 @@ class PatternDetailsAdapter extends RecyclerView.Adapter<PatternDetailsAdapter.V
                                            @NonNull PatternDetailsItem newItem) {
                 if (oldItem.getType() != newItem.getType())
                     return false;
-                if (oldItem.getType() == PatternDetailsItem.Type.HEADER)
+                if (oldItem.getType()
+                           .equals(PatternDetailsItem.Type.HEADER))
                     return true;    // only one header item, ever
                 // the rest is comparing two account row items
                 return oldItem.asAccountRowItem()
                               .getId() == newItem.asAccountRowItem()
                                                  .getId();
             }
-            @SuppressLint("DiffUtilEquals")
             @Override
             public boolean areContentsTheSame(@NonNull PatternDetailsItem oldItem,
                                               @NonNull PatternDetailsItem newItem) {
-                if (oldItem.getType() == PatternDetailsItem.Type.HEADER) {
+                if (oldItem.getType()
+                           .equals(PatternDetailsItem.Type.HEADER))
+                {
                     PatternDetailsItem.Header oldHeader = oldItem.asHeaderItem();
                     PatternDetailsItem.Header newHeader = newItem.asHeaderItem();
 
@@ -87,6 +90,7 @@ class PatternDetailsAdapter extends RecyclerView.Adapter<PatternDetailsAdapter.V
     }
     @Override
     public long getItemId(int position) {
+        // header item is always first and IDs id may duplicate some of the account IDs
         if (position == 0)
             return -1;
         PatternDetailsItem.AccountRow accRow = differ.getCurrentList()
@@ -142,9 +146,11 @@ class PatternDetailsAdapter extends RecyclerView.Adapter<PatternDetailsAdapter.V
     public String getMatchGroupText(int groupNumber) {
         PatternDetailsItem.Header header = getHeader();
         Pattern p = header.getCompiledPattern();
-        if (p == null) return null;
+        if (p == null)
+            return null;
 
-        Matcher m = p.matcher(header.getTestText());
+        final String testText = Misc.nullIsEmpty(header.getTestText());
+        Matcher m = p.matcher(testText);
         if (m.matches() && m.groupCount() >= groupNumber)
             return m.group(groupNumber);
         else
@@ -161,121 +167,105 @@ class PatternDetailsAdapter extends RecyclerView.Adapter<PatternDetailsAdapter.V
     private enum AccDetail {ACCOUNT, COMMENT, AMOUNT}
 
     public abstract static class ViewHolder extends RecyclerView.ViewHolder {
-        protected int updateInProgress = 0;
         ViewHolder(@NonNull View itemView) {
             super(itemView);
         }
-        protected void startUpdate() {
-            updateInProgress++;
-        }
-        protected void finishUpdate() {
-            if (updateInProgress <= 0)
-                throw new IllegalStateException(
-                        "Unexpected updateInProgress value " + updateInProgress);
-
-            updateInProgress--;
-        }
         abstract void bind(PatternDetailsItem item);
     }
 
     public class Header extends ViewHolder {
         private final PatternDetailsHeaderBinding b;
-        private final TextWatcher patternNameWatcher = new TextWatcher() {
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {}
-            @Override
-            public void afterTextChanged(Editable s) {
-                Object tag = b.patternDetailsItemHead.getTag();
-                if (tag != null) {
-                    final PatternDetailsItem.Header header =
-                            ((PatternDetailsItem) tag).asHeaderItem();
+        public Header(@NonNull PatternDetailsHeaderBinding binding) {
+            super(binding.getRoot());
+            b = binding;
+
+            TextWatcher patternNameWatcher = new TextWatcher() {
+                @Override
+                public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+                @Override
+                public void onTextChanged(CharSequence s, int start, int before, int count) {}
+                @Override
+                public void afterTextChanged(Editable s) {
+                    final PatternDetailsItem.Header header = getItem();
                     Logger.debug(D_PATTERN_UI,
                             "Storing changed pattern name " + s + "; header=" + header);
                     header.setName(String.valueOf(s));
                 }
-            }
-        };
-        private final TextWatcher patternWatcher = new TextWatcher() {
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {}
-            @Override
-            public void afterTextChanged(Editable s) {
-                Object tag = b.patternDetailsItemHead.getTag();
-                if (tag != null) {
-                    final PatternDetailsItem.Header header =
-                            ((PatternDetailsItem) tag).asHeaderItem();
+            };
+            b.patternName.addTextChangedListener(patternNameWatcher);
+            TextWatcher patternWatcher = new TextWatcher() {
+                @Override
+                public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+                @Override
+                public void onTextChanged(CharSequence s, int start, int before, int count) {}
+                @Override
+                public void afterTextChanged(Editable s) {
+                    final PatternDetailsItem.Header header = getItem();
                     Logger.debug(D_PATTERN_UI,
                             "Storing changed pattern " + s + "; header=" + header);
                     header.setPattern(String.valueOf(s));
                 }
-            }
-        };
-        private final TextWatcher testTextWatcher = new TextWatcher() {
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {}
-            @Override
-            public void afterTextChanged(Editable s) {
-                Object tag = b.patternDetailsItemHead.getTag();
-                if (tag != null) {
-                    final PatternDetailsItem.Header header =
-                            ((PatternDetailsItem) tag).asHeaderItem();
+            };
+            b.pattern.addTextChangedListener(patternWatcher);
+            TextWatcher testTextWatcher = new TextWatcher() {
+                @Override
+                public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+                @Override
+                public void onTextChanged(CharSequence s, int start, int before, int count) {}
+                @Override
+                public void afterTextChanged(Editable s) {
+                    final PatternDetailsItem.Header header = getItem();
                     Logger.debug(D_PATTERN_UI,
                             "Storing changed test text " + s + "; header=" + header);
                     header.setTestText(String.valueOf(s));
                 }
-            }
-        };
-        private final TextWatcher transactionDescriptionWatcher = new TextWatcher() {
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-            }
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {
+            };
+            b.testText.addTextChangedListener(testTextWatcher);
+            TextWatcher transactionDescriptionWatcher = new TextWatcher() {
+                @Override
+                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+                }
+                @Override
+                public void onTextChanged(CharSequence s, int start, int before, int count) {
 
-            }
-            @Override
-            public void afterTextChanged(Editable s) {
-                PatternDetailsItem.Header header = ((PatternDetailsItem) Objects.requireNonNull(
-                        b.patternDetailsItemHead.getTag())).asHeaderItem();
-                Logger.debug(D_PATTERN_UI,
-                        "Storing changed transaction description " + s + "; header=" + header);
-                header.setTransactionDescription(String.valueOf(s));
-            }
-        };
-        private final TextWatcher transactionCommentWatcher = new TextWatcher() {
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+                }
+                @Override
+                public void afterTextChanged(Editable s) {
+                    final PatternDetailsItem.Header header = getItem();
+                    Logger.debug(D_PATTERN_UI,
+                            "Storing changed transaction description " + s + "; header=" + header);
+                    header.setTransactionDescription(String.valueOf(s));
+                }
+            };
+            b.transactionDescription.addTextChangedListener(transactionDescriptionWatcher);
+            TextWatcher transactionCommentWatcher = new TextWatcher() {
+                @Override
+                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
 
-            }
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {
+                }
+                @Override
+                public void onTextChanged(CharSequence s, int start, int before, int count) {
 
-            }
-            @Override
-            public void afterTextChanged(Editable s) {
-                PatternDetailsItem.Header header = ((PatternDetailsItem) Objects.requireNonNull(
-                        b.patternDetailsItemHead.getTag())).asHeaderItem();
-                Logger.debug(D_PATTERN_UI,
-                        "Storing changed transaction description " + s + "; header=" + header);
-                header.setTransactionComment(String.valueOf(s));
-            }
-        };
-        public Header(@NonNull PatternDetailsHeaderBinding binding) {
-            super(binding.getRoot());
-            b = binding;
+                }
+                @Override
+                public void afterTextChanged(Editable s) {
+                    final PatternDetailsItem.Header header = getItem();
+                    Logger.debug(D_PATTERN_UI,
+                            "Storing changed transaction description " + s + "; header=" + header);
+                    header.setTransactionComment(String.valueOf(s));
+                }
+            };
+            b.transactionComment.addTextChangedListener(transactionCommentWatcher);
         }
-        Header(@NonNull View itemView) {
-            super(itemView);
-            throw new IllegalStateException("Should not be used");
+        @NotNull
+        private PatternDetailsItem.Header getItem() {
+            int pos = getAdapterPosition();
+            return differ.getCurrentList()
+                         .get(pos)
+                         .asHeaderItem();
         }
-        private void selectHeaderDetailSource(View v, PatternDetailsItem.Header header,
-                                              HeaderDetail detail) {
+        private void selectHeaderDetailSource(View v, HeaderDetail detail) {
+            PatternDetailsItem.Header header = getItem();
             Logger.debug(D_PATTERN_UI, "header is " + header);
             PatternDetailSourceSelectorFragment sel =
                     PatternDetailSourceSelectorFragment.newInstance(1, header.getPattern(),
@@ -332,117 +322,98 @@ class PatternDetailsAdapter extends RecyclerView.Adapter<PatternDetailsAdapter.V
         @Override
         void bind(PatternDetailsItem item) {
             PatternDetailsItem.Header header = item.asHeaderItem();
-            startUpdate();
-            try {
-                Logger.debug(D_PATTERN_UI, "Binding to header " + header);
-                b.patternName.setText(header.getName());
-                b.pattern.setText(header.getPattern());
-                b.testText.setText(header.getTestText());
-
-                if (header.hasLiteralDateYear()) {
-                    b.patternDetailsYearSource.setText(R.string.pattern_details_source_literal);
-                    b.patternDetailsDateYear.setText(String.valueOf(header.getDateYear()));
-                    b.patternDetailsDateYearLayout.setVisibility(View.VISIBLE);
-                }
-                else {
-                    b.patternDetailsDateYearLayout.setVisibility(View.GONE);
-                    b.patternDetailsYearSource.setText(String.format(Locale.US, "Group %d (%s)",
-                            header.getDateYearMatchGroup(), getMatchGroupText(
-                                    header.getDateYearMatchGroup())));
-                }
-                b.patternDetailsYearSourceLabel.setOnClickListener(
-                        v -> selectHeaderDetailSource(v, header, HeaderDetail.DATE_YEAR));
-                b.patternDetailsYearSource.setOnClickListener(
-                        v -> selectHeaderDetailSource(v, header, HeaderDetail.DATE_YEAR));
-
-                if (header.hasLiteralDateMonth()) {
-                    b.patternDetailsMonthSource.setText(R.string.pattern_details_source_literal);
-                    b.patternDetailsDateMonth.setText(String.valueOf(header.getDateMonth()));
-                    b.patternDetailsDateMonthLayout.setVisibility(View.VISIBLE);
-                }
-                else {
-                    b.patternDetailsDateMonthLayout.setVisibility(View.GONE);
-                    b.patternDetailsMonthSource.setText(String.format(Locale.US, "Group %d (%s)",
-                            header.getDateMonthMatchGroup(), getMatchGroupText(
-                                    header.getDateMonthMatchGroup())));
-                }
-                b.patternDetailsMonthSourceLabel.setOnClickListener(
-                        v -> selectHeaderDetailSource(v, header, HeaderDetail.DATE_MONTH));
-                b.patternDetailsMonthSource.setOnClickListener(
-                        v -> selectHeaderDetailSource(v, header, HeaderDetail.DATE_MONTH));
-
-                if (header.hasLiteralDateDay()) {
-                    b.patternDetailsDaySource.setText(R.string.pattern_details_source_literal);
-                    b.patternDetailsDateDay.setText(String.valueOf(header.getDateDay()));
-                    b.patternDetailsDateDayLayout.setVisibility(View.VISIBLE);
-                }
-                else {
-                    b.patternDetailsDateDayLayout.setVisibility(View.GONE);
-                    b.patternDetailsDaySource.setText(String.format(Locale.US, "Group %d (%s)",
-                            header.getDateDayMatchGroup(), getMatchGroupText(
-                                    header.getDateDayMatchGroup())));
-                }
-                b.patternDetailsDaySourceLabel.setOnClickListener(
-                        v -> selectHeaderDetailSource(v, header, HeaderDetail.DATE_DAY));
-                b.patternDetailsDaySource.setOnClickListener(
-                        v -> selectHeaderDetailSource(v, header, HeaderDetail.DATE_DAY));
-
-                if (header.hasLiteralTransactionDescription()) {
-                    b.patternTransactionDescriptionSource.setText(
-                            R.string.pattern_details_source_literal);
-                    b.transactionDescription.setText(header.getTransactionDescription());
-                    b.transactionDescriptionLayout.setVisibility(View.VISIBLE);
-                }
-                else {
-                    b.transactionDescriptionLayout.setVisibility(View.GONE);
-                    b.patternTransactionDescriptionSource.setText(
-                            String.format(Locale.US, "Group %d (%s)",
-                                    header.getTransactionDescriptionMatchGroup(), getMatchGroupText(
-                                            header.getTransactionDescriptionMatchGroup())));
+            Logger.debug(D_PATTERN_UI, "Binding to header " + header);
 
-                }
-                b.patternTransactionDescriptionSourceLabel.setOnClickListener(
-                        v -> selectHeaderDetailSource(v, header, HeaderDetail.DESCRIPTION));
-                b.patternTransactionDescriptionSource.setOnClickListener(
-                        v -> selectHeaderDetailSource(v, header, HeaderDetail.DESCRIPTION));
-
-                if (header.hasLiteralTransactionComment()) {
-                    b.patternTransactionCommentSource.setText(
-                            R.string.pattern_details_source_literal);
-                    b.transactionComment.setText(header.getTransactionComment());
-                    b.transactionCommentLayout.setVisibility(View.VISIBLE);
-                }
-                else {
-                    b.transactionCommentLayout.setVisibility(View.GONE);
-                    b.patternTransactionCommentSource.setText(
-                            String.format(Locale.US, "Group %d (%s)",
-                                    header.getTransactionCommentMatchGroup(),
-                                    getMatchGroupText(header.getTransactionCommentMatchGroup())));
+            b.patternName.setText(header.getName());
+            b.pattern.setText(header.getPattern());
+            b.testText.setText(header.getTestText());
 
-                }
-                b.patternTransactionCommentSourceLabel.setOnClickListener(
-                        v -> selectHeaderDetailSource(v, header, HeaderDetail.COMMENT));
-                b.patternTransactionCommentSource.setOnClickListener(
-                        v -> selectHeaderDetailSource(v, header, HeaderDetail.COMMENT));
-
-                b.patternDetailsHeadScanQrButton.setOnClickListener(this::scanTestQR);
-
-                final Object prevTag = b.patternDetailsItemHead.getTag();
-                if (!(prevTag instanceof PatternDetailsItem)) {
-                    Logger.debug(D_PATTERN_UI, "Hooked text change listeners");
-
-                    b.patternName.addTextChangedListener(patternNameWatcher);
-                    b.pattern.addTextChangedListener(patternWatcher);
-                    b.testText.addTextChangedListener(testTextWatcher);
-                    b.transactionDescription.addTextChangedListener(transactionDescriptionWatcher);
-                    b.transactionComment.addTextChangedListener(transactionCommentWatcher);
-                }
+            if (header.hasLiteralDateYear()) {
+                b.patternDetailsYearSource.setText(R.string.pattern_details_source_literal);
+                b.patternDetailsDateYear.setText(String.valueOf(header.getDateYear()));
+                b.patternDetailsDateYearLayout.setVisibility(View.VISIBLE);
+            }
+            else {
+                b.patternDetailsDateYearLayout.setVisibility(View.GONE);
+                b.patternDetailsYearSource.setText(
+                        String.format(Locale.US, "Group %d (%s)", header.getDateYearMatchGroup(),
+                                getMatchGroupText(header.getDateYearMatchGroup())));
+            }
+            b.patternDetailsYearSourceLabel.setOnClickListener(
+                    v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
+            b.patternDetailsYearSource.setOnClickListener(
+                    v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
+
+            if (header.hasLiteralDateMonth()) {
+                b.patternDetailsMonthSource.setText(R.string.pattern_details_source_literal);
+                b.patternDetailsDateMonth.setText(String.valueOf(header.getDateMonth()));
+                b.patternDetailsDateMonthLayout.setVisibility(View.VISIBLE);
+            }
+            else {
+                b.patternDetailsDateMonthLayout.setVisibility(View.GONE);
+                b.patternDetailsMonthSource.setText(
+                        String.format(Locale.US, "Group %d (%s)", header.getDateMonthMatchGroup(),
+                                getMatchGroupText(header.getDateMonthMatchGroup())));
+            }
+            b.patternDetailsMonthSourceLabel.setOnClickListener(
+                    v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
+            b.patternDetailsMonthSource.setOnClickListener(
+                    v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
+
+            if (header.hasLiteralDateDay()) {
+                b.patternDetailsDaySource.setText(R.string.pattern_details_source_literal);
+                b.patternDetailsDateDay.setText(String.valueOf(header.getDateDay()));
+                b.patternDetailsDateDayLayout.setVisibility(View.VISIBLE);
+            }
+            else {
+                b.patternDetailsDateDayLayout.setVisibility(View.GONE);
+                b.patternDetailsDaySource.setText(
+                        String.format(Locale.US, "Group %d (%s)", header.getDateDayMatchGroup(),
+                                getMatchGroupText(header.getDateDayMatchGroup())));
+            }
+            b.patternDetailsDaySourceLabel.setOnClickListener(
+                    v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
+            b.patternDetailsDaySource.setOnClickListener(
+                    v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
 
-                b.patternDetailsItemHead.setTag(item);
+            if (header.hasLiteralTransactionDescription()) {
+                b.patternTransactionDescriptionSource.setText(
+                        R.string.pattern_details_source_literal);
+                b.transactionDescription.setText(header.getTransactionDescription());
+                b.transactionDescriptionLayout.setVisibility(View.VISIBLE);
             }
-            finally {
-                finishUpdate();
+            else {
+                b.transactionDescriptionLayout.setVisibility(View.GONE);
+                b.patternTransactionDescriptionSource.setText(
+                        String.format(Locale.US, "Group %d (%s)",
+                                header.getTransactionDescriptionMatchGroup(),
+                                getMatchGroupText(header.getTransactionDescriptionMatchGroup())));
+
+            }
+            b.patternTransactionDescriptionSourceLabel.setOnClickListener(
+                    v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
+            b.patternTransactionDescriptionSource.setOnClickListener(
+                    v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
+
+            if (header.hasLiteralTransactionComment()) {
+                b.patternTransactionCommentSource.setText(R.string.pattern_details_source_literal);
+                b.transactionComment.setText(header.getTransactionComment());
+                b.transactionCommentLayout.setVisibility(View.VISIBLE);
+            }
+            else {
+                b.transactionCommentLayout.setVisibility(View.GONE);
+                b.patternTransactionCommentSource.setText(String.format(Locale.US, "Group %d (%s)",
+                        header.getTransactionCommentMatchGroup(),
+                        getMatchGroupText(header.getTransactionCommentMatchGroup())));
+
             }
+            b.patternTransactionCommentSourceLabel.setOnClickListener(
+                    v -> selectHeaderDetailSource(v, HeaderDetail.COMMENT));
+            b.patternTransactionCommentSource.setOnClickListener(
+                    v -> selectHeaderDetailSource(v, HeaderDetail.COMMENT));
+
+            b.patternDetailsHeadScanQrButton.setOnClickListener(this::scanTestQR);
+
         }
         private void scanTestQR(View view) {
             QRScanAbleFragment.triggerQRScan();
@@ -454,10 +425,35 @@ class PatternDetailsAdapter extends RecyclerView.Adapter<PatternDetailsAdapter.V
         public AccountRow(@NonNull PatternDetailsAccountBinding binding) {
             super(binding.getRoot());
             b = binding;
-        }
-        AccountRow(@NonNull View itemView) {
-            super(itemView);
-            throw new IllegalStateException("Should not be used");
+
+            TextWatcher accountNameWatcher = new TextWatcher() {
+                @Override
+                public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+                @Override
+                public void onTextChanged(CharSequence s, int start, int before, int count) {}
+                @Override
+                public void afterTextChanged(Editable s) {
+                    PatternDetailsItem.AccountRow accRow = getItem();
+                    Logger.debug(D_PATTERN_UI,
+                            "Storing changed account name " + s + "; accRow=" + accRow);
+                    accRow.setAccountName(String.valueOf(s));
+                }
+            };
+            b.patternDetailsAccountName.addTextChangedListener(accountNameWatcher);
+            TextWatcher accountCommentWatcher = new TextWatcher() {
+                @Override
+                public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+                @Override
+                public void onTextChanged(CharSequence s, int start, int before, int count) {}
+                @Override
+                public void afterTextChanged(Editable s) {
+                    PatternDetailsItem.AccountRow accRow = getItem();
+                    Logger.debug(D_PATTERN_UI,
+                            "Storing changed account comment " + s + "; accRow=" + accRow);
+                    accRow.setAccountComment(String.valueOf(s));
+                }
+            };
+            b.patternDetailsAccountComment.addTextChangedListener(accountCommentWatcher);
         }
         @Override
         void bind(PatternDetailsItem item) {
@@ -502,20 +498,25 @@ class PatternDetailsAdapter extends RecyclerView.Adapter<PatternDetailsAdapter.V
             }
 
             b.patternAccountNameSourceLabel.setOnClickListener(
-                    v -> selectAccountRowDetailSource(v, accRow, AccDetail.ACCOUNT));
+                    v -> selectAccountRowDetailSource(v, AccDetail.ACCOUNT));
             b.patternDetailsAccountNameSource.setOnClickListener(
-                    v -> selectAccountRowDetailSource(v, accRow, AccDetail.ACCOUNT));
+                    v -> selectAccountRowDetailSource(v, AccDetail.ACCOUNT));
             b.patternAccountCommentSourceLabel.setOnClickListener(
-                    v -> selectAccountRowDetailSource(v, accRow, AccDetail.COMMENT));
+                    v -> selectAccountRowDetailSource(v, AccDetail.COMMENT));
             b.patternDetailsAccountCommentSource.setOnClickListener(
-                    v -> selectAccountRowDetailSource(v, accRow, AccDetail.COMMENT));
+                    v -> selectAccountRowDetailSource(v, AccDetail.COMMENT));
             b.patternAccountAmountSourceLabel.setOnClickListener(
-                    v -> selectAccountRowDetailSource(v, accRow, AccDetail.AMOUNT));
+                    v -> selectAccountRowDetailSource(v, AccDetail.AMOUNT));
             b.patternDetailsAccountAmountSource.setOnClickListener(
-                    v -> selectAccountRowDetailSource(v, accRow, AccDetail.AMOUNT));
+                    v -> selectAccountRowDetailSource(v, AccDetail.AMOUNT));
+        }
+        private @NotNull PatternDetailsItem.AccountRow getItem() {
+            return differ.getCurrentList()
+                         .get(getAdapterPosition())
+                         .asAccountRowItem();
         }
-        private void selectAccountRowDetailSource(View v, PatternDetailsItem.AccountRow accRow,
-                                                  AccDetail detail) {
+        private void selectAccountRowDetailSource(View v, AccDetail detail) {
+            PatternDetailsItem.AccountRow accRow = getItem();
             final PatternDetailsItem.Header header = getHeader();
             Logger.debug(D_PATTERN_UI, "header is " + header);
             PatternDetailSourceSelectorFragment sel =
index 83aa17d22a82715c13e4142f7b159061f4b090b6..2c2621055d5a44408d191ee62b41e46aed033ace 100644 (file)
@@ -44,7 +44,7 @@ public class PatternDetailsFragment extends QRScanAbleFragment {
     PatternDetailsFragmentBinding b;
     private PatternDetailsViewModel mViewModel;
     private int mColumnCount = 1;
-    private int mPatternId = PatternDetailsViewModel.NEW_PATTERN;
+    private Long mPatternId;
     public PatternDetailsFragment() {
     }
     public static PatternDetailsFragment newInstance(int columnCount, int patternId) {
@@ -63,9 +63,10 @@ public class PatternDetailsFragment extends QRScanAbleFragment {
         final Bundle args = getArguments();
         if (args != null) {
             mColumnCount = args.getInt(ARG_COLUMN_COUNT, 1);
-            mPatternId = args.getInt(ARG_PATTERN_ID, PatternDetailsViewModel.NEW_PATTERN);
+            mPatternId = args.getLong(ARG_PATTERN_ID, -1);
+            if (mPatternId == -1)
+                mPatternId = null;
         }
-        mViewModel.setPatternId(mPatternId);
     }
     @Override
     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@@ -83,7 +84,7 @@ public class PatternDetailsFragment extends QRScanAbleFragment {
 
         PatternDetailsAdapter adapter = new PatternDetailsAdapter();
         b.patternDetailsRecyclerView.setAdapter(adapter);
-        mViewModel.getItems()
+        mViewModel.getItems(mPatternId)
                   .observe(getViewLifecycleOwner(), adapter::setItems);
         return b.getRoot();
     }
@@ -101,7 +102,8 @@ public class PatternDetailsFragment extends QRScanAbleFragment {
     @Override
     protected void onQrScanned(String text) {
         Logger.debug("PatDet_fr", String.format("Got scanned text '%s'", text));
-        mViewModel.setTestText(text);
+        if (text != null)
+            mViewModel.setTestText(text);
     }
     public void onSavePattern() {
         mViewModel.onSavePattern();
index 1c6e0359c3811eda293547baf0330a699b48a2d6..70a2b119c5c2b577d16fce274f75a83460a003c7 100644 (file)
 
 package net.ktnx.mobileledger.ui.patterns;
 
-import android.database.Cursor;
 import android.os.AsyncTask;
 
-import androidx.annotation.NonNull;
 import androidx.lifecycle.LiveData;
 import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.Observer;
 import androidx.lifecycle.ViewModel;
 
-import net.ktnx.mobileledger.App;
 import net.ktnx.mobileledger.dao.PatternAccountDAO;
 import net.ktnx.mobileledger.dao.PatternHeaderDAO;
 import net.ktnx.mobileledger.db.DB;
 import net.ktnx.mobileledger.db.PatternAccount;
 import net.ktnx.mobileledger.db.PatternHeader;
-import net.ktnx.mobileledger.model.Currency;
+import net.ktnx.mobileledger.db.PatternWithAccounts;
 import net.ktnx.mobileledger.model.PatternDetailsItem;
 import net.ktnx.mobileledger.utils.Logger;
-import net.ktnx.mobileledger.utils.MLDB;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.Objects;
 
 public class PatternDetailsViewModel extends ViewModel {
-    static final int NEW_PATTERN = -1;
-    private final MutableLiveData<List<PatternDetailsItem>> items = new MutableLiveData<>();
-    private long mPatternId;
+    private final MutableLiveData<List<PatternDetailsItem>> items =
+            new MutableLiveData<>(Collections.emptyList());
+    private Long mPatternId;
     private String mDefaultPatternName;
     public String getDefaultPatternName() {
         return mDefaultPatternName;
@@ -52,13 +50,21 @@ public class PatternDetailsViewModel extends ViewModel {
     public void setDefaultPatternName(String name) {
         mDefaultPatternName = name;
     }
-    public LiveData<List<PatternDetailsItem>> getItems() {
-        return items;
-    }
 
     public void resetItems() {
-        items.setValue(Collections.emptyList());
-        checkItemConsistency();
+        ArrayList<PatternDetailsItem> newList = new ArrayList<>();
+        final PatternDetailsItem.Header header = PatternDetailsItem.createHeader();
+        header.setName(mDefaultPatternName);
+        header.setId(1);
+        newList.add(header);
+
+        while (newList.size() < 3) {
+            final PatternDetailsItem.AccountRow aRow = PatternDetailsItem.createAccountRow();
+            aRow.setId(newList.size() + 1);
+            newList.add(aRow);
+        }
+
+        items.setValue(newList);
     }
     private void checkItemConsistency() {
         ArrayList<PatternDetailsItem> newList = new ArrayList<>(items.getValue());
@@ -71,98 +77,49 @@ public class PatternDetailsViewModel extends ViewModel {
         }
 
         while (newList.size() < 3) {
-            newList.add(PatternDetailsItem.createAccountRow(newList.size() - 1));
+            newList.add(PatternDetailsItem.createAccountRow());
             changes = true;
         }
 
         if (changes)
             items.setValue(newList);
     }
-    public void loadItems(long patternId) {
-        DB db = App.getRoomDB();
-        LiveData<PatternHeader> ph = db.getPatternDAO()
-                                       .getPattern(patternId);
-        ArrayList<PatternDetailsItem> list = new ArrayList<>();
-
-        MLDB.queryInBackground(
-                "SELECT name, regular_expression, transaction_description, transaction_comment, " +
-                "date_year_match_group, date_month_match_group, date_day_match_group FROM " +
-                "patterns WHERE id=?", new String[]{String.valueOf(patternId)},
-                new MLDB.CallbackHelper() {
-                    @Override
-                    public void onDone() {
-                        super.onDone();
-
-                        MLDB.queryInBackground(
-                                "SELECT id, position, acc, acc_match_group, currency, " +
-                                "currency_match_group, amount, amount_match_group," +
-                                " comment, comment_match_group FROM " +
-                                "pattern_accounts WHERE pattern_id=? ORDER BY " + "position ASC",
-                                new String[]{String.valueOf(patternId)}, new MLDB.CallbackHelper() {
-                                    @Override
-                                    public void onDone() {
-                                        super.onDone();
-                                        items.postValue(list);
-                                    }
-                                    @Override
-                                    public boolean onRow(@NonNull Cursor cursor) {
-                                        PatternDetailsItem.AccountRow item =
-                                                PatternDetailsItem.createAccountRow(
-                                                        cursor.getInt(1));
-                                        list.add(item);
-
-                                        item.setId(cursor.getInt(0));
-
-                                        if (cursor.isNull(3)) {
-                                            item.setAccountName(cursor.getString(2));
-                                        }
-                                        else {
-                                            item.setAccountNameMatchGroup(cursor.getShort(3));
-                                        }
-
-                                        if (cursor.isNull(5)) {
-                                            final int currId = cursor.getInt(4);
-                                            if (currId > 0)
-                                                item.setCurrency(Currency.loadById(currId));
-                                        }
-                                        else {
-                                            item.setCurrencyMatchGroup(cursor.getShort(5));
-                                        }
-
-                                        if (cursor.isNull(7)) {
-                                            item.setAmount(cursor.getFloat(6));
-                                        }
-                                        else {
-                                            item.setAmountMatchGroup(cursor.getShort(7));
-                                        }
-
-                                        if (cursor.isNull(9)) {
-                                            item.setAccountComment(cursor.getString(8));
-                                        }
-                                        else {
-                                            item.setAccountCommentMatchGroup(cursor.getShort(9));
-                                        }
-
-                                        return true;
-                                    }
-                                });
-                    }
-                    @Override
-                    public boolean onRow(@NonNull Cursor cursor) {
-                        PatternDetailsItem.Header header = PatternDetailsItem.createHeader();
-                        header.setName(cursor.getString(0));
-                        header.setPattern(cursor.getString(1));
-                        header.setTransactionDescription(cursor.getString(2));
-                        header.setTransactionComment(cursor.getString(3));
-                        header.setDateYearMatchGroup(cursor.getShort(4));
-                        header.setDateMonthMatchGroup(cursor.getShort(5));
-                        header.setDateDayMatchGroup(cursor.getShort(6));
-
-                        list.add(header);
-
-                        return false;
-                    }
-                });
+    public LiveData<List<PatternDetailsItem>> getItems(Long patternId) {
+        if (patternId != null && patternId <= 0)
+            throw new IllegalArgumentException("Pattern ID " + patternId + " is invalid");
+
+        mPatternId = patternId;
+
+        if (mPatternId == null) {
+            resetItems();
+            return items;
+        }
+
+        DB db = DB.get();
+        LiveData<PatternWithAccounts> dbList = db.getPatternDAO()
+                                                 .getPatternWithAccounts(mPatternId);
+        Observer<PatternWithAccounts> observer = new Observer<PatternWithAccounts>() {
+            @Override
+            public void onChanged(PatternWithAccounts src) {
+                ArrayList<PatternDetailsItem> l = new ArrayList<>();
+
+                PatternDetailsItem header = PatternDetailsItem.fromRoomObject(src.header);
+                l.add(header);
+                for (PatternAccount acc : src.accounts) {
+                    l.add(PatternDetailsItem.fromRoomObject(acc));
+                }
+
+                for (PatternDetailsItem i : l) {
+                    Logger.debug("patterns-db", "Loaded pattern item " + i);
+                }
+                items.postValue(l);
+
+                dbList.removeObserver(this);
+            }
+        };
+        dbList.observeForever(observer);
+
+        return items;
     }
     public void setTestText(String text) {
         List<PatternDetailsItem> list = new ArrayList<>(items.getValue());
@@ -173,46 +130,46 @@ public class PatternDetailsViewModel extends ViewModel {
 
         items.setValue(list);
     }
-    public void setPatternId(int patternId) {
-        if (mPatternId != patternId) {
-            if (patternId == NEW_PATTERN) {
-                resetItems();
-            }
-            else {
-                loadItems(patternId);
-            }
-            mPatternId = patternId;
-        }
-
-    }
     public void onSavePattern() {
         Logger.debug("flow", "PatternDetailsViewModel.onSavePattern(); model=" + this);
         final List<PatternDetailsItem> list = Objects.requireNonNull(items.getValue());
 
         AsyncTask.execute(() -> {
+            boolean newPattern = mPatternId == null || mPatternId <= 0;
+
             PatternDetailsItem.Header modelHeader = list.get(0)
                                                         .asHeaderItem();
-            PatternHeaderDAO headerDAO = App.getRoomDB()
-                                            .getPatternDAO();
+            PatternHeaderDAO headerDAO = DB.get()
+                                           .getPatternDAO();
             PatternHeader dbHeader = modelHeader.toDBO();
-            if (mPatternId <= 0) {
+            if (newPattern) {
                 dbHeader.setId(mPatternId = headerDAO.insert(dbHeader));
             }
             else
                 headerDAO.update(dbHeader);
 
+            Logger.debug("pattern-db",
+                    String.format(Locale.US, "Stored pattern header %d, item=%s", dbHeader.getId(),
+                            modelHeader));
 
-            PatternAccountDAO paDAO = App.getRoomDB()
-                                         .getPatternAccountDAO();
+
+            PatternAccountDAO paDAO = DB.get()
+                                        .getPatternAccountDAO();
             for (int i = 1; i < list.size(); i++) {
-                PatternAccount dbAccount = list.get(i)
-                                               .asAccountRowItem()
-                                               .toDBO(dbHeader.getId());
+                final PatternDetailsItem.AccountRow accRowItem = list.get(i)
+                                                                     .asAccountRowItem();
+                PatternAccount dbAccount = accRowItem.toDBO(dbHeader.getId());
                 dbAccount.setPatternId(mPatternId);
-                if (dbAccount.getId() == null || dbAccount.getId() <= 0)
+                dbAccount.setPosition(i);
+                if (newPattern)
                     dbAccount.setId(paDAO.insert(dbAccount));
                 else
                     paDAO.update(dbAccount);
+
+                Logger.debug("pattern-db", String.format(Locale.US,
+                        "Stored pattern account %d, account=%s, comment=%s, item=%s",
+                        dbAccount.getId(), dbAccount.getAccountName(),
+                        dbAccount.getAccountComment(), accRowItem));
             }
         });
     }
index 52aab60eef374d168dad6018e06be7f978b008e9..226f846f253929870a47278b177334599528eb67 100644 (file)
@@ -28,14 +28,20 @@ import androidx.fragment.app.Fragment;
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.LifecycleEventObserver;
 import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LiveData;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
+import net.ktnx.mobileledger.dao.PatternHeaderDAO;
 import net.ktnx.mobileledger.databinding.FragmentPatternListBinding;
+import net.ktnx.mobileledger.db.DB;
+import net.ktnx.mobileledger.db.PatternHeader;
 import net.ktnx.mobileledger.utils.Logger;
 
 import org.jetbrains.annotations.NotNull;
 
+import java.util.List;
+
 /**
  * A simple {@link Fragment} subclass.
  * Use the {@link PatternListFragment#newInstance} factory method to
@@ -79,7 +85,10 @@ public class PatternListFragment extends Fragment {
         PatternsRecyclerViewAdapter modelAdapter = new PatternsRecyclerViewAdapter();
 
         b.patternList.setAdapter(modelAdapter);
-        PatternsModel.retrievePatterns(modelAdapter);
+        PatternHeaderDAO pDao = DB.get()
+                                  .getPatternDAO();
+        LiveData<List<PatternHeader>> patterns = pDao.getPatterns();
+        patterns.observe(getViewLifecycleOwner(), list -> {modelAdapter.setPatterns(list);});
         LinearLayoutManager llm = new LinearLayoutManager(getContext());
         llm.setOrientation(RecyclerView.VERTICAL);
         b.patternList.setLayoutManager(llm);
@@ -112,7 +121,7 @@ public class PatternListFragment extends Fragment {
         if (mListener == null)
             return;
 
-        mListener.onNewPattern();
+        mListener.onEditPattern(null);
     }
     /**
      * This interface must be implemented by activities that contain this
@@ -125,8 +134,8 @@ public class PatternListFragment extends Fragment {
      * >Communicating with Other Fragments</a> for more information.
      */
     public interface OnPatternListFragmentInteractionListener {
-        void onNewPattern();
         void onSavePattern();
-        void onEditPattern(int id);
+
+        void onEditPattern(Long id);
     }
 }
\ No newline at end of file
index 829260b976f3658b4e8c09c27926f443cedd3242..b6cac9b2662335643782d15ecc2ec381f02f9dd7 100644 (file)
@@ -21,7 +21,7 @@ import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView;
 
 import net.ktnx.mobileledger.databinding.PatternLayoutBinding;
-import net.ktnx.mobileledger.model.PatternEntry;
+import net.ktnx.mobileledger.db.PatternHeader;
 
 class PatternViewHolder extends RecyclerView.ViewHolder {
     final PatternLayoutBinding b;
@@ -29,7 +29,7 @@ class PatternViewHolder extends RecyclerView.ViewHolder {
         super(binding.getRoot());
         b = binding;
     }
-    public void bindToItem(PatternEntry item) {
+    public void bindToItem(PatternHeader item) {
         b.patternName.setText(item.getName());
         b.editButon.setOnClickListener(v -> {
             ((PatternsActivity) v.getContext()).onEditPattern(item.getId());
index da258d48a7a0ec12425802c1d61b26f0f8f766fc..c9a41280f2049f9351325a87a76d2603d3c1f857 100644 (file)
@@ -82,23 +82,19 @@ public class PatternsActivity extends CrashReportingActivity
 
         b.toolbarLayout.setTitle(getString(R.string.title_activity_patterns));
 
-        b.fabAdd.setOnClickListener(v -> onNewPattern());
+        b.fabAdd.setOnClickListener(v -> onEditPattern(null));
         b.fabSave.setOnClickListener(v -> onSavePattern());
     }
     @Override
-    public void onNewPattern() {
-        navController.navigate(R.id.action_patternListFragment_to_patternDetailsFragment);
-//        final Snackbar snackbar =
-//                Snackbar.make(b.fragmentContainer, "New pattern action coming up soon",
-//                        Snackbar.LENGTH_INDEFINITE);
-//        snackbar.setAction("Action", v -> snackbar.dismiss());
-//        snackbar.show();
-    }
-    @Override
-    public void onEditPattern(int id) {
-        Bundle bundle = new Bundle();
-        bundle.putInt(PatternDetailsFragment.ARG_PATTERN_ID, id);
-        navController.navigate(R.id.action_patternListFragment_to_patternDetailsFragment, bundle);
+    public void onEditPattern(Long id) {
+        if (id == null){
+            navController.navigate(R.id.action_patternListFragment_to_patternDetailsFragment);
+        }
+        else{
+            Bundle bundle = new Bundle();
+            bundle.putLong(PatternDetailsFragment.ARG_PATTERN_ID, id);
+            navController.navigate(R.id.action_patternListFragment_to_patternDetailsFragment, bundle);
+        }
     }
     @Override
     public void onSavePattern() {
index c11a6851c69c50044dba8b1df04dd37d6066f196..642a5ca6a875440e9d0a37f77c918f7d5d6aae37 100644 (file)
@@ -26,26 +26,26 @@ import androidx.recyclerview.widget.DiffUtil;
 import androidx.recyclerview.widget.RecyclerView;
 
 import net.ktnx.mobileledger.databinding.PatternLayoutBinding;
-import net.ktnx.mobileledger.model.PatternEntry;
+import net.ktnx.mobileledger.db.PatternHeader;
 
 import org.jetbrains.annotations.NotNull;
 
 import java.util.List;
-import java.util.Objects;
 
 public class PatternsRecyclerViewAdapter extends RecyclerView.Adapter<PatternViewHolder> {
-    private final AsyncListDiffer<PatternEntry> listDiffer;
+    private final AsyncListDiffer<PatternHeader> listDiffer;
     public PatternsRecyclerViewAdapter() {
-        listDiffer = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback<PatternEntry>() {
+        listDiffer = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback<PatternHeader>() {
             @Override
-            public boolean areItemsTheSame(@NotNull PatternEntry oldItem,
-                                           @NotNull PatternEntry newItem) {
-                return oldItem.getId() == newItem.getId();
+            public boolean areItemsTheSame(@NotNull PatternHeader oldItem,
+                                           @NotNull PatternHeader newItem) {
+                return oldItem.getId()
+                              .equals(newItem.getId());
             }
             @Override
-            public boolean areContentsTheSame(@NotNull PatternEntry oldItem,
-                                              @NotNull PatternEntry newItem) {
-                return Objects.equals(oldItem.getName(), newItem.getName());
+            public boolean areContentsTheSame(@NotNull PatternHeader oldItem,
+                                              @NotNull PatternHeader newItem) {
+                return oldItem.equals(newItem);
             }
         });
     }
@@ -68,7 +68,7 @@ public class PatternsRecyclerViewAdapter extends RecyclerView.Adapter<PatternVie
         return listDiffer.getCurrentList()
                          .size();
     }
-    public void setPatterns(List<PatternEntry> newList) {
+    public void setPatterns(List<PatternHeader> newList) {
         listDiffer.submitList(newList);
     }
 }
index c75b6339ae0fb0b074deed2d2088ed7c0e811fd2..927a34d936b44326cfeed93aa9866ccd64ff770f 100644 (file)
@@ -26,6 +26,8 @@ import androidx.annotation.Nullable;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentActivity;
 
+import org.jetbrains.annotations.Contract;
+
 public class Misc {
     public static boolean isZero(float f) {
         return (f < 0.005) && (f > -0.005);
@@ -81,4 +83,18 @@ public class Misc {
 
         return string.trim();
     }
+    @Contract(value = "null, null -> true; null, !null -> false; !null, null -> false", pure = true)
+    public static boolean equalIntegers(Integer a, Integer b) {
+        if ( a == null && b == null) return true;
+        if (a == null || b == null) return false;
+
+        return a.equals(b);
+    }
+    @Contract(value = "null, null -> true; null, !null -> false; !null, null -> false", pure = true)
+    public static boolean equalLongs(Long a, Long b) {
+        if ( a == null && b == null) return true;
+        if (a == null || b == null) return false;
+
+        return a.equals(b);
+    }
 }