From: Damyan Ivanov Date: Sat, 30 Jan 2021 11:56:08 +0000 (+0000) Subject: rework pattern management machinery X-Git-Tag: v0.17.0~203 X-Git-Url: https://git.ktnx.net/?a=commitdiff_plain;h=b0404689e0dbb2b08f02deca7ee5d14636a3baa6;p=mobile-ledger.git rework pattern management machinery 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 --- diff --git a/app/src/main/java/net/ktnx/mobileledger/App.java b/app/src/main/java/net/ktnx/mobileledger/App.java index 7935beba..a492e8a4 100644 --- a/app/src/main/java/net/ktnx/mobileledger/App.java +++ b/app/src/main/java/net/ktnx/mobileledger/App.java @@ -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 diff --git a/app/src/main/java/net/ktnx/mobileledger/dao/PatternHeaderDAO.java b/app/src/main/java/net/ktnx/mobileledger/dao/PatternHeaderDAO.java index 4f4dcd7a..43f688aa 100644 --- a/app/src/main/java/net/ktnx/mobileledger/dao/PatternHeaderDAO.java +++ b/app/src/main/java/net/ktnx/mobileledger/dao/PatternHeaderDAO.java @@ -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 getPattern(Long id); -// not useful for now -// @Transaction -// @Query("SELECT * FROM patterns") -// List getPatternsWithAccounts(); + @Transaction + @Query("SELECT * FROM patterns WHERE id = :id") + LiveData getPatternWithAccounts(Long id); } diff --git a/app/src/main/java/net/ktnx/mobileledger/db/DB.java b/app/src/main/java/net/ktnx/mobileledger/db/DB.java index eb9c090a..2dbc7c24 100644 --- a/app/src/main/java/net/ktnx/mobileledger/db/DB.java +++ b/app/src/main/java/net/ktnx/mobileledger/db/DB.java @@ -18,14 +18,30 @@ 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(); diff --git a/app/src/main/java/net/ktnx/mobileledger/db/PatternAccount.java b/app/src/main/java/net/ktnx/mobileledger/db/PatternAccount.java index fe8769bf..2b91c5d1 100644 --- a/app/src/main/java/net/ktnx/mobileledger/db/PatternAccount.java +++ b/app/src/main/java/net/ktnx/mobileledger/db/PatternAccount.java @@ -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; } diff --git a/app/src/main/java/net/ktnx/mobileledger/db/PatternHeader.java b/app/src/main/java/net/ktnx/mobileledger/db/PatternHeader.java index 091f1f2e..c6223c4f 100644 --- a/app/src/main/java/net/ktnx/mobileledger/db/PatternHeader.java +++ b/app/src/main/java/net/ktnx/mobileledger/db/PatternHeader.java @@ -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 index 00000000..8992fe18 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/db/PatternWithAccounts.java @@ -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 . + */ + +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 accounts; + + public Long getId() { + return header.getId(); + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/model/PatternDetailsItem.java b/app/src/main/java/net/ktnx/mobileledger/model/PatternDetailsItem.java index 82b1bb45..43c77f72 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/PatternDetailsItem.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/PatternDetailsItem.java @@ -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 withLiteralInt(int initialValue) { + public static PossiblyMatchedValue withLiteralInt(Integer initialValue) { PossiblyMatchedValue result = new PossiblyMatchedValue<>(); result.setValue(initialValue); return result; } @NonNull - public static PossiblyMatchedValue withLiteralFloat(float initialValue) { + public static PossiblyMatchedValue withLiteralFloat(Float initialValue) { PossiblyMatchedValue result = new PossiblyMatchedValue<>(); result.setValue(initialValue); return result; } - public static PossiblyMatchedValue withLiteralShort(short initialValue) { + public static PossiblyMatchedValue withLiteralShort(Short initialValue) { PossiblyMatchedValue result = new PossiblyMatchedValue<>(); result.setValue(initialValue); return result; @@ -212,8 +217,11 @@ abstract public class PatternDetailsItem { public boolean equals(PossiblyMatchedValue 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 amount = PossiblyMatchedValue.withLiteralFloat(0f); private final PossiblyMatchedValue 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 transactionComment = PossiblyMatchedValue.withLiteralString(""); - private PossiblyMatchedValue dateYear = - PossiblyMatchedValue.withLiteralShort((short) 0); - private PossiblyMatchedValue dateMonth = - PossiblyMatchedValue.withLiteralShort((short) 0); - private PossiblyMatchedValue dateDay = - PossiblyMatchedValue.withLiteralShort((short) 0); + private PossiblyMatchedValue dateYear = PossiblyMatchedValue.withLiteralInt(null); + private PossiblyMatchedValue dateMonth = PossiblyMatchedValue.withLiteralInt(null); + private PossiblyMatchedValue 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; } } diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternDetailsAdapter.java b/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternDetailsAdapter.java index 5b6ab814..e98a28f7 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternDetailsAdapter.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternDetailsAdapter.java @@ -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= groupNumber) return m.group(groupNumber); else @@ -161,121 +167,105 @@ class PatternDetailsAdapter extends RecyclerView.Adapter 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> items = new MutableLiveData<>(); - private long mPatternId; + private final MutableLiveData> 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> getItems() { - return items; - } public void resetItems() { - items.setValue(Collections.emptyList()); - checkItemConsistency(); + ArrayList 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 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 ph = db.getPatternDAO() - .getPattern(patternId); - ArrayList 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> 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 dbList = db.getPatternDAO() + .getPatternWithAccounts(mPatternId); + Observer observer = new Observer() { + @Override + public void onChanged(PatternWithAccounts src) { + ArrayList 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 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 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)); } }); } diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternListFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternListFragment.java index 52aab60e..226f846f 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternListFragment.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternListFragment.java @@ -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> 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 for more information. */ public interface OnPatternListFragmentInteractionListener { - void onNewPattern(); void onSavePattern(); - void onEditPattern(int id); + + void onEditPattern(Long id); } } \ No newline at end of file diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternViewHolder.java b/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternViewHolder.java index 829260b9..b6cac9b2 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternViewHolder.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternViewHolder.java @@ -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()); diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternsActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternsActivity.java index da258d48..c9a41280 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternsActivity.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternsActivity.java @@ -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() { diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternsRecyclerViewAdapter.java b/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternsRecyclerViewAdapter.java index c11a6851..642a5ca6 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternsRecyclerViewAdapter.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternsRecyclerViewAdapter.java @@ -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 { - private final AsyncListDiffer listDiffer; + private final AsyncListDiffer listDiffer; public PatternsRecyclerViewAdapter() { - listDiffer = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback() { + listDiffer = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback() { @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 newList) { + public void setPatterns(List newList) { listDiffer.submitList(newList); } } diff --git a/app/src/main/java/net/ktnx/mobileledger/utils/Misc.java b/app/src/main/java/net/ktnx/mobileledger/utils/Misc.java index c75b6339..927a34d9 100644 --- a/app/src/main/java/net/ktnx/mobileledger/utils/Misc.java +++ b/app/src/main/java/net/ktnx/mobileledger/utils/Misc.java @@ -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); + } }