--- /dev/null
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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<SimpleDate, Void, Integer> {
+ @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<TransactionListItem> 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<TransactionListItem> {
+ @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;
+ }
+ }
+ }
+}
}
debug("UTT", sql);
+ SimpleDate latestDate = null, earliestDate = null;
SQLiteDatabase db = App.getDatabase();
boolean odd = true;
SimpleDate lastDate = SimpleDate.today();
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);
odd = !odd;
}
Data.transactions.setList(newList);
+ Data.latestTransactionDate.postValue(latestDate);
+ Data.earliestTransactionDate.postValue(earliestDate);
debug("UTT", "transaction list value updated");
}
/*
- * 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
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;
public final class Data {
public static final ObservableList<TransactionListItem> transactions =
new ObservableList<>(new ArrayList<>());
- public static final ObservableList<LedgerAccount> accounts = new ObservableList<>(new ArrayList<>());
- public static final MutableLiveData<Boolean> backgroundTasksRunning = new MutableLiveData<>(false);
+ public static final MutableLiveData<SimpleDate> earliestTransactionDate =
+ new MutableLiveData<>(null);
+ public static final MutableLiveData<SimpleDate> latestTransactionDate =
+ new MutableLiveData<>(null);
+ public static final ObservableList<LedgerAccount> accounts =
+ new ObservableList<>(new ArrayList<>());
+ public static final MutableLiveData<Boolean> backgroundTasksRunning =
+ new MutableLiveData<>(false);
public static final MutableLiveData<Date> lastUpdateDate = new MutableLiveData<>();
public static final MutableLiveData<MobileLedgerProfile> profile = new InertMutableLiveData<>();
public static final MutableLiveData<ArrayList<MobileLedgerProfile>> profiles =
public static final MutableLiveData<Locale> locale = new MutableLiveData<>(Locale.getDefault());
private static final AtomicInteger backgroundTaskCount = new AtomicInteger(0);
private static final Locker profilesLocker = new Locker();
+ public static MutableLiveData<Integer> foundTransactionItemIndex = new MutableLiveData<>(null);
private static RetrieveTransactionsTask retrieveTransactionsTask;
public static final MutableLiveData<Boolean> drawerOpen = new MutableLiveData<>(false);
public static void backgroundTaskStarted() {
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;
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) {
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;
// 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());
}
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();
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);
public Type getType() {
return type;
}
+ @NonNull
public SimpleDate getDate() {
- return date;
+ return (date != null) ? date : transaction.getDate();
}
public boolean isMonthShown() {
return monthShown;
@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);
/*
- * 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
import android.content.Context;
import android.database.Cursor;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
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;
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()
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<SimpleDate, Void, Integer> finder = new TransactionDateFinder();
+
+ finder.execute(new SimpleDate(year, month + 1, day));
}
}
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();
import java.util.Calendar;
import java.util.Date;
-public class SimpleDate {
+public class SimpleDate implements Comparable<SimpleDate> {
public int year;
public int month;
public int day;
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)
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);
+ }
}
~ Modified/adapted by Damyan Ivanov for MoLe
-->
-<vector android:height="24dp" android:tint="#EEEEEE"
- android:viewportHeight="24.0" android:viewportWidth="24.0"
- android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
- <path android:fillColor="#FF000000" android:pathData="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z"/>
+<vector android:height="24dp"
+ android:tint="?colorOnPrimary"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0"
+ android:width="24dp"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ >
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z"
+ />
</vector>
--- /dev/null
+<!--
+ ~ Copyright Google Inc.
+ ~
+ ~ Licensed under the Apache License, version 2.0 ("the License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the license at:
+ ~
+ ~ https://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distribution under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ ~ Modified/adapted by Damyan Ivanov for MoLe
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:autoMirrored="true"
+ android:tint="?colorOnPrimary"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ >
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,3h-1L19,1h-2v2L7,3L7,1L5,1v2L4,3c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,5c0,-1.1 -0.9,-2 -2,-2zM20,21L4,21L4,8h16v13z"
+ />
+</vector>
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright © 2019 Damyan Ivanov.
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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
~ along with MoLe. If not, see <https://www.gnu.org/licenses/>.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <CalendarView
- android:id="@+id/calendarView"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-</LinearLayout>
\ No newline at end of file
+<CalendarView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/calendarView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
\ No newline at end of file
xmlns:tools="http://schemas.android.com/tools"
>
+ <item
+ android:id="@+id/menu_go_to_date"
+ android:icon="@drawable/ic_baseline_calendar_today_24"
+ android:title="@string/go_to_date_menu_title"
+ app:showAsAction="ifRoom"
+ />
<item
android:id="@+id/menu_transaction_list_filter"
android:icon="@drawable/ic_filter_list_white_24dp"
--- /dev/null
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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