From: Damyan Ivanov Date: Tue, 16 Jun 2020 21:02:40 +0000 (+0300) Subject: new: go to a date from transaction list X-Git-Tag: v0.14.0~13 X-Git-Url: https://git.ktnx.net/?p=mobile-ledger.git;a=commitdiff_plain;h=2c14b80572cc9199f7ed0171786a04931075b50d new: go to a date from transaction list --- diff --git a/app/src/main/java/net/ktnx/mobileledger/async/TransactionDateFinder.java b/app/src/main/java/net/ktnx/mobileledger/async/TransactionDateFinder.java new file mode 100644 index 00000000..c15e5990 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/async/TransactionDateFinder.java @@ -0,0 +1,81 @@ +/* + * Copyright © 2020 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.async; + +import android.os.AsyncTask; + +import net.ktnx.mobileledger.model.Data; +import net.ktnx.mobileledger.model.TransactionListItem; +import net.ktnx.mobileledger.utils.LockHolder; +import net.ktnx.mobileledger.utils.Logger; +import net.ktnx.mobileledger.utils.SimpleDate; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +public class TransactionDateFinder extends AsyncTask { + @Override + protected void onPostExecute(Integer pos) { + Data.foundTransactionItemIndex.setValue(pos); + } + @Override + protected Integer doInBackground(SimpleDate... simpleDates) { + SimpleDate date = simpleDates[0]; + Logger.debug("go-to-date", + String.format(Locale.US, "Looking for date %04d-%02d-%02d", date.year, date.month, + date.day)); + Logger.debug("go-to-date", String.format(Locale.US, "List contains %d transactions", + Data.transactions.size())); + + try (LockHolder locker = Data.transactions.lockForWriting()) { + List transactions = Data.transactions.getList(); + TransactionListItem target = new TransactionListItem(date, true); + int found = Collections.binarySearch(transactions, target, + new TransactionListItemComparator()); + if (found >= 0) + return found; + else + return 1 - found; + } + } + static class TransactionListItemComparator implements Comparator { + @Override + public int compare(TransactionListItem a, TransactionListItem b) { + final SimpleDate aDate = a.getDate(); + final SimpleDate bDate = b.getDate(); + int res = aDate.compareTo(bDate); + if (res != 0) + return -res; // transactions are reverse sorted by date + + if (a.getType() == TransactionListItem.Type.DELIMITER) { + if (b.getType() == TransactionListItem.Type.DELIMITER) + return 0; + else + return -1; + } + else { + if (b.getType() == TransactionListItem.Type.DELIMITER) + return +1; + else + return 0; + } + } + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/async/UpdateTransactionsTask.java b/app/src/main/java/net/ktnx/mobileledger/async/UpdateTransactionsTask.java index bb3d3dde..ab114531 100644 --- a/app/src/main/java/net/ktnx/mobileledger/async/UpdateTransactionsTask.java +++ b/app/src/main/java/net/ktnx/mobileledger/async/UpdateTransactionsTask.java @@ -63,6 +63,7 @@ public class UpdateTransactionsTask extends AsyncTask { } debug("UTT", sql); + SimpleDate latestDate = null, earliestDate = null; SQLiteDatabase db = App.getDatabase(); boolean odd = true; SimpleDate lastDate = SimpleDate.today(); @@ -75,6 +76,10 @@ public class UpdateTransactionsTask extends AsyncTask { SimpleDate date = new SimpleDate(cursor.getInt(1), cursor.getInt(2), cursor.getInt(3)); + if (null == latestDate) + latestDate = date; + earliestDate = date; + if (!date.equals(lastDate)) { boolean showMonth = (date.month != lastDate.month) || (date.year != lastDate.year); @@ -88,6 +93,8 @@ public class UpdateTransactionsTask extends AsyncTask { odd = !odd; } Data.transactions.setList(newList); + Data.latestTransactionDate.postValue(latestDate); + Data.earliestTransactionDate.postValue(earliestDate); debug("UTT", "transaction list value updated"); } diff --git a/app/src/main/java/net/ktnx/mobileledger/model/Data.java b/app/src/main/java/net/ktnx/mobileledger/model/Data.java index 7c5895a1..5764434f 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/Data.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/Data.java @@ -1,5 +1,5 @@ /* - * Copyright © 2019 Damyan Ivanov. + * Copyright © 2020 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 @@ -31,6 +31,7 @@ import net.ktnx.mobileledger.utils.Locker; import net.ktnx.mobileledger.utils.Logger; import net.ktnx.mobileledger.utils.MLDB; import net.ktnx.mobileledger.utils.ObservableList; +import net.ktnx.mobileledger.utils.SimpleDate; import java.lang.ref.WeakReference; import java.text.NumberFormat; @@ -45,8 +46,14 @@ import static net.ktnx.mobileledger.utils.Logger.debug; public final class Data { public static final ObservableList transactions = new ObservableList<>(new ArrayList<>()); - public static final ObservableList accounts = new ObservableList<>(new ArrayList<>()); - public static final MutableLiveData backgroundTasksRunning = new MutableLiveData<>(false); + public static final MutableLiveData earliestTransactionDate = + new MutableLiveData<>(null); + public static final MutableLiveData latestTransactionDate = + new MutableLiveData<>(null); + public static final ObservableList accounts = + new ObservableList<>(new ArrayList<>()); + public static final MutableLiveData backgroundTasksRunning = + new MutableLiveData<>(false); public static final MutableLiveData lastUpdateDate = new MutableLiveData<>(); public static final MutableLiveData profile = new InertMutableLiveData<>(); public static final MutableLiveData> profiles = @@ -58,6 +65,7 @@ public final class Data { public static final MutableLiveData locale = new MutableLiveData<>(Locale.getDefault()); private static final AtomicInteger backgroundTaskCount = new AtomicInteger(0); private static final Locker profilesLocker = new Locker(); + public static MutableLiveData foundTransactionItemIndex = new MutableLiveData<>(null); private static RetrieveTransactionsTask retrieveTransactionsTask; public static final MutableLiveData drawerOpen = new MutableLiveData<>(false); public static void backgroundTaskStarted() { diff --git a/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java b/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java index 9b73d2ce..5bc8456b 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java @@ -20,6 +20,9 @@ package net.ktnx.mobileledger.model; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import androidx.annotation.NonNull; + +import net.ktnx.mobileledger.App; import net.ktnx.mobileledger.utils.Digest; import net.ktnx.mobileledger.utils.Globals; import net.ktnx.mobileledger.utils.SimpleDate; @@ -98,7 +101,11 @@ public class LedgerTransaction { accounts.add(item); dataHash = null; } + @NonNull public SimpleDate getDate() { + loadData(App.getDatabase()); + if (date == null) + throw new IllegalStateException("Transaction has no date"); return date; } public void setDate(SimpleDate date) { diff --git a/app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java b/app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java index 925e05eb..2de99c8f 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java @@ -29,12 +29,11 @@ import net.ktnx.mobileledger.App; import net.ktnx.mobileledger.R; import net.ktnx.mobileledger.async.DbOpQueue; import net.ktnx.mobileledger.async.SendTransactionTask; -import net.ktnx.mobileledger.utils.Globals; -import net.ktnx.mobileledger.utils.Logger; import net.ktnx.mobileledger.utils.MLDB; import net.ktnx.mobileledger.utils.Misc; import java.util.ArrayList; +import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Locale; @@ -60,6 +59,8 @@ public final class MobileLedgerProfile { // N.B. when adding new fields, update the copy-constructor below private FutureDates futureDates = FutureDates.None; private SendTransactionTask.API apiVersion = SendTransactionTask.API.auto; + private Calendar firstTransactionDate; + private Calendar lastTransactionDate; public MobileLedgerProfile() { this.uuid = String.valueOf(UUID.randomUUID()); } @@ -525,7 +526,7 @@ public final class MobileLedgerProfile { db.execSQL("delete from transactions where profile=?", pUuid); db.execSQL("delete from transaction_accounts where profile=?", pUuid); db.setTransactionSuccessful(); - Logger.debug("wipe", String.format(Locale.ENGLISH, "Profile %s wiped out", pUuid[0])); + debug("wipe", String.format(Locale.ENGLISH, "Profile %s wiped out", pUuid[0])); } finally { db.endTransaction(); @@ -567,6 +568,12 @@ public final class MobileLedgerProfile { return null; } } + public Calendar getFirstTransactionDate() { + return firstTransactionDate; + } + public Calendar getLastTransactionDate() { + return lastTransactionDate; + } public enum FutureDates { None(0), OneWeek(7), TwoWeeks(14), OneMonth(30), TwoMonths(60), ThreeMonths(90), SixMonths(180), OneYear(365), All(-1); diff --git a/app/src/main/java/net/ktnx/mobileledger/model/TransactionListItem.java b/app/src/main/java/net/ktnx/mobileledger/model/TransactionListItem.java index ab8bb7ac..084eaad8 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/TransactionListItem.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/TransactionListItem.java @@ -41,8 +41,9 @@ public class TransactionListItem { public Type getType() { return type; } + @NonNull public SimpleDate getDate() { - return date; + return (date != null) ? date : transaction.getDate(); } public boolean isMonthShown() { return monthShown; diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/DatePickerFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/DatePickerFragment.java index 80c69e59..5e0fe82a 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/DatePickerFragment.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/DatePickerFragment.java @@ -121,7 +121,7 @@ public class DatePickerFragment extends AppCompatDialogFragment @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - Dialog dpd = new Dialog(Objects.requireNonNull(getActivity())); + Dialog dpd = new Dialog(requireActivity()); dpd.setContentView(R.layout.date_picker_view); dpd.setTitle(null); CalendarView cv = dpd.findViewById(R.id.calendarView); diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListFragment.java index 3625ea40..68e243a8 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListFragment.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListFragment.java @@ -1,5 +1,5 @@ /* - * Copyright © 2019 Damyan Ivanov. + * Copyright © 2020 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 @@ -19,6 +19,7 @@ package net.ktnx.mobileledger.ui.transaction_list; import android.content.Context; import android.database.Cursor; +import android.os.AsyncTask; import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; @@ -37,21 +38,28 @@ import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.snackbar.Snackbar; import net.ktnx.mobileledger.R; +import net.ktnx.mobileledger.async.TransactionDateFinder; import net.ktnx.mobileledger.model.Data; +import net.ktnx.mobileledger.ui.DatePickerFragment; import net.ktnx.mobileledger.ui.MobileLedgerListFragment; import net.ktnx.mobileledger.ui.activity.MainActivity; import net.ktnx.mobileledger.utils.Colors; import net.ktnx.mobileledger.utils.Globals; +import net.ktnx.mobileledger.utils.Logger; import net.ktnx.mobileledger.utils.MLDB; +import net.ktnx.mobileledger.utils.SimpleDate; import org.jetbrains.annotations.NotNull; +import java.util.Locale; + import static android.content.Context.INPUT_METHOD_SERVICE; import static net.ktnx.mobileledger.utils.Logger.debug; // TODO: support transaction-level comment -public class TransactionListFragment extends MobileLedgerListFragment { +public class TransactionListFragment extends MobileLedgerListFragment + implements DatePickerFragment.DatePickedListener { private MenuItem menuTransactionListFilter; private View vAccountFilter; private AutoCompleteTextView accNameFilter; @@ -153,6 +161,12 @@ public class TransactionListFragment extends MobileLedgerListFragment { menuTransactionListFilter.setVisible(true); Globals.hideSoftKeyboard(mActivity); }); + + Data.foundTransactionItemIndex.observe(getViewLifecycleOwner(), pos -> { + Logger.debug("go-to-date", String.format(Locale.US, "Found pos %d", pos)); + if (pos != null) + root.scrollToPosition(pos); + }); } private void onAccountNameFilterChanged(String accName) { final String fieldText = accNameFilter.getText() @@ -200,5 +214,22 @@ public class TransactionListFragment extends MobileLedgerListFragment { return true; }); + + menu.findItem(R.id.menu_go_to_date) + .setOnMenuItemClickListener(item -> { + DatePickerFragment picker = new DatePickerFragment(); + picker.setOnDatePickedListener(this); + picker.setDateRange(Data.earliestTransactionDate.getValue(), + Data.latestTransactionDate.getValue()); + picker.show(requireActivity().getSupportFragmentManager(), null); + return true; + }); + } + @Override + public void onDatePicked(int year, int month, int day) { + RecyclerView list = requireActivity().findViewById(R.id.transaction_root); + AsyncTask finder = new TransactionDateFinder(); + + finder.execute(new SimpleDate(year, month + 1, day)); } } diff --git a/app/src/main/java/net/ktnx/mobileledger/utils/Globals.java b/app/src/main/java/net/ktnx/mobileledger/utils/Globals.java index e1ce8eea..c6cc87a3 100644 --- a/app/src/main/java/net/ktnx/mobileledger/utils/Globals.java +++ b/app/src/main/java/net/ktnx/mobileledger/utils/Globals.java @@ -47,7 +47,7 @@ public final class Globals { public static String[] monthNames; public static String developerEmail = "dam+mole-crash@ktnx.net"; private static Pattern reLedgerDate = - Pattern.compile("^(?:(\\d+)/)??(?:(\\d\\d?)/)?(\\d\\d?)$"); + Pattern.compile("^(?:(?:(\\d+)/)??(\\d\\d?)/)?(\\d\\d?)$"); public static void hideSoftKeyboard(Activity act) { // hide the keyboard View v = act.getCurrentFocus(); diff --git a/app/src/main/java/net/ktnx/mobileledger/utils/SimpleDate.java b/app/src/main/java/net/ktnx/mobileledger/utils/SimpleDate.java index ff6c0e55..28ad751d 100644 --- a/app/src/main/java/net/ktnx/mobileledger/utils/SimpleDate.java +++ b/app/src/main/java/net/ktnx/mobileledger/utils/SimpleDate.java @@ -23,7 +23,7 @@ import androidx.annotation.Nullable; import java.util.Calendar; import java.util.Date; -public class SimpleDate { +public class SimpleDate implements Comparable { public int year; public int month; public int day; @@ -56,14 +56,7 @@ public class SimpleDate { if (date == null) return false; - if (year != date.year) - return false; - if (month != date.month) - return false; - if (day != date.day) - return false; - - return true; + return ((year == date.year) && (month == date.month) && (day == date.day)); } public boolean earlierThan(@NonNull SimpleDate date) { if (year < date.year) @@ -87,4 +80,15 @@ public class SimpleDate { return false; return (day > date.day); } + public int compareTo(SimpleDate date) { + int res = Integer.compare(year, date.year); + if (res != 0) + return res; + + res = Integer.compare(month, date.month); + if (res != 0) + return res; + + return Integer.compare(day, date.day); + } } diff --git a/app/src/main/res/drawable-anydpi-v21/ic_filter_list_white_24dp.xml b/app/src/main/res/drawable-anydpi-v21/ic_filter_list_white_24dp.xml index 336d3ccb..b864b8a0 100644 --- a/app/src/main/res/drawable-anydpi-v21/ic_filter_list_white_24dp.xml +++ b/app/src/main/res/drawable-anydpi-v21/ic_filter_list_white_24dp.xml @@ -16,8 +16,15 @@ ~ Modified/adapted by Damyan Ivanov for MoLe --> - - + + diff --git a/app/src/main/res/drawable/ic_baseline_calendar_today_24.xml b/app/src/main/res/drawable/ic_baseline_calendar_today_24.xml new file mode 100644 index 00000000..18fcdbcc --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_calendar_today_24.xml @@ -0,0 +1,31 @@ + + + + + diff --git a/app/src/main/res/layout/date_picker_view.xml b/app/src/main/res/layout/date_picker_view.xml index 8c1658f6..bc5194e2 100644 --- a/app/src/main/res/layout/date_picker_view.xml +++ b/app/src/main/res/layout/date_picker_view.xml @@ -1,6 +1,5 @@ - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/menu/transaction_list.xml b/app/src/main/res/menu/transaction_list.xml index 063e43df..8ebea83d 100644 --- a/app/src/main/res/menu/transaction_list.xml +++ b/app/src/main/res/menu/transaction_list.xml @@ -20,6 +20,12 @@ xmlns:tools="http://schemas.android.com/tools" > + . + */ + +package net.ktnx.mobileledger.utils; + +import org.junit.After; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +public class SimpleDateTest { + + @After + public void tearDown() throws Exception { + } + @Test + public void compareTo() { + SimpleDate d1 = new SimpleDate(2020, 6, 1); + SimpleDate d2 = new SimpleDate(2019, 7, 6); + + assertTrue(d1.compareTo(d2) > 0); + assertTrue(d2.compareTo(d1) < 0); + assertTrue(d1.compareTo(new SimpleDate(2020, 6, 2)) < 0); + assertTrue(d1.compareTo(new SimpleDate(2020, 5, 2)) > 0); + assertTrue(d1.compareTo(new SimpleDate(2019, 5, 2)) > 0); + } +} \ No newline at end of file