major refactor, make account summary and transaction list fragments, part of the...
authorDamyan Ivanov <dam+mobileledger@ktnx.net>
Mon, 31 Dec 2018 05:31:57 +0000 (05:31 +0000)
committerDamyan Ivanov <dam+mobileledger@ktnx.net>
Mon, 31 Dec 2018 05:31:57 +0000 (05:31 +0000)
35 files changed:
app/src/main/AndroidManifest.xml
app/src/main/java/net/ktnx/mobileledger/AccountSummary.java [deleted file]
app/src/main/java/net/ktnx/mobileledger/AccountSummaryViewModel.java [deleted file]
app/src/main/java/net/ktnx/mobileledger/AppCompatPreferenceActivity.java [deleted file]
app/src/main/java/net/ktnx/mobileledger/NewTransactionActivity.java [deleted file]
app/src/main/java/net/ktnx/mobileledger/OnSwipeTouchListener.java [deleted file]
app/src/main/java/net/ktnx/mobileledger/RecyclerItemListener.java [deleted file]
app/src/main/java/net/ktnx/mobileledger/SettingsActivity.java [deleted file]
app/src/main/java/net/ktnx/mobileledger/TransactionListActivity.java [deleted file]
app/src/main/java/net/ktnx/mobileledger/TransactionListAdapter.java [deleted file]
app/src/main/java/net/ktnx/mobileledger/async/RetrieveAccountsTask.java
app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java
app/src/main/java/net/ktnx/mobileledger/ui/OnSwipeTouchListener.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/ui/RecyclerItemListener.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryViewModel.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/ui/activity/AppCompatPreferenceActivity.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionActivity.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/ui/activity/SettingsActivity.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListFragment.java
app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListViewModel.java
app/src/main/java/net/ktnx/mobileledger/utils/Globals.java
app/src/main/res/layout/account_summary_fragment.xml [new file with mode: 0644]
app/src/main/res/layout/activity_new_transaction.xml
app/src/main/res/layout/app_bar_account_summary.xml
app/src/main/res/layout/content_account_summary.xml
app/src/main/res/layout/content_new_transaction.xml
app/src/main/res/layout/drawer.xml
app/src/main/res/layout/transaction_list_activity.xml
app/src/main/res/layout/transaction_list_fragment.xml
app/src/main/res/menu/account_summary.xml
app/src/main/res/menu/transaction_list.xml
app/src/main/res/xml/pref_headers.xml

index 1cd74d1..5ab5cdc 100644 (file)
@@ -31,7 +31,7 @@
         android:name="net.ktnx.mobileledger.MobileLedgerApplication"
         android:theme="@style/AppTheme">
         <activity
-            android:name=".AccountSummary"
+            android:name=".ui.activity.MainActivity"
             android:label="@string/account_summary_title"
             android:theme="@style/AppTheme.NoActionBar">
             <intent-filter>
             </intent-filter>
         </activity>
         <activity
-            android:name=".SettingsActivity"
+            android:name=".ui.activity.SettingsActivity"
             android:label="@string/title_activity_settings"
-            android:parentActivityName=".AccountSummary">
+            android:parentActivityName=".ui.activity.MainActivity">
             <meta-data
                 android:name="android.support.PARENT_ACTIVITY"
-                android:value="net.ktnx.mobileledger.AccountSummary" />
+                android:value="net.ktnx.mobileledger.ui.activity.MainActivity" />
         </activity>
         <activity
-            android:name=".NewTransactionActivity"
+            android:name=".ui.activity.NewTransactionActivity"
             android:label="@string/title_activity_new_transaction"
-            android:parentActivityName=".AccountSummary"
+            android:parentActivityName=".ui.activity.MainActivity"
             android:theme="@style/AppTheme.NoActionBar">
             <meta-data
                 android:name="android.support.PARENT_ACTIVITY"
-                android:value="net.ktnx.mobileledger.AccountSummary" />
-        </activity>
-        <activity
-            android:name=".TransactionListActivity"
-            android:label="@string/title_activity_transaction_list"
-            android:parentActivityName=".AccountSummary"
-            android:theme="@style/AppTheme.NoActionBar">
-            <meta-data
-                android:name="android.support.PARENT_ACTIVITY"
-                android:value="net.ktnx.mobileledger.AccountSummary" />
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
+                android:value="net.ktnx.mobileledger.ui.activity.MainActivity" />
         </activity>
     </application>
 
diff --git a/app/src/main/java/net/ktnx/mobileledger/AccountSummary.java b/app/src/main/java/net/ktnx/mobileledger/AccountSummary.java
deleted file mode 100644 (file)
index 3a59003..0000000
+++ /dev/null
@@ -1,311 +0,0 @@
-/*
- * Copyright © 2018 Damyan Ivanov.
- * This file is part of Mobile-Ledger.
- * Mobile-Ledger 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.
- *
- * Mobile-Ledger 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 Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
- */
-
-package net.ktnx.mobileledger;
-
-import android.arch.lifecycle.ViewModelProviders;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageInfo;
-import android.os.Build;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.support.annotation.NonNull;
-import android.support.design.widget.FloatingActionButton;
-import android.support.design.widget.Snackbar;
-import android.support.v4.view.GravityCompat;
-import android.support.v4.widget.DrawerLayout;
-import android.support.v4.widget.SwipeRefreshLayout;
-import android.support.v7.app.ActionBarDrawerToggle;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.Toolbar;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import net.ktnx.mobileledger.async.RetrieveAccountsTask;
-import net.ktnx.mobileledger.model.LedgerAccount;
-import net.ktnx.mobileledger.utils.MLDB;
-
-import java.lang.ref.WeakReference;
-import java.util.Date;
-import java.util.List;
-
-import static net.ktnx.mobileledger.SettingsActivity.PREF_KEY_SHOW_ONLY_STARRED_ACCOUNTS;
-
-public class AccountSummary extends AppCompatActivity {
-    private static long account_list_last_updated;
-    private static boolean account_list_needs_update = true;
-    DrawerLayout drawer;
-    MenuItem mShowHiddenAccounts;
-    SharedPreferences.OnSharedPreferenceChangeListener sBindPreferenceSummaryToValueListener;
-    private AccountSummaryViewModel model;
-    private AccountSummaryAdapter modelAdapter;
-    private Menu optMenu;
-
-    public static void preferences_changed() {
-        account_list_needs_update = true;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_account_summary);
-        Toolbar toolbar = findViewById(R.id.toolbar);
-        setSupportActionBar(toolbar);
-
-        drawer = findViewById(R.id.drawer_layout);
-        ActionBarDrawerToggle toggle =
-                new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open,
-                        R.string.navigation_drawer_close);
-        drawer.addDrawerListener(toggle);
-        toggle.syncState();
-
-        android.widget.TextView ver = drawer.findViewById(R.id.drawer_version_text);
-
-        try {
-            PackageInfo pi =
-                    getApplicationContext().getPackageManager().getPackageInfo(getPackageName(), 0);
-            ver.setText(pi.versionName);
-        }
-        catch (Exception e) {
-            e.printStackTrace();
-        }
-
-        model = ViewModelProviders.of(this).get(AccountSummaryViewModel.class);
-        List<LedgerAccount> accounts = model.getAccounts(getApplicationContext());
-        modelAdapter = new AccountSummaryAdapter(accounts);
-
-        RecyclerView root = findViewById(R.id.account_root);
-        root.setAdapter(modelAdapter);
-
-        LinearLayoutManager llm = new LinearLayoutManager(this);
-        llm.setOrientation(LinearLayoutManager.VERTICAL);
-        root.setLayoutManager(llm);
-
-        root.addOnItemTouchListener(new RecyclerItemListener(this, root,
-                new RecyclerItemListener.RecyclerTouchListener() {
-                    @Override
-                    public void onClickItem(View v, int position) {
-                        Log.d("list", String.format("item %d clicked", position));
-                        if (modelAdapter.isSelectionActive()) {
-                            modelAdapter.selectItem(position);
-                        }
-                    }
-
-                    @Override
-                    public void onLongClickItem(View v, int position) {
-                        Log.d("list", String.format("item %d long-clicked", position));
-                        modelAdapter.startSelection();
-                        if (optMenu != null) {
-                            optMenu.findItem(R.id.menu_acc_summary_cancel_selection)
-                                    .setVisible(true);
-                            optMenu.findItem(R.id.menu_acc_summary_confirm_selection)
-                                    .setVisible(true);
-                            optMenu.findItem(R.id.menu_acc_summary_only_starred).setVisible(false);
-                        }
-                        {
-                            FloatingActionButton fab = findViewById(R.id.btn_add_transaction);
-                            if (fab != null) fab.hide();
-                        }
-                    }
-                }));
-
-        root.addOnScrollListener(new RecyclerView.OnScrollListener() {
-            @Override
-            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
-                if (dy < 0) ((FloatingActionButton) findViewById(R.id.btn_add_transaction)).show();
-                if (dy > 0) ((FloatingActionButton) findViewById(R.id.btn_add_transaction)).hide();
-            }
-        });
-        SwipeRefreshLayout swiper = findViewById(R.id.account_swiper);
-        swiper.setColorSchemeResources(R.color.colorPrimary, R.color.colorAccent);
-        swiper.setOnRefreshListener(() -> {
-            Log.d("ui", "refreshing accounts via swipe");
-            update_accounts(true);
-        });
-        prepare_db();
-//        update_account_table();
-        update_accounts(false);
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        LinearLayout grp = drawer.findViewById(R.id.nav_actions);
-        for (int i = 0; i < grp.getChildCount(); i++) {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-                grp.getChildAt(i).setBackgroundColor(
-                        getResources().getColor(R.color.drawer_background, getTheme()));
-            }
-            else {
-                grp.getChildAt(i)
-                        .setBackgroundColor(getResources().getColor(R.color.drawer_background));
-            }
-        }
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-            drawer.findViewById(R.id.nav_account_summary).setBackgroundColor(
-                    getResources().getColor(R.color.table_row_even_bg, getTheme()));
-        }
-        else {
-            drawer.findViewById(R.id.nav_account_summary)
-                    .setBackgroundColor(getResources().getColor(R.color.table_row_even_bg));
-        }
-    }
-
-    public void fab_new_transaction_clicked(View view) {
-        Intent intent = new Intent(this, NewTransactionActivity.class);
-        startActivity(intent);
-        overridePendingTransition(R.anim.slide_in_right, R.anim.dummy);
-    }
-
-    public void nav_exit_clicked(View view) {
-        Log.w("app", "exiting");
-        finish();
-    }
-
-    public void nav_settings_clicked(View view) {
-        Intent intent = new Intent(this, SettingsActivity.class);
-        startActivity(intent);
-    }
-    public void onLatestTransactionsClicked(View view) {
-        Intent intent = new Intent(this, TransactionListActivity.class);
-        startActivity(intent);
-    }
-    @Override
-    public void onBackPressed() {
-        DrawerLayout drawer = findViewById(R.id.drawer_layout);
-        if (drawer.isDrawerOpen(GravityCompat.START)) {
-            drawer.closeDrawer(GravityCompat.START);
-        }
-        else {
-            super.onBackPressed();
-        }
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        // Inflate the menu; this adds items to the action bar if it is present.
-        getMenuInflater().inflate(R.menu.account_summary, menu);
-        optMenu = menu;
-
-        mShowHiddenAccounts = menu.findItem(R.id.menu_acc_summary_only_starred);
-        if (mShowHiddenAccounts == null) throw new AssertionError();
-
-        sBindPreferenceSummaryToValueListener = (preference, value) -> mShowHiddenAccounts
-                .setChecked(preference.getBoolean(PREF_KEY_SHOW_ONLY_STARRED_ACCOUNTS, false));
-        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
-        pref.registerOnSharedPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
-
-        mShowHiddenAccounts.setChecked(pref.getBoolean(PREF_KEY_SHOW_ONLY_STARRED_ACCOUNTS, false));
-
-        return true;
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        // Handle action bar item clicks here. The action bar will
-        // automatically handle clicks on the Home/Up button, so long
-        // as you specify a parent activity in AndroidManifest.xml.
-//        int id = item.getItemId();
-
-        //noinspection SimplifiableIfStatement
-        //if (id == R.id.action_settings) {
-        //    return true;
-        // }
-
-        return super.onOptionsItemSelected(item);
-    }
-
-    public void onRefreshAccountSummaryClicked(MenuItem mi) {
-        update_accounts(true);
-    }
-
-    public void onShowOnlyStarredClicked(MenuItem mi) {
-        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
-        boolean flag = pref.getBoolean(PREF_KEY_SHOW_ONLY_STARRED_ACCOUNTS, false);
-
-        SharedPreferences.Editor editor = pref.edit();
-        editor.putBoolean(PREF_KEY_SHOW_ONLY_STARRED_ACCOUNTS, !flag);
-        Log.d("pref", "Setting show only starred accounts pref to " + (flag ? "false" : "true"));
-        editor.apply();
-
-        update_account_table();
-    }
-
-    private void prepare_db() {
-        account_list_last_updated = MLDB.get_option_value(this, "last_refresh", (long) 0);
-    }
-
-    private void update_accounts(boolean force) {
-        long now = new Date().getTime();
-        if ((now > (account_list_last_updated + (24 * 3600 * 1000))) || force) {
-            Log.d("db",
-                    "accounts last updated at " + account_list_last_updated + " and now is " + now +
-                    ". re-fetching");
-            update_accounts();
-        }
-    }
-
-    private void update_accounts() {
-        RetrieveAccountsTask task = new RetrieveAccountsTask(new WeakReference<>(this));
-
-        task.setPref(PreferenceManager.getDefaultSharedPreferences(this));
-        task.execute();
-
-    }
-    public void onAccountRefreshDone(int error) {
-        SwipeRefreshLayout srl = findViewById(R.id.account_swiper);
-        srl.setRefreshing(false);
-        if (error != 0) {
-            String err_text = getResources().getString(error);
-            Log.d("visual", String.format("showing snackbar: %s", err_text));
-            Snackbar.make(drawer, err_text, Snackbar.LENGTH_LONG).show();
-        }
-        else {
-            MLDB.set_option_value(this, "last_refresh", new Date().getTime());
-            update_account_table();
-        }
-    }
-    private void update_account_table() {
-        model.reloadAccounts(getApplicationContext());
-        modelAdapter.notifyDataSetChanged();
-    }
-    void stopSelection() {
-        modelAdapter.stopSelection();
-        if (optMenu != null) {
-            optMenu.findItem(R.id.menu_acc_summary_cancel_selection).setVisible(false);
-            optMenu.findItem(R.id.menu_acc_summary_confirm_selection).setVisible(false);
-            optMenu.findItem(R.id.menu_acc_summary_only_starred).setVisible(true);
-        }
-        {
-            FloatingActionButton fab = findViewById(R.id.btn_add_transaction);
-            if (fab != null) fab.show();
-        }
-    }
-    public void onCancelAccSelection(MenuItem item) {
-        stopSelection();
-    }
-    public void onConfirmAccSelection(MenuItem item) {
-        model.commitSelections(getApplicationContext());
-        stopSelection();
-    }
-}
diff --git a/app/src/main/java/net/ktnx/mobileledger/AccountSummaryViewModel.java b/app/src/main/java/net/ktnx/mobileledger/AccountSummaryViewModel.java
deleted file mode 100644 (file)
index f9b5d74..0000000
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright © 2018 Damyan Ivanov.
- * This file is part of Mobile-Ledger.
- * Mobile-Ledger 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.
- *
- * Mobile-Ledger 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 Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
- */
-
-package net.ktnx.mobileledger;
-
-import android.app.Application;
-import android.arch.lifecycle.AndroidViewModel;
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Typeface;
-import android.os.Build;
-import android.preference.PreferenceManager;
-import android.support.annotation.NonNull;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CheckBox;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import net.ktnx.mobileledger.model.LedgerAccount;
-import net.ktnx.mobileledger.utils.MLDB;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static net.ktnx.mobileledger.SettingsActivity.PREF_KEY_SHOW_ONLY_STARRED_ACCOUNTS;
-
-class AccountSummaryViewModel extends AndroidViewModel {
-    private List<LedgerAccount> accounts;
-
-    public AccountSummaryViewModel(@NonNull Application application) {
-        super(application);
-    }
-
-    List<LedgerAccount> getAccounts(Context context) {
-        if (accounts == null) {
-            accounts = new ArrayList<>();
-            reloadAccounts(context);
-        }
-
-        return accounts;
-    }
-
-    void reloadAccounts(Context context) {
-        accounts.clear();
-        boolean showingOnlyStarred =
-                PreferenceManager.getDefaultSharedPreferences(getApplication())
-                        .getBoolean(PREF_KEY_SHOW_ONLY_STARRED_ACCOUNTS, false);
-        String sql = "SELECT name, hidden FROM accounts";
-        if (showingOnlyStarred) sql += " WHERE hidden = 0";
-        sql += " ORDER BY name";
-
-        try (SQLiteDatabase db = MLDB.getReadableDatabase(context)) {
-            try (Cursor cursor = db
-                    .rawQuery(sql,null))
-            {
-                while (cursor.moveToNext()) {
-                    LedgerAccount acc = new LedgerAccount(cursor.getString(0));
-                    acc.setHidden(cursor.getInt(1) == 1);
-                    try (Cursor c2 = db.rawQuery(
-                            "SELECT value, currency FROM account_values " + "WHERE account = ?",
-                            new String[]{acc.getName()}))
-                    {
-                        while (c2.moveToNext()) {
-                            acc.addAmount(c2.getFloat(0), c2.getString(1));
-                        }
-                    }
-                    accounts.add(acc);
-                }
-            }
-        }
-    }
-    void commitSelections(Context context) {
-        try(SQLiteDatabase db = MLDB.getWritableDatabase(context)) {
-            db.beginTransaction();
-            try {
-                for (LedgerAccount acc : accounts) {
-                    Log.d("db", String.format("Setting %s to %s", acc.getName(),
-                            acc.isHidden() ? "hidden" : "starred"));
-                    db.execSQL("UPDATE accounts SET hidden=? WHERE name=?",
-                            new Object[]{acc.isHiddenToBe() ? 1 : 0, acc.getName()});
-                }
-                db.setTransactionSuccessful();
-                for (LedgerAccount acc : accounts ) { acc.setHidden(acc.isHiddenToBe()); }
-            }
-            finally { db.endTransaction(); }
-        }
-    }
-}
-
-class AccountSummaryAdapter extends RecyclerView.Adapter<AccountSummaryAdapter.LedgerRowHolder> {
-    private List<LedgerAccount> accounts;
-    private boolean selectionActive;
-
-    AccountSummaryAdapter(List<LedgerAccount> accounts) {
-        this.accounts = accounts;
-        this.selectionActive = false;
-    }
-
-    public void onBindViewHolder(@NonNull LedgerRowHolder holder, int position) {
-        LedgerAccount acc = accounts.get(position);
-        Context ctx = holder.row.getContext();
-        Resources rm = ctx.getResources();
-
-        holder.tvAccountName.setText(acc.getShortName());
-        holder.tvAccountName.setPadding(
-                acc.getLevel() * rm.getDimensionPixelSize(R.dimen.activity_horizontal_margin)/2,
-                0, 0,
-                0);
-        holder.tvAccountAmounts.setText(acc.getAmountsString());
-
-        if (acc.isHidden()) {
-            holder.tvAccountName.setTypeface(null, Typeface.ITALIC);
-            holder.tvAccountAmounts.setTypeface(null, Typeface.ITALIC);
-        }
-        else {
-            holder.tvAccountName.setTypeface(null, Typeface.NORMAL);
-            holder.tvAccountAmounts.setTypeface(null, Typeface.NORMAL);
-        }
-
-        if (position % 2 == 0) {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) holder.row
-                    .setBackgroundColor(rm.getColor(R.color.table_row_even_bg, ctx.getTheme()));
-            else holder.row.setBackgroundColor(rm.getColor(R.color.table_row_even_bg));
-        }
-        else {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) holder.row
-                    .setBackgroundColor(rm.getColor(R.color.drawer_background, ctx.getTheme()));
-            else holder.row.setBackgroundColor(rm.getColor(R.color.drawer_background));
-        }
-
-        holder.selectionCb.setVisibility( selectionActive ? View.VISIBLE : View.GONE);
-        holder.selectionCb.setChecked(!acc.isHiddenToBe());
-
-        holder.row.setTag(R.id.POS, position);
-    }
-
-    @NonNull
-    @Override
-    public LedgerRowHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
-        View row = LayoutInflater.from(parent.getContext())
-                .inflate(R.layout.account_summary_row, parent, false);
-        return new LedgerRowHolder(row);
-    }
-
-    @Override
-    public int getItemCount() {
-        return accounts.size();
-    }
-    public void startSelection() {
-        for( LedgerAccount acc : accounts ) acc.setHiddenToBe(acc.isHidden());
-        this.selectionActive = true;
-        notifyDataSetChanged();
-    }
-
-    public void stopSelection() {
-        this.selectionActive = false;
-        notifyDataSetChanged();
-    }
-
-    public boolean isSelectionActive() {
-        return selectionActive;
-    }
-
-    public void selectItem(int position) {
-        LedgerAccount acc = accounts.get(position);
-        acc.toggleHiddenToBe();
-        toggleChildrenOf(acc, acc.isHiddenToBe());
-        notifyDataSetChanged();
-    }
-    void toggleChildrenOf(LedgerAccount parent, boolean hiddenToBe) {
-        for (LedgerAccount acc : accounts) {
-            String acc_parent = acc.getParentName();
-            if ((acc_parent != null) && acc.getParentName().equals(parent.getName())) {
-                acc.setHiddenToBe(hiddenToBe);
-                toggleChildrenOf(acc, hiddenToBe);
-            }
-        }
-    }
-    class LedgerRowHolder extends RecyclerView.ViewHolder {
-        CheckBox selectionCb;
-        TextView tvAccountName, tvAccountAmounts;
-        LinearLayout row;
-        public LedgerRowHolder(@NonNull View itemView) {
-            super(itemView);
-            this.row = (LinearLayout) itemView;
-            this.tvAccountName = itemView.findViewById(R.id.account_row_acc_name);
-            this.tvAccountAmounts = itemView.findViewById(R.id.account_row_acc_amounts);
-            this.selectionCb = itemView.findViewById(R.id.account_row_check);
-        }
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/ktnx/mobileledger/AppCompatPreferenceActivity.java b/app/src/main/java/net/ktnx/mobileledger/AppCompatPreferenceActivity.java
deleted file mode 100644 (file)
index 359bbad..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright © 2018 Damyan Ivanov.
- * This file is part of Mobile-Ledger.
- * Mobile-Ledger 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.
- *
- * Mobile-Ledger 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 Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
- */
-
-package net.ktnx.mobileledger;
-
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.preference.PreferenceActivity;
-import android.support.annotation.LayoutRes;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v7.app.ActionBar;
-import android.support.v7.app.AppCompatDelegate;
-import android.support.v7.widget.Toolbar;
-import android.view.MenuInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
- * to be used with AppCompat.
- */
-public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
-
-    private AppCompatDelegate mDelegate;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        getDelegate().installViewFactory();
-        getDelegate().onCreate(savedInstanceState);
-        super.onCreate(savedInstanceState);
-    }
-
-    @Override
-    protected void onPostCreate(Bundle savedInstanceState) {
-        super.onPostCreate(savedInstanceState);
-        getDelegate().onPostCreate(savedInstanceState);
-    }
-
-    public ActionBar getSupportActionBar() {
-        return getDelegate().getSupportActionBar();
-    }
-
-    public void setSupportActionBar(@Nullable Toolbar toolbar) {
-        getDelegate().setSupportActionBar(toolbar);
-    }
-
-    @NonNull
-    @Override
-    public MenuInflater getMenuInflater() {
-        return getDelegate().getMenuInflater();
-    }
-
-    @Override
-    public void setContentView(@LayoutRes int layoutResID) {
-        getDelegate().setContentView(layoutResID);
-    }
-
-    @Override
-    public void setContentView(View view) {
-        getDelegate().setContentView(view);
-    }
-
-    @Override
-    public void setContentView(View view, ViewGroup.LayoutParams params) {
-        getDelegate().setContentView(view, params);
-    }
-
-    @Override
-    public void addContentView(View view, ViewGroup.LayoutParams params) {
-        getDelegate().addContentView(view, params);
-    }
-
-    @Override
-    protected void onPostResume() {
-        super.onPostResume();
-        getDelegate().onPostResume();
-    }
-
-    @Override
-    protected void onTitleChanged(CharSequence title, int color) {
-        super.onTitleChanged(title, color);
-        getDelegate().setTitle(title);
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        getDelegate().onConfigurationChanged(newConfig);
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        getDelegate().onStop();
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        getDelegate().onDestroy();
-    }
-
-    public void invalidateOptionsMenu() {
-        getDelegate().invalidateOptionsMenu();
-    }
-
-    private AppCompatDelegate getDelegate() {
-        if (mDelegate == null) {
-            mDelegate = AppCompatDelegate.create(this, null);
-        }
-        return mDelegate;
-    }
-}
diff --git a/app/src/main/java/net/ktnx/mobileledger/NewTransactionActivity.java b/app/src/main/java/net/ktnx/mobileledger/NewTransactionActivity.java
deleted file mode 100644 (file)
index e7d6ca4..0000000
+++ /dev/null
@@ -1,445 +0,0 @@
-/*
- * Copyright © 2018 Damyan Ivanov.
- * This file is part of Mobile-Ledger.
- * Mobile-Ledger 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.
- *
- * Mobile-Ledger 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 Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
- */
-
-package net.ktnx.mobileledger;
-
-import android.annotation.SuppressLint;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.support.design.widget.BaseTransientBottomBar;
-import android.support.design.widget.Snackbar;
-import android.support.v4.app.DialogFragment;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.Toolbar;
-import android.text.Editable;
-import android.text.InputType;
-import android.text.TextWatcher;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.inputmethod.EditorInfo;
-import android.widget.AutoCompleteTextView;
-import android.widget.EditText;
-import android.widget.ProgressBar;
-import android.widget.TableLayout;
-import android.widget.TableRow;
-import android.widget.TextView;
-
-import net.ktnx.mobileledger.async.SaveTransactionTask;
-import net.ktnx.mobileledger.async.TaskCallback;
-import net.ktnx.mobileledger.model.LedgerTransaction;
-import net.ktnx.mobileledger.model.LedgerTransactionAccount;
-import net.ktnx.mobileledger.ui.DatePickerFragment;
-import net.ktnx.mobileledger.utils.MLDB;
-
-import java.util.Date;
-import java.util.Objects;
-
-/*
- * TODO: nicer progress while transaction is submitted
- * TODO: latest transactions, maybe with browsing further in the past?
- * TODO: reports
- * TODO: get rid of the custom session/cookie and auth code?
- *         (the last problem with the POST was the missing content-length header)
- * TODO: app icon
- * TODO: nicer swiping removal with visual feedback
- * TODO: setup wizard
- * TODO: update accounts/check settings upon change of backend settings
- *  */
-
-public class NewTransactionActivity extends AppCompatActivity implements TaskCallback {
-    private static SaveTransactionTask saver;
-    private TableLayout table;
-    private ProgressBar progress;
-    private TextView text_date;
-    private AutoCompleteTextView text_descr;
-    private MenuItem mSave;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_new_transaction);
-        Toolbar toolbar = findViewById(R.id.toolbar);
-        setSupportActionBar(toolbar);
-
-        text_date = findViewById(R.id.new_transaction_date);
-        text_date.setOnFocusChangeListener(new View.OnFocusChangeListener() {
-            @Override
-            public void onFocusChange(View v, boolean hasFocus) {
-                if (hasFocus) pickTransactionDate(v);
-            }
-        });
-        text_descr = findViewById(R.id.new_transaction_description);
-        MLDB.hook_autocompletion_adapter(this, text_descr, MLDB.DESCRIPTION_HISTORY_TABLE,
-                "description");
-        hook_text_change_listener(text_descr);
-
-        progress = findViewById(R.id.save_transaction_progress);
-
-        Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
-        table = findViewById(R.id.new_transaction_accounts_table);
-        for (int i = 0; i < table.getChildCount(); i++) {
-            TableRow row = (TableRow) table.getChildAt(i);
-            AutoCompleteTextView acc_name_view = (AutoCompleteTextView) row.getChildAt(0);
-            TextView amount_view = (TextView) row.getChildAt(1);
-            hook_swipe_listener(row);
-            MLDB.hook_autocompletion_adapter(this, acc_name_view, MLDB.ACCOUNTS_TABLE, "name");
-            hook_text_change_listener(acc_name_view);
-            hook_text_change_listener(amount_view);
-//            Log.d("swipe", "hooked to row "+i);
-        }
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        if (text_descr.getText().toString().isEmpty()) text_descr.requestFocus();
-    }
-
-    @Override
-    public void finish() {
-        super.finish();
-        overridePendingTransition(R.anim.dummy, R.anim.slide_out_right);
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case android.R.id.home:
-                finish();
-                return true;
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    public void save_transaction() {
-        if (mSave != null) mSave.setVisible(false);
-        toggle_all_editing(false);
-        progress.setVisibility(View.VISIBLE);
-
-        saver = new SaveTransactionTask(this);
-
-        saver.setPref(PreferenceManager.getDefaultSharedPreferences(this));
-        String date = text_date.getText().toString();
-        if (date.isEmpty()) date = String.valueOf(new Date().getDate());
-        LedgerTransaction tr = new LedgerTransaction(date, text_descr.getText().toString());
-
-        TableLayout table = findViewById(R.id.new_transaction_accounts_table);
-        for (int i = 0; i < table.getChildCount(); i++) {
-            TableRow row = (TableRow) table.getChildAt(i);
-            String acc = ((TextView) row.getChildAt(0)).getText().toString();
-            String amt = ((TextView) row.getChildAt(1)).getText().toString();
-            LedgerTransactionAccount item =
-                    amt.length() > 0 ? new LedgerTransactionAccount(acc, Float.parseFloat(amt))
-                                     : new LedgerTransactionAccount(acc);
-
-            tr.addAccount(item);
-        }
-        saver.execute(tr);
-    }
-
-    private void toggle_all_editing(boolean enabled) {
-        text_date.setEnabled(enabled);
-        text_descr.setEnabled(enabled);
-        TableLayout table = findViewById(R.id.new_transaction_accounts_table);
-        for (int i = 0; i < table.getChildCount(); i++) {
-            TableRow row = (TableRow) table.getChildAt(i);
-            for (int j = 0; j < row.getChildCount(); j++) {
-                row.getChildAt(j).setEnabled(enabled);
-            }
-        }
-    }
-
-    private void hook_swipe_listener(final TableRow row) {
-        row.getChildAt(0).setOnTouchListener(new OnSwipeTouchListener(this) {
-            public void onSwipeLeft() {
-//                Log.d("swipe", "LEFT" + row.getId());
-                if (table.getChildCount() > 2) {
-                    TableRow prev_row = (TableRow) table.getChildAt(table.indexOfChild(row) - 1);
-                    TableRow next_row = (TableRow) table.getChildAt(table.indexOfChild(row) + 1);
-                    TextView prev_amt =
-                            (prev_row != null) ? (TextView) prev_row.getChildAt(1) : text_descr;
-                    TextView next_acc =
-                            (next_row != null) ? (TextView) next_row.getChildAt(0) : null;
-
-                    if (next_acc == null) {
-                        prev_amt.setNextFocusRightId(R.id.none);
-                        prev_amt.setNextFocusForwardId(R.id.none);
-                        prev_amt.setImeOptions(EditorInfo.IME_ACTION_DONE);
-                    }
-                    else {
-                        prev_amt.setNextFocusRightId(next_acc.getId());
-                        prev_amt.setNextFocusForwardId(next_acc.getId());
-                        prev_amt.setImeOptions(EditorInfo.IME_ACTION_NEXT);
-                    }
-
-                    if (row.hasFocus()) {
-                        if (next_acc != null) next_acc.requestFocus();
-                        else prev_amt.requestFocus();
-                    }
-
-                    table.removeView(row);
-                    check_transaction_submittable();
-//                    Toast.makeText(NewTransactionActivity.this, "LEFT", Toast.LENGTH_LONG).show();
-                }
-                else {
-                    Snackbar.make(table, R.string.msg_at_least_two_accounts_are_required,
-                            Snackbar.LENGTH_LONG).setAction("Action", null).show();
-                }
-            }
-            //            @Override
-//            public boolean performClick(View view, MotionEvent m) {
-//                return true;
-//            }
-            public boolean onTouch(View view, MotionEvent m) {
-                return gestureDetector.onTouchEvent(m);
-            }
-        });
-    }
-
-    private void hook_text_change_listener(final TextView view) {
-        view.addTextChangedListener(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) {
-//                Log.d("input", "text changed");
-                check_transaction_submittable();
-            }
-        });
-
-    }
-
-    public boolean onCreateOptionsMenu(Menu menu) {
-        // Inflate the menu; this adds items to the action bar if it is present.
-        getMenuInflater().inflate(R.menu.new_transaction, menu);
-        mSave = menu.findItem(R.id.action_submit_transaction);
-        if (mSave == null) throw new AssertionError();
-
-        check_transaction_submittable();
-
-        return true;
-    }
-
-    public void pickTransactionDate(View view) {
-        DialogFragment picker = new DatePickerFragment();
-        picker.show(getSupportFragmentManager(), "datePicker");
-    }
-
-    public int dp2px(float dp) {
-        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
-                getResources().getDisplayMetrics()));
-    }
-
-    private void do_add_account_row(boolean focus) {
-        final AutoCompleteTextView acc = new AutoCompleteTextView(this);
-        acc.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT,
-                TableRow.LayoutParams.WRAP_CONTENT, 9f));
-        acc.setHint(R.string.new_transaction_account_hint);
-        acc.setWidth(0);
-        acc.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_ENTER_ACTION |
-                          EditorInfo.IME_FLAG_NAVIGATE_NEXT);
-        acc.setSingleLine(true);
-
-        final EditText amt = new EditText(this);
-        amt.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT,
-                TableRow.LayoutParams.MATCH_PARENT, 1f));
-        amt.setHint(R.string.new_transaction_amount_hint);
-        amt.setWidth(0);
-        amt.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED |
-                         InputType.TYPE_NUMBER_FLAG_DECIMAL);
-        amt.setMinWidth(dp2px(40));
-        amt.setTextAlignment(EditText.TEXT_ALIGNMENT_VIEW_END);
-        amt.setImeOptions(EditorInfo.IME_ACTION_DONE);
-
-        // forward navigation support
-        final TableRow last_row = (TableRow) table.getChildAt(table.getChildCount() - 1);
-        final TextView last_amt = (TextView) last_row.getChildAt(1);
-        last_amt.setNextFocusForwardId(acc.getId());
-        last_amt.setNextFocusRightId(acc.getId());
-        last_amt.setImeOptions(EditorInfo.IME_ACTION_NEXT);
-        acc.setNextFocusForwardId(amt.getId());
-        acc.setNextFocusRightId(amt.getId());
-
-        final TableRow row = new TableRow(this);
-        row.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT,
-                TableRow.LayoutParams.MATCH_PARENT));
-        row.setGravity(Gravity.BOTTOM);
-        row.addView(acc);
-        row.addView(amt);
-        table.addView(row);
-
-        if (focus) acc.requestFocus();
-
-        hook_swipe_listener(row);
-        MLDB.hook_autocompletion_adapter(this, acc, MLDB.ACCOUNTS_TABLE, "name");
-        hook_text_change_listener(acc);
-        hook_text_change_listener(amt);
-    }
-
-    public void addTransactionAccountFromMenu(MenuItem item) {
-        do_add_account_row(true);
-    }
-
-    public void resetTransactionFromMenu(MenuItem item) {
-        reset_form();
-    }
-
-    public void saveTransactionFromMenu(MenuItem item) {
-        save_transaction();
-    }
-
-    private boolean is_zero(float f) {
-        return (f < 0.005) && (f > -0.005);
-    }
-
-    // rules:
-    // 1) at least two account names
-    // 2) each amount must have account name
-    // 3) amounts must balance to 0, or
-    // 3a) there must be exactly one empty amount
-    // 4) empty accounts with empty amounts are ignored
-    // 5) a row with an empty account name or empty amount is guaranteed to exist
-    @SuppressLint("DefaultLocale")
-    private void check_transaction_submittable() {
-        TableLayout table = findViewById(R.id.new_transaction_accounts_table);
-        int accounts = 0;
-        int accounts_with_values = 0;
-        int amounts = 0;
-        int amounts_with_accounts = 0;
-        int empty_rows = 0;
-        TextView empty_amount = null;
-        boolean single_empty_amount = false;
-        boolean single_empty_amount_has_account = false;
-        float running_total = 0f;
-        boolean have_description =
-                !((TextView) findViewById(R.id.new_transaction_description)).getText().toString()
-                        .isEmpty();
-
-        try {
-            for (int i = 0; i < table.getChildCount(); i++) {
-                TableRow row = (TableRow) table.getChildAt(i);
-
-                TextView acc_name_v = (TextView) row.getChildAt(0);
-                TextView amount_v = (TextView) row.getChildAt(1);
-                String amt = String.valueOf(amount_v.getText());
-                String acc_name = String.valueOf(acc_name_v.getText());
-                acc_name = acc_name.trim();
-
-                if (!acc_name.isEmpty()) {
-                    accounts++;
-
-                    if (!amt.isEmpty()) {
-                        accounts_with_values++;
-                    }
-                }
-                else empty_rows++;
-
-                if (amt.isEmpty()) {
-                    amount_v.setHint(String.format("%1.2f", 0f));
-                    if (empty_amount == null) {
-                        empty_amount = amount_v;
-                        single_empty_amount = true;
-                        single_empty_amount_has_account = !acc_name.isEmpty();
-                    }
-                    else if (!acc_name.isEmpty()) single_empty_amount = false;
-                }
-                else {
-                    amounts++;
-                    if (!acc_name.isEmpty()) amounts_with_accounts++;
-                    running_total += Float.valueOf(amt);
-                }
-            }
-
-            if ((empty_rows == 0) &&
-                ((table.getChildCount() == accounts) || (table.getChildCount() == amounts)))
-            {
-                do_add_account_row(false);
-            }
-
-            Log.d("submittable", String.format("accounts=%d, accounts_with_values=%s, " +
-                                               "amounts_with_accounts=%d, amounts=%d, running_total=%1.2f, " +
-                                               "single_empty_with_acc=%s", accounts,
-                    accounts_with_values, amounts_with_accounts, amounts, running_total,
-                    (single_empty_amount && single_empty_amount_has_account) ? "true" : "false"));
-
-            if (have_description && (accounts >= 2) && (accounts_with_values >= (accounts - 1)) &&
-                (amounts_with_accounts == amounts) &&
-                (single_empty_amount && single_empty_amount_has_account || is_zero(running_total)))
-            {
-                if (mSave != null) mSave.setVisible(true);
-            }
-            else if (mSave != null) mSave.setVisible(false);
-
-            if (single_empty_amount) {
-                empty_amount.setHint(String.format("%1.2f",
-                        (Math.abs(running_total) > 0.005) ? -running_total : 0f));
-            }
-
-        }
-        catch (NumberFormatException e) {
-            if (mSave != null) mSave.setVisible(false);
-        }
-        catch (Exception e) {
-            e.printStackTrace();
-            if (mSave != null) mSave.setVisible(false);
-        }
-    }
-
-    @Override
-    public void done(String error) {
-        progress.setVisibility(View.INVISIBLE);
-        Log.d("visuals", "hiding progress");
-
-        if (error == null) reset_form();
-        else Snackbar.make(findViewById(R.id.new_transaction_accounts_table), error,
-                BaseTransientBottomBar.LENGTH_LONG).show();
-
-        toggle_all_editing(true);
-        check_transaction_submittable();
-    }
-
-    private void reset_form() {
-        text_date.setText("");
-        text_descr.setText("");
-
-        text_descr.requestFocus();
-
-        while (table.getChildCount() > 2) {
-            table.removeViewAt(2);
-        }
-        for (int i = 0; i < 2; i++) {
-            TableRow tr = (TableRow) table.getChildAt(i);
-            if (tr == null) break;
-
-            ((TextView) tr.getChildAt(0)).setText("");
-            ((TextView) tr.getChildAt(1)).setText("");
-        }
-    }
-}
diff --git a/app/src/main/java/net/ktnx/mobileledger/OnSwipeTouchListener.java b/app/src/main/java/net/ktnx/mobileledger/OnSwipeTouchListener.java
deleted file mode 100644 (file)
index 3257b2c..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright © 2018 Damyan Ivanov.
- * This file is part of Mobile-Ledger.
- * Mobile-Ledger 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.
- *
- * Mobile-Ledger 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 Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
- */
-
-package net.ktnx.mobileledger;
-
-import android.content.Context;
-import android.util.Log;
-import android.view.GestureDetector;
-import android.view.GestureDetector.SimpleOnGestureListener;
-import android.view.MotionEvent;
-import android.view.View;
-
-public abstract class OnSwipeTouchListener implements View.OnTouchListener {
-    public final GestureDetector gestureDetector;
-
-    OnSwipeTouchListener(Context ctx) {
-        gestureDetector = new GestureDetector(ctx, new GestureListener() );
-    }
-
-    private final class GestureListener extends SimpleOnGestureListener {
-        private static final int SWIPE_THRESHOLD = 100;
-        private static final int SWIPE_VELOCITY_THRESHOLD = 100;
-
-        @Override
-        public boolean onDown(MotionEvent e) {
-            Log.d("sw-l", "onDown");
-            return false;
-        }
-
-        @Override
-        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-            boolean result = false;
-
-            Log.d("sw-l", "onFling");
-
-            try {
-                float diffX = e2.getX() - e1.getX();
-                float diffY = e2.getY() - e1.getY();
-                if (Math.abs(diffX) > Math.abs(diffY)) {
-                    if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
-                        if (diffX > 0) {
-                            Log.d("sw-l", "calling onSwipeRight");
-                            onSwipeRight();
-                        }
-                        else {
-                            Log.d("sw-l", "calling onSwipeLeft");
-                            onSwipeLeft();
-                        }
-                    }
-                    result = true;
-                }
-                else if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
-                    if (diffY > 0) {
-                        onSwipeDown();
-                    }
-                    else {
-                        onSwipeUp();
-                    }
-                    result = true;
-                }
-            }
-            catch (Exception e) {
-                e.printStackTrace();
-            }
-
-            return result;
-        }
-    }
-
-    public void onSwipeRight() {}
-    public void onSwipeLeft() {
-        Log.d("sw-l", "LEFT");
-    }
-    public void onSwipeUp() {}
-    public void onSwipeDown() {}
-}
diff --git a/app/src/main/java/net/ktnx/mobileledger/RecyclerItemListener.java b/app/src/main/java/net/ktnx/mobileledger/RecyclerItemListener.java
deleted file mode 100644 (file)
index b95f4f7..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright © 2018 Damyan Ivanov.
- * This file is part of Mobile-Ledger.
- * Mobile-Ledger 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.
- *
- * Mobile-Ledger 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 Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
- */
-
-package net.ktnx.mobileledger;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.OnItemTouchListener;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-import android.view.View;
-
-class RecyclerItemListener implements OnItemTouchListener {
-    private RecyclerTouchListener listener;
-    private GestureDetector gd;
-
-    interface RecyclerTouchListener {
-        void onClickItem(View v, int position);
-        void onLongClickItem(View v, int position);
-    }
-
-    public RecyclerItemListener(Context ctx, RecyclerView rv, RecyclerTouchListener listener) {
-        this.listener = listener;
-        this.gd = new GestureDetector(
-                ctx, new GestureDetector.SimpleOnGestureListener() {
-            @Override
-            public void onLongPress(MotionEvent e) {
-                View v = rv.findChildViewUnder(e.getX(), e.getY());
-                listener.onLongClickItem(v, rv.getChildAdapterPosition(v));
-            }
-
-            @Override
-            public boolean onSingleTapUp(MotionEvent e) {
-                View v = rv.findChildViewUnder(e.getX(), e.getY());
-                listener.onClickItem(v, rv.getChildAdapterPosition(v));
-                return true;
-            }
-        }
-        );
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
-                                         @NonNull MotionEvent motionEvent) {
-        View v = recyclerView.findChildViewUnder(motionEvent.getX(), motionEvent.getY());
-        return (v != null) && gd.onTouchEvent(motionEvent);
-    }
-
-    @Override
-    public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
-
-    }
-
-    @Override
-    public void onRequestDisallowInterceptTouchEvent(boolean b) {
-
-    }
-}
diff --git a/app/src/main/java/net/ktnx/mobileledger/SettingsActivity.java b/app/src/main/java/net/ktnx/mobileledger/SettingsActivity.java
deleted file mode 100644 (file)
index 07e0c61..0000000
+++ /dev/null
@@ -1,319 +0,0 @@
-/*
- * Copyright © 2018 Damyan Ivanov.
- * This file is part of Mobile-Ledger.
- * Mobile-Ledger 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.
- *
- * Mobile-Ledger 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 Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
- */
-
-package net.ktnx.mobileledger;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.preference.PreferenceActivity;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceManager;
-import android.preference.RingtonePreference;
-import android.support.v4.app.NavUtils;
-import android.support.v7.app.ActionBar;
-import android.text.TextUtils;
-import android.view.MenuItem;
-
-import java.util.List;
-
-/**
- * A {@link PreferenceActivity} that presents a set of application settings. On
- * handset devices, settings are presented as a single list. On tablets,
- * settings are split by category, with category headers shown to the left of
- * the list of settings.
- * <p>
- * See <a href="http://developer.android.com/design/patterns/settings.html">
- * Android Design: Settings</a> for design guidelines and the <a
- * href="http://developer.android.com/guide/topics/ui/settings.html">Settings
- * API Guide</a> for more information on developing a Settings UI.
- */
-public class SettingsActivity extends AppCompatPreferenceActivity {
-    public static String PREF_KEY_SHOW_ONLY_STARRED_ACCOUNTS = "pref_show_only_starred_accounts";
-
-    /**
-     * A preference value change listener that updates the preference's summary
-     * to reflect its new value.
-     */
-    private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = (preference, value) -> {
-        String stringValue = value.toString();
-
-        AccountSummary.preferences_changed();
-
-        if (preference instanceof ListPreference) {
-            // For list preferences, look up the correct display value in
-            // the preference's 'entries' list.
-            ListPreference listPreference = (ListPreference) preference;
-            int index = listPreference.findIndexOfValue(stringValue);
-
-            // Set the summary to reflect the new value.
-            preference.setSummary(
-                    index >= 0
-                            ? listPreference.getEntries()[index]
-                            : null);
-
-        } else if (preference instanceof RingtonePreference) {
-            // For ringtone preferences, look up the correct display value
-            // using RingtoneManager.
-            if (TextUtils.isEmpty(stringValue)) {
-                // Empty values correspond to 'silent' (no ringtone).
-                preference.setSummary(R.string.pref_ringtone_silent);
-
-            } else {
-                Ringtone ringtone = RingtoneManager.getRingtone(
-                        preference.getContext(), Uri.parse(stringValue));
-
-                if (ringtone == null) {
-                    // Clear the summary if there was a lookup error.
-                    preference.setSummary(null);
-                } else {
-                    // Set the summary to reflect the new ringtone display
-                    // name.
-                    String name = ringtone.getTitle(preference.getContext());
-                    preference.setSummary(name);
-                }
-            }
-        } else {
-            // For all other preferences, set the summary to the value's
-            // simple string representation.
-            preference.setSummary(stringValue);
-        }
-
-        return true;
-    };
-
-    /**
-     * Helper method to determine if the device has an extra-large screen. For
-     * example, 10" tablets are extra-large.
-     */
-    private static boolean isXLargeTablet(Context context) {
-        return (context.getResources().getConfiguration().screenLayout
-                & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
-    }
-
-    /**
-     * Binds a preference's summary to its value. More specifically, when the
-     * preference's value is changed, its summary (line of text below the
-     * preference title) is updated to reflect the value. The summary is also
-     * immediately updated upon calling this method. The exact display format is
-     * dependent on the type of preference.
-     *
-     * @see #sBindPreferenceSummaryToValueListener
-     */
-    private static void bindPreferenceSummaryToValue(Preference preference) {
-        // Set the listener to watch for value changes.
-        preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
-
-        // Trigger the listener immediately with the preference's
-        // current value.
-        sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
-                PreferenceManager
-                        .getDefaultSharedPreferences(preference.getContext())
-                        .getString(preference.getKey(), ""));
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setupActionBar();
-    }
-
-    /**
-     * Set up the {@link android.app.ActionBar}, if the API is available.
-     */
-    private void setupActionBar() {
-        ActionBar actionBar = getSupportActionBar();
-        if (actionBar != null) {
-            // Show the Up button in the action bar.
-            actionBar.setDisplayHomeAsUpEnabled(true);
-        }
-    }
-
-    @Override
-    public boolean onMenuItemSelected(int featureId, MenuItem item) {
-        int id = item.getItemId();
-        if (id == android.R.id.home) {
-            if (!super.onMenuItemSelected(featureId, item)) {
-                NavUtils.navigateUpFromSameTask(this);
-            }
-            return true;
-        }
-        return super.onMenuItemSelected(featureId, item);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public boolean onIsMultiPane() {
-        return isXLargeTablet(this);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
-    public void onBuildHeaders(List<Header> target) {
-        loadHeadersFromResource(R.xml.pref_headers, target);
-    }
-
-    /**
-     * This method stops fragment injection in malicious applications.
-     * Make sure to deny any unknown fragments here.
-     */
-    protected boolean isValidFragment(String fragmentName) {
-        return PreferenceFragment.class.getName().equals(fragmentName)
-                || BackendPreferenceFragment.class.getName().equals(fragmentName)
-                || DataSyncPreferenceFragment.class.getName().equals(fragmentName)
-                || NotificationPreferenceFragment.class.getName().equals(fragmentName)
-                || InterfacePreferenceFragment.class.getName().equals(fragmentName);
-    }
-
-    /**
-     * This fragment shows general preferences only. It is used when the
-     * activity is showing a two-pane settings UI.
-     */
-    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
-    public static class BackendPreferenceFragment extends PreferenceFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            addPreferencesFromResource(R.xml.pref_backend);
-            setHasOptionsMenu(true);
-
-            // Bind the summaries of EditText/List/Dialog/Ringtone preferences
-            // to their values. When their values change, their summaries are
-            // updated to reflect the new value, per the Android Design
-            // guidelines.
-            bindPreferenceSummaryToValue(findPreference("backend_url"));
-            bindPreferenceSummaryToValue(findPreference("backend_auth_user"));
-
-        }
-
-        @Override
-        public boolean onOptionsItemSelected(MenuItem item) {
-            int id = item.getItemId();
-            if (id == android.R.id.home) {
-                startActivity(new Intent(getActivity(), SettingsActivity.class));
-                return true;
-            }
-            return super.onOptionsItemSelected(item);
-        }
-    }
-
-    /**
-     * This fragment shows general preferences only. It is used when the
-     * activity is showing a two-pane settings UI.
-     */
-    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
-    public static
-    class InterfacePreferenceFragment extends PreferenceFragment {
-        @Override
-        public
-        void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            addPreferencesFromResource(R.xml.pref_interface);
-            setHasOptionsMenu(true);
-
-            // Bind the summaries of EditText/List/Dialog/Ringtone preferences
-            // to their values. When their values change, their summaries are
-            // updated to reflect the new value, per the Android Design
-            // guidelines.
-
-        }
-
-        @Override
-        public
-        boolean onOptionsItemSelected(MenuItem item) {
-            int id = item.getItemId();
-            if (id == android.R.id.home) {
-                startActivity(new Intent(getActivity(), SettingsActivity.class));
-                return true;
-            }
-            return super.onOptionsItemSelected(item);
-        }
-    }
-
-    /**
-     * This fragment shows notification preferences only. It is used when the
-     * activity is showing a two-pane settings UI.
-     */
-    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
-    public static class NotificationPreferenceFragment extends PreferenceFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            addPreferencesFromResource(R.xml.pref_notification);
-            setHasOptionsMenu(true);
-
-            // Bind the summaries of EditText/List/Dialog/Ringtone preferences
-            // to their values. When their values change, their summaries are
-            // updated to reflect the new value, per the Android Design
-            // guidelines.
-            bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone"));
-        }
-
-        @Override
-        public boolean onOptionsItemSelected(MenuItem item) {
-            int id = item.getItemId();
-            if (id == android.R.id.home) {
-                startActivity(new Intent(getActivity(), SettingsActivity.class));
-                return true;
-            }
-            return super.onOptionsItemSelected(item);
-        }
-    }
-
-    /**
-     * This fragment shows data and sync preferences only. It is used when the
-     * activity is showing a two-pane settings UI.
-     */
-    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
-    public static class DataSyncPreferenceFragment extends PreferenceFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            addPreferencesFromResource(R.xml.pref_data_sync);
-            setHasOptionsMenu(true);
-
-            // Bind the summaries of EditText/List/Dialog/Ringtone preferences
-            // to their values. When their values change, their summaries are
-            // updated to reflect the new value, per the Android Design
-            // guidelines.
-            bindPreferenceSummaryToValue(findPreference("sync_frequency"));
-        }
-
-        @Override
-        public boolean onOptionsItemSelected(MenuItem item) {
-            int id = item.getItemId();
-            if (id == android.R.id.home) {
-                startActivity(new Intent(getActivity(), SettingsActivity.class));
-                return true;
-            }
-            return super.onOptionsItemSelected(item);
-        }
-    }
-}
diff --git a/app/src/main/java/net/ktnx/mobileledger/TransactionListActivity.java b/app/src/main/java/net/ktnx/mobileledger/TransactionListActivity.java
deleted file mode 100644 (file)
index ac992cd..0000000
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright © 2018 Damyan Ivanov.
- * This file is part of Mobile-Ledger.
- * Mobile-Ledger 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.
- *
- * Mobile-Ledger 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 Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
- */
-
-package net.ktnx.mobileledger;
-
-import android.arch.lifecycle.ViewModelProviders;
-import android.database.MatrixCursor;
-import android.os.Build;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.support.v4.widget.SwipeRefreshLayout;
-import android.support.v7.app.ActionBar;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.Toolbar;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.AdapterView;
-import android.widget.AutoCompleteTextView;
-import android.widget.LinearLayout;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-
-import net.ktnx.mobileledger.async.RetrieveTransactionsTask;
-import net.ktnx.mobileledger.ui.transaction_list.TransactionListViewModel;
-import net.ktnx.mobileledger.utils.MLDB;
-
-import java.lang.ref.WeakReference;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
-import java.util.Date;
-
-public class TransactionListActivity extends AppCompatActivity {
-    public TransactionListViewModel model;
-    private View bTransactionListCancelDownload;
-    private MenuItem menuTransactionListFilter;
-    private View vAccountFilter;
-    private SwipeRefreshLayout swiper;
-    private RecyclerView root;
-    private ProgressBar progressBar;
-    private LinearLayout progressLayout;
-    private TextView tvLastUpdate;
-    private TransactionListAdapter modelAdapter;
-    private RetrieveTransactionsTask retrieveTransactionsTask;
-    private AutoCompleteTextView accNameFilter;
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.transaction_list_activity);
-
-        Toolbar toolbar = findViewById(R.id.toolbar);
-        setSupportActionBar(toolbar);
-
-        setupActionBar();
-
-        swiper = findViewById(R.id.transaction_swipe);
-        if (swiper == null) throw new RuntimeException("Can't get hold on the swipe layout");
-        root = findViewById(R.id.transaction_root);
-        if (root == null) throw new RuntimeException("Can't get hold on the transaction list view");
-        progressBar = findViewById(R.id.transaction_list_progress_bar);
-        if (progressBar == null)
-            throw new RuntimeException("Can't get hold on the transaction list progress bar");
-        progressLayout = findViewById(R.id.transaction_progress_layout);
-        if (progressLayout == null) throw new RuntimeException(
-                "Can't get hold on the transaction list progress bar layout");
-        tvLastUpdate = findViewById(R.id.transactions_last_update);
-        updateLastUpdateText();
-        model = ViewModelProviders.of(this).get(TransactionListViewModel.class);
-        modelAdapter = new TransactionListAdapter(model);
-
-        RecyclerView root = findViewById(R.id.transaction_root);
-        root.setAdapter(modelAdapter);
-
-        LinearLayoutManager llm = new LinearLayoutManager(this);
-
-        llm.setOrientation(LinearLayoutManager.VERTICAL);
-        root.setLayoutManager(llm);
-
-        swiper.setOnRefreshListener(() -> {
-            Log.d("ui", "refreshing transactions via swipe");
-            update_transactions();
-        });
-
-        swiper.setColorSchemeResources(R.color.colorPrimary, R.color.colorAccent);
-
-        vAccountFilter = findViewById(R.id.transaction_list_account_name_filter);
-        accNameFilter = findViewById(R.id.transaction_filter_account_name);
-        bTransactionListCancelDownload = findViewById(R.id.transaction_list_cancel_download);
-
-        MLDB.hook_autocompletion_adapter(this, accNameFilter, "accounts", "name");
-        TransactionListActivity me = this;
-        accNameFilter.setOnItemClickListener(new AdapterView.OnItemClickListener() {
-            @Override
-            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-                Log.d("tmp", "direct onItemClick");
-                model.reloadTransactions(me);
-                MatrixCursor mc = (MatrixCursor) parent.getItemAtPosition(position);
-                modelAdapter.setBoldAccountName(mc.getString(1));
-                modelAdapter.notifyDataSetChanged();
-                me.hideSoftKeyboard();
-            }
-        });
-
-        updateLastUpdateText();
-        long last_update = MLDB.get_option_value(this, MLDB.OPT_TRANSACTION_LIST_STAMP, 0L);
-        Log.d("transactions", String.format("Last update = %d", last_update));
-        if (last_update == 0) {
-            update_transactions();
-        }
-        else {
-            model.reloadTransactions(this);
-        }
-    }
-    private void setupActionBar() {
-        ActionBar actionBar = getSupportActionBar();
-        if (actionBar != null) {
-            // Show the Up button in the action bar.
-            actionBar.setDisplayHomeAsUpEnabled(true);
-        }
-    }
-    @Override
-    public void finish() {
-        super.finish();
-        Log.d("visuals", "finishing");
-        overridePendingTransition(R.anim.dummy, R.anim.slide_out_right);
-    }
-    private void update_transactions() {
-        retrieveTransactionsTask = new RetrieveTransactionsTask(new WeakReference<>(this));
-
-        RetrieveTransactionsTask.Params params = new RetrieveTransactionsTask.Params(
-                PreferenceManager.getDefaultSharedPreferences(this));
-
-        retrieveTransactionsTask.execute(params);
-        bTransactionListCancelDownload.setEnabled(true);
-    }
-
-    public void onRetrieveStart() {
-        progressBar.setIndeterminate(true);
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) progressBar.setProgress(0, false);
-        else progressBar.setProgress(0);
-        progressLayout.setVisibility(View.VISIBLE);
-    }
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        getMenuInflater().inflate(R.menu.transaction_list, menu);
-        menuTransactionListFilter = menu.findItem(R.id.menu_transaction_list_filter);
-        if ((menuTransactionListFilter == null)) throw new AssertionError();
-
-        return true;
-    }
-    public void onRetrieveProgress(RetrieveTransactionsTask.Progress progress) {
-        if ((progress.getTotal() == RetrieveTransactionsTask.Progress.INDETERMINATE) ||
-            (progress.getTotal() == 0))
-        {
-            progressBar.setIndeterminate(true);
-        }
-        else {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-                progressBar.setMin(0);
-            }
-            progressBar.setMax(progress.getTotal());
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-                progressBar.setProgress(progress.getProgress(), true);
-            }
-            else progressBar.setProgress(progress.getProgress());
-            progressBar.setIndeterminate(false);
-        }
-    }
-
-    public void onRetrieveDone(boolean success) {
-        progressLayout.setVisibility(View.GONE);
-        swiper.setRefreshing(false);
-        updateLastUpdateText();
-        if (success) {
-            Log.d("transactions", "calling notifyDataSetChanged()");
-            modelAdapter.notifyDataSetChanged();
-        }
-    }
-    private void updateLastUpdateText() {
-        {
-            long last_update = MLDB.get_option_value(this, MLDB.OPT_TRANSACTION_LIST_STAMP, 0L);
-            Log.d("transactions", String.format("Last update = %d", last_update));
-            if (last_update == 0) {
-                tvLastUpdate.setText(getString(R.string.transaction_last_update_never));
-            }
-            else {
-                Date date = new Date(last_update);
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-                    tvLastUpdate.setText(date.toInstant().atZone(ZoneId.systemDefault())
-                            .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
-                }
-                else {
-                    tvLastUpdate.setText(date.toLocaleString());
-                }
-            }
-        }
-    }
-    public void onClearAccountNameClick(View view) {
-        vAccountFilter.setVisibility(View.GONE);
-        menuTransactionListFilter.setVisible(true);
-        accNameFilter.setText(null);
-        model.reloadTransactions(this);
-        modelAdapter.resetBoldAccountName();
-        modelAdapter.notifyDataSetChanged();
-        hideSoftKeyboard();
-    }
-    private void hideSoftKeyboard() {
-        // hide the keyboard
-        View v = getCurrentFocus();
-        if (v != null) {
-            InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
-            imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
-        }
-    }
-    public void onShowFilterClick(MenuItem menuItem) {
-        vAccountFilter.setVisibility(View.VISIBLE);
-        menuTransactionListFilter.setVisible(false);
-        accNameFilter.requestFocus();
-        InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
-        imm.showSoftInput(accNameFilter, 0);
-    }
-    public void onStopTransactionRefreshClick(View view) {
-        Log.d("interactive", "Cancelling transactions refresh");
-        if (retrieveTransactionsTask != null) retrieveTransactionsTask.cancel(false);
-        bTransactionListCancelDownload.setEnabled(false);
-    }
-}
diff --git a/app/src/main/java/net/ktnx/mobileledger/TransactionListAdapter.java b/app/src/main/java/net/ktnx/mobileledger/TransactionListAdapter.java
deleted file mode 100644 (file)
index 41c3cdd..0000000
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright © 2018 Damyan Ivanov.
- * This file is part of Mobile-Ledger.
- * Mobile-Ledger 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.
- *
- * Mobile-Ledger 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 Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
- */
-
-package net.ktnx.mobileledger;
-
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Typeface;
-import android.support.annotation.NonNull;
-import android.support.constraint.ConstraintLayout;
-import android.support.v7.widget.AppCompatTextView;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import net.ktnx.mobileledger.model.LedgerTransaction;
-import net.ktnx.mobileledger.model.LedgerTransactionAccount;
-import net.ktnx.mobileledger.ui.transaction_list.TransactionListViewModel;
-import net.ktnx.mobileledger.utils.Globals;
-import net.ktnx.mobileledger.utils.MLDB;
-
-import static net.ktnx.mobileledger.utils.DimensionUtils.dp2px;
-
-class TransactionListAdapter
-        extends RecyclerView.Adapter<TransactionListAdapter.TransactionRowHolder> {
-    TransactionListViewModel model;
-    private String boldAccountName;
-    public TransactionListAdapter(TransactionListViewModel model) {
-        this.model = model;
-    }
-    public void onBindViewHolder(@NonNull TransactionRowHolder holder, int position) {
-        LedgerTransaction tr = model.getTransaction(position);
-        // in a race when transaction list is reduced, but the model hasn't been notified yet
-        // the view will disappear when the notifications reaches the model, so by simply omitting
-        // the out-of-range get() call nothing bad happens - just a to-be-deleted view remains
-        // a bit longer
-        if (tr == null) return;
-
-        Context ctx = holder.row.getContext();
-
-        try (SQLiteDatabase db = MLDB.getReadableDatabase(ctx)) {
-            tr.loadData(db);
-            holder.tvDescription.setText(tr.getDescription());
-            holder.tvDate.setText(tr.getDate());
-
-            int rowIndex = 0;
-            for (LedgerTransactionAccount acc : tr.getAccounts()) {
-                LinearLayout row = (LinearLayout) holder.tableAccounts.getChildAt(rowIndex++);
-                TextView accName, accAmount;
-                if (row == null) {
-                    row = new LinearLayout(ctx);
-                    row.setLayoutParams(
-                            new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
-                                    LinearLayout.LayoutParams.WRAP_CONTENT));
-                    row.setGravity(Gravity.CENTER_VERTICAL);
-                    row.setOrientation(LinearLayout.HORIZONTAL);
-                    row.setPaddingRelative(dp2px(ctx, 8), 0, 0, 0);
-                    accName = new AppCompatTextView(ctx);
-                    accName.setLayoutParams(
-                            new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT,
-                                    5f));
-                    accName.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
-                    row.addView(accName);
-                    accAmount = new AppCompatTextView(ctx);
-                    LinearLayout.LayoutParams llp =
-                            new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
-                                    LinearLayout.LayoutParams.WRAP_CONTENT);
-                    llp.setMarginEnd(0);
-                    accAmount.setLayoutParams(llp);
-                    accAmount.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
-                    accAmount.setMinWidth(dp2px(ctx, 60));
-                    row.addView(accAmount);
-                    holder.tableAccounts.addView(row);
-                }
-                else {
-                    accName = (TextView) row.getChildAt(0);
-                    accAmount = (TextView) row.getChildAt(1);
-                }
-                accName.setText(acc.getAccountName());
-                accAmount.setText(acc.toString());
-
-                if ((boldAccountName != null) && boldAccountName.equals(acc.getAccountName())) {
-                    accName.setTypeface(null, Typeface.BOLD);
-                    accAmount.setTypeface(null, Typeface.BOLD);
-                    accName.setTextColor(Globals.primaryDark);
-                    accAmount.setTextColor(Globals.primaryDark);
-                }
-                else {
-                    accName.setTypeface(null, Typeface.NORMAL);
-                    accAmount.setTypeface(null, Typeface.NORMAL);
-                    accName.setTextColor(Globals.defaultTextColor);
-                    accAmount.setTextColor(Globals.defaultTextColor);
-                }
-
-            }
-            if (holder.tableAccounts.getChildCount() > rowIndex) {
-                holder.tableAccounts
-                        .removeViews(rowIndex, holder.tableAccounts.getChildCount() - rowIndex);
-            }
-
-            if (position % 2 == 0) {
-                holder.row.setBackgroundColor(Globals.table_row_even_bg);
-            }
-            else {
-                holder.row.setBackgroundColor(Globals.table_row_odd_bg);
-            }
-
-            Log.d("transactions", String.format("Filled position %d", position));
-        }
-    }
-
-    @NonNull
-    @Override
-    public TransactionRowHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
-        Log.d("perf", "onCreateViewHolder called");
-        View row = LayoutInflater.from(parent.getContext())
-                .inflate(R.layout.transaction_list_row, parent, false);
-        return new TransactionRowHolder(row);
-    }
-
-    @Override
-    public int getItemCount() {
-        return model.getTransactionCount();
-    }
-    public void setBoldAccountName(String boldAccountName) {
-        this.boldAccountName = boldAccountName;
-    }
-    public void resetBoldAccountName() {
-        this.boldAccountName = null;
-    }
-    class TransactionRowHolder extends RecyclerView.ViewHolder {
-        TextView tvDescription, tvDate;
-        LinearLayout tableAccounts;
-        ConstraintLayout row;
-        public TransactionRowHolder(@NonNull View itemView) {
-            super(itemView);
-            this.row = itemView.findViewById(R.id.transaction_row);
-            this.tvDescription = itemView.findViewById(R.id.transaction_row_description);
-            this.tvDate = itemView.findViewById(R.id.transaction_row_date);
-            this.tableAccounts = itemView.findViewById(R.id.transaction_row_acc_amounts);
-        }
-    }
-}
\ No newline at end of file
index a342ca4..59362fa 100644 (file)
@@ -21,7 +21,7 @@ import android.content.SharedPreferences;
 import android.database.sqlite.SQLiteDatabase;
 import android.util.Log;
 
-import net.ktnx.mobileledger.AccountSummary;
+import net.ktnx.mobileledger.ui.account_summary.AccountSummaryFragment;
 import net.ktnx.mobileledger.R;
 import net.ktnx.mobileledger.model.LedgerAccount;
 import net.ktnx.mobileledger.utils.MLDB;
@@ -41,10 +41,10 @@ import java.util.regex.Pattern;
 
 public class RetrieveAccountsTask extends android.os.AsyncTask<Void, Integer, Void> {
     int error;
-    WeakReference<AccountSummary> mContext;
+    WeakReference<AccountSummaryFragment> mContext;
     private SharedPreferences pref;
 
-    public RetrieveAccountsTask(WeakReference<AccountSummary> context) {
+    public RetrieveAccountsTask(WeakReference<AccountSummaryFragment> context) {
         mContext = context;
         error = 0;
     }
@@ -57,7 +57,7 @@ public class RetrieveAccountsTask extends android.os.AsyncTask<Void, Integer, Vo
         try {
             HttpURLConnection http = NetworkUtil.prepare_connection(pref, "add");
             publishProgress(0);
-            try (SQLiteDatabase db = MLDB.getWritableDatabase(mContext.get())) {
+            try (SQLiteDatabase db = MLDB.getWritableDatabase(mContext.get().getActivity())) {
                 try (InputStream resp = http.getInputStream()) {
                     Log.d("update_accounts", String.valueOf(http.getResponseCode()));
                     if (http.getResponseCode() != 200) {
@@ -197,7 +197,7 @@ public class RetrieveAccountsTask extends android.os.AsyncTask<Void, Integer, Vo
     }
     @Override
     protected void onPostExecute(Void result) {
-        AccountSummary ctx = mContext.get();
+        AccountSummaryFragment ctx = mContext.get();
         if (ctx == null) return;
         ctx.onAccountRefreshDone(this.error);
     }
index 701ec50..b8a3413 100644 (file)
@@ -24,9 +24,9 @@ import android.os.AsyncTask;
 import android.util.Log;
 
 import net.ktnx.mobileledger.R;
-import net.ktnx.mobileledger.TransactionListActivity;
 import net.ktnx.mobileledger.model.LedgerTransaction;
 import net.ktnx.mobileledger.model.LedgerTransactionAccount;
+import net.ktnx.mobileledger.ui.transaction_list.TransactionListFragment;
 import net.ktnx.mobileledger.utils.MLDB;
 import net.ktnx.mobileledger.utils.NetworkUtil;
 
@@ -52,10 +52,10 @@ public class RetrieveTransactionsTask extends
     private static final Pattern transactionDetailsPattern =
             Pattern.compile("^\\s+" + "(\\S[\\S\\s]+\\S)\\s\\s+([-+]?\\d[\\d,.]*)(?:\\s+(\\S+)$)?");
     private static final Pattern endPattern = Pattern.compile("\\bid=\"addmodal\"");
-    protected WeakReference<TransactionListActivity> contextRef;
+    protected WeakReference<TransactionListFragment> contextRef;
     protected int error;
     private boolean success;
-    public RetrieveTransactionsTask(WeakReference<TransactionListActivity> contextRef) {
+    public RetrieveTransactionsTask(WeakReference<TransactionListFragment> contextRef) {
         this.contextRef = contextRef;
     }
     private static final void L(String msg) {
@@ -64,28 +64,28 @@ public class RetrieveTransactionsTask extends
     @Override
     protected void onProgressUpdate(Progress... values) {
         super.onProgressUpdate(values);
-        TransactionListActivity context = getContext();
+        TransactionListFragment context = getContext();
         if (context == null) return;
         context.onRetrieveProgress(values[0]);
     }
     @Override
     protected void onPreExecute() {
         super.onPreExecute();
-        TransactionListActivity context = getContext();
+        TransactionListFragment context = getContext();
         if (context == null) return;
         context.onRetrieveStart();
     }
     @Override
     protected void onPostExecute(Void aVoid) {
         super.onPostExecute(aVoid);
-        TransactionListActivity context = getContext();
+        TransactionListFragment context = getContext();
         if (context == null) return;
         context.onRetrieveDone(success);
     }
     @Override
     protected void onCancelled() {
         super.onCancelled();
-        TransactionListActivity context = getContext();
+        TransactionListFragment context = getContext();
         if (context == null) return;
         context.onRetrieveDone(false);
     }
@@ -100,9 +100,9 @@ public class RetrieveTransactionsTask extends
                     NetworkUtil.prepare_connection(params[0].getBackendPref(), "journal");
             http.setAllowUserInteraction(false);
             publishProgress(progress);
-            TransactionListActivity ctx = contextRef.get();
+            TransactionListFragment ctx = getContext();
             if (ctx == null) return null;
-            try (SQLiteDatabase db = MLDB.getWritableDatabase(ctx)) {
+            try (SQLiteDatabase db = MLDB.getWritableDatabase(ctx.getActivity())) {
                 try (InputStream resp = http.getInputStream()) {
                     if (http.getResponseCode() != 200) throw new IOException(
                             String.format("HTTP error %d", http.getResponseCode()));
@@ -251,7 +251,8 @@ public class RetrieveTransactionsTask extends
 
             if (success && !isCancelled()) {
                 Log.d("db", "Updating transaction list stamp");
-                MLDB.set_option_value(ctx, MLDB.OPT_TRANSACTION_LIST_STAMP, new Date().getTime());
+                MLDB.set_option_value(ctx.getActivity(), MLDB.OPT_TRANSACTION_LIST_STAMP,
+                        new Date().getTime());
                 ctx.model.reloadTransactions(ctx);
             }
         }
@@ -269,7 +270,7 @@ public class RetrieveTransactionsTask extends
         }
         return null;
     }
-    TransactionListActivity getContext() {
+    TransactionListFragment getContext() {
         return contextRef.get();
     }
 
diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/OnSwipeTouchListener.java b/app/src/main/java/net/ktnx/mobileledger/ui/OnSwipeTouchListener.java
new file mode 100644 (file)
index 0000000..ecc0095
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright © 2018 Damyan Ivanov.
+ * This file is part of Mobile-Ledger.
+ * Mobile-Ledger 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.
+ *
+ * Mobile-Ledger 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 Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
+import android.view.View;
+
+public abstract class OnSwipeTouchListener implements View.OnTouchListener {
+    public final GestureDetector gestureDetector;
+
+    protected OnSwipeTouchListener(Context ctx) {
+        gestureDetector = new GestureDetector(ctx, new GestureListener() );
+    }
+
+    private final class GestureListener extends SimpleOnGestureListener {
+        private static final int SWIPE_THRESHOLD = 100;
+        private static final int SWIPE_VELOCITY_THRESHOLD = 100;
+
+        @Override
+        public boolean onDown(MotionEvent e) {
+            Log.d("sw-l", "onDown");
+            return false;
+        }
+
+        @Override
+        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+            boolean result = false;
+
+            Log.d("sw-l", "onFling");
+
+            try {
+                float diffX = e2.getX() - e1.getX();
+                float diffY = e2.getY() - e1.getY();
+                if (Math.abs(diffX) > Math.abs(diffY)) {
+                    if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
+                        if (diffX > 0) {
+                            Log.d("sw-l", "calling onSwipeRight");
+                            onSwipeRight();
+                        }
+                        else {
+                            Log.d("sw-l", "calling onSwipeLeft");
+                            onSwipeLeft();
+                        }
+                    }
+                    result = true;
+                }
+                else if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
+                    if (diffY > 0) {
+                        onSwipeDown();
+                    }
+                    else {
+                        onSwipeUp();
+                    }
+                    result = true;
+                }
+            }
+            catch (Exception e) {
+                e.printStackTrace();
+            }
+
+            return result;
+        }
+    }
+
+    public void onSwipeRight() {}
+    public void onSwipeLeft() {
+        Log.d("sw-l", "LEFT");
+    }
+    public void onSwipeUp() {}
+    public void onSwipeDown() {}
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/RecyclerItemListener.java b/app/src/main/java/net/ktnx/mobileledger/ui/RecyclerItemListener.java
new file mode 100644 (file)
index 0000000..64427c2
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright © 2018 Damyan Ivanov.
+ * This file is part of Mobile-Ledger.
+ * Mobile-Ledger 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.
+ *
+ * Mobile-Ledger 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 Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnItemTouchListener;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+
+public class RecyclerItemListener implements OnItemTouchListener {
+    private RecyclerTouchListener listener;
+    private GestureDetector gd;
+
+    public interface RecyclerTouchListener {
+        void onClickItem(View v, int position);
+        void onLongClickItem(View v, int position);
+    }
+
+    public RecyclerItemListener(Context ctx, RecyclerView rv, RecyclerTouchListener listener) {
+        this.listener = listener;
+        this.gd = new GestureDetector(
+                ctx, new GestureDetector.SimpleOnGestureListener() {
+            @Override
+            public void onLongPress(MotionEvent e) {
+                View v = rv.findChildViewUnder(e.getX(), e.getY());
+                listener.onLongClickItem(v, rv.getChildAdapterPosition(v));
+            }
+
+            @Override
+            public boolean onSingleTapUp(MotionEvent e) {
+                View v = rv.findChildViewUnder(e.getX(), e.getY());
+                listener.onClickItem(v, rv.getChildAdapterPosition(v));
+                return true;
+            }
+        }
+        );
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
+                                         @NonNull MotionEvent motionEvent) {
+        View v = recyclerView.findChildViewUnder(motionEvent.getX(), motionEvent.getY());
+        return (v != null) && gd.onTouchEvent(motionEvent);
+    }
+
+    @Override
+    public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
+
+    }
+
+    @Override
+    public void onRequestDisallowInterceptTouchEvent(boolean b) {
+
+    }
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java
new file mode 100644 (file)
index 0000000..b0b6732
--- /dev/null
@@ -0,0 +1,236 @@
+/*
+ * Copyright © 2018 Damyan Ivanov.
+ * This file is part of Mobile-Ledger.
+ * Mobile-Ledger 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.
+ *
+ * Mobile-Ledger 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 Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui.account_summary;
+
+import android.arch.lifecycle.ViewModelProviders;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.Snackbar;
+import android.support.v4.app.Fragment;
+import android.support.v4.widget.SwipeRefreshLayout;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.ui.RecyclerItemListener;
+import net.ktnx.mobileledger.async.RetrieveAccountsTask;
+import net.ktnx.mobileledger.model.LedgerAccount;
+import net.ktnx.mobileledger.ui.activity.MainActivity;
+import net.ktnx.mobileledger.utils.MLDB;
+
+import java.lang.ref.WeakReference;
+import java.util.Date;
+import java.util.List;
+
+import static net.ktnx.mobileledger.ui.activity.SettingsActivity.PREF_KEY_SHOW_ONLY_STARRED_ACCOUNTS;
+
+public class AccountSummaryFragment extends Fragment {
+
+    private static long account_list_last_updated;
+    private static boolean account_list_needs_update = true;
+    MenuItem mShowHiddenAccounts;
+    SharedPreferences.OnSharedPreferenceChangeListener sBindPreferenceSummaryToValueListener;
+    private AccountSummaryViewModel model;
+    private AccountSummaryAdapter modelAdapter;
+    private Menu optMenu;
+    private MainActivity mActivity;
+    private FloatingActionButton fab;
+    private SwipeRefreshLayout swiper;
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setHasOptionsMenu(true);
+    }
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mActivity = (MainActivity) context;
+    }
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+                             @Nullable Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.account_summary_fragment, container, false);
+    }
+
+    @Override
+    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        model = ViewModelProviders.of(this).get(AccountSummaryViewModel.class);
+        List<LedgerAccount> accounts = model.getAccounts(this.getContext());
+        modelAdapter = new AccountSummaryAdapter(accounts);
+
+        RecyclerView root = mActivity.findViewById(R.id.account_root);
+        root.setAdapter(modelAdapter);
+
+        LinearLayoutManager llm = new LinearLayoutManager(mActivity);
+        llm.setOrientation(LinearLayoutManager.VERTICAL);
+        root.setLayoutManager(llm);
+
+        fab = mActivity.findViewById(R.id.btn_add_transaction);
+
+        root.addOnItemTouchListener(new RecyclerItemListener(mActivity, root,
+                new RecyclerItemListener.RecyclerTouchListener() {
+                    @Override
+                    public void onClickItem(View v, int position) {
+                        Log.d("list", String.format("item %d clicked", position));
+                        if (modelAdapter.isSelectionActive()) {
+                            modelAdapter.selectItem(position);
+                        }
+                    }
+
+                    @Override
+                    public void onLongClickItem(View v, int position) {
+                        Log.d("list", String.format("item %d long-clicked", position));
+                        modelAdapter.startSelection();
+                        if (optMenu != null) {
+                            optMenu.findItem(R.id.menu_acc_summary_cancel_selection)
+                                    .setVisible(true);
+                            optMenu.findItem(R.id.menu_acc_summary_confirm_selection)
+                                    .setVisible(true);
+                            optMenu.findItem(R.id.menu_acc_summary_only_starred).setVisible(false);
+                        }
+                        {
+                            if (fab != null) fab.hide();
+                        }
+                    }
+                }));
+
+        root.addOnScrollListener(new RecyclerView.OnScrollListener() {
+            @Override
+            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+                if (fab != null) {
+                    if (dy < 0) fab.show();
+                    if (dy > 0) fab.hide();
+                }
+            }
+        });
+        swiper = mActivity.findViewById(R.id.account_swiper);
+        swiper.setColorSchemeResources(R.color.colorPrimary, R.color.colorAccent);
+        swiper.setOnRefreshListener(() -> {
+            Log.d("ui", "refreshing accounts via swipe");
+            update_accounts(true);
+        });
+        prepare_db();
+//        update_account_table();
+        update_accounts(false);
+
+    }
+    private void prepare_db() {
+        account_list_last_updated = MLDB.get_option_value(mActivity, "last_refresh", (long) 0);
+    }
+
+    private void update_accounts(boolean force) {
+        long now = new Date().getTime();
+        if ((now > (account_list_last_updated + (24 * 3600 * 1000))) || force) {
+            Log.d("db",
+                    "accounts last updated at " + account_list_last_updated + " and now is " + now +
+                    ". re-fetching");
+            update_accounts();
+        }
+    }
+
+    private void update_accounts() {
+        RetrieveAccountsTask task = new RetrieveAccountsTask(new WeakReference<>(this));
+
+        task.setPref(PreferenceManager.getDefaultSharedPreferences(mActivity));
+        task.execute();
+
+    }
+    public void onAccountRefreshDone(int error) {
+        swiper.setRefreshing(false);
+        if (error != 0) {
+            String err_text = getResources().getString(error);
+            Log.d("visual", String.format("showing snackbar: %s", err_text));
+            Snackbar.make(swiper, err_text, Snackbar.LENGTH_LONG).show();
+        }
+        else {
+            MLDB.set_option_value(mActivity, "last_refresh", new Date().getTime());
+            update_account_table();
+        }
+    }
+    private void update_account_table() {
+        if (this.getContext() == null) return;
+
+        model.reloadAccounts(this.getContext());
+        modelAdapter.notifyDataSetChanged();
+    }
+    public void onRefreshAccountSummaryClicked(MenuItem mi) {
+        update_accounts(true);
+    }
+
+    public void onShowOnlyStarredClicked(MenuItem mi) {
+        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(mActivity);
+        boolean flag = pref.getBoolean(PREF_KEY_SHOW_ONLY_STARRED_ACCOUNTS, false);
+
+        SharedPreferences.Editor editor = pref.edit();
+        editor.putBoolean(PREF_KEY_SHOW_ONLY_STARRED_ACCOUNTS, !flag);
+        Log.d("pref", "Setting show only starred accounts pref to " + (flag ? "false" : "true"));
+        editor.apply();
+
+        update_account_table();
+    }
+
+    void stopSelection() {
+        modelAdapter.stopSelection();
+        if (optMenu != null) {
+            optMenu.findItem(R.id.menu_acc_summary_cancel_selection).setVisible(false);
+            optMenu.findItem(R.id.menu_acc_summary_confirm_selection).setVisible(false);
+            optMenu.findItem(R.id.menu_acc_summary_only_starred).setVisible(true);
+        }
+        {
+            if (fab != null) fab.show();
+        }
+    }
+    public void onCancelAccSelection(MenuItem item) {
+        stopSelection();
+    }
+    public void onConfirmAccSelection(MenuItem item) {
+        model.commitSelections(mActivity);
+        stopSelection();
+    }
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        // Inflate the menu; this adds items to the action bar if it is present.
+        inflater.inflate(R.menu.account_summary, menu);
+        optMenu = menu;
+
+        mShowHiddenAccounts = menu.findItem(R.id.menu_acc_summary_only_starred);
+        if (mShowHiddenAccounts == null) throw new AssertionError();
+
+        sBindPreferenceSummaryToValueListener = (preference, value) -> mShowHiddenAccounts
+                .setChecked(preference.getBoolean(PREF_KEY_SHOW_ONLY_STARRED_ACCOUNTS, false));
+        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(mActivity);
+        pref.registerOnSharedPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
+
+        mShowHiddenAccounts.setChecked(pref.getBoolean(PREF_KEY_SHOW_ONLY_STARRED_ACCOUNTS, false));
+
+        Log.d("menu", "MainActivity: onCreateOptionsMenu called");
+    }
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryViewModel.java b/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryViewModel.java
new file mode 100644 (file)
index 0000000..942a568
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * Copyright © 2018 Damyan Ivanov.
+ * This file is part of Mobile-Ledger.
+ * Mobile-Ledger 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.
+ *
+ * Mobile-Ledger 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 Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui.account_summary;
+
+import android.app.Application;
+import android.arch.lifecycle.AndroidViewModel;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.graphics.Typeface;
+import android.os.Build;
+import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.model.LedgerAccount;
+import net.ktnx.mobileledger.utils.MLDB;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static net.ktnx.mobileledger.ui.activity.SettingsActivity.PREF_KEY_SHOW_ONLY_STARRED_ACCOUNTS;
+
+class AccountSummaryViewModel extends AndroidViewModel {
+    private List<LedgerAccount> accounts;
+
+    public AccountSummaryViewModel(@NonNull Application application) {
+        super(application);
+    }
+
+    List<LedgerAccount> getAccounts(Context context) {
+        if (accounts == null) {
+            accounts = new ArrayList<>();
+            reloadAccounts(context);
+        }
+
+        return accounts;
+    }
+
+    void reloadAccounts(Context context) {
+        accounts.clear();
+        boolean showingOnlyStarred =
+                PreferenceManager.getDefaultSharedPreferences(getApplication())
+                        .getBoolean(PREF_KEY_SHOW_ONLY_STARRED_ACCOUNTS, false);
+        String sql = "SELECT name, hidden FROM accounts";
+        if (showingOnlyStarred) sql += " WHERE hidden = 0";
+        sql += " ORDER BY name";
+
+        try (SQLiteDatabase db = MLDB.getReadableDatabase(context)) {
+            try (Cursor cursor = db
+                    .rawQuery(sql,null))
+            {
+                while (cursor.moveToNext()) {
+                    LedgerAccount acc = new LedgerAccount(cursor.getString(0));
+                    acc.setHidden(cursor.getInt(1) == 1);
+                    try (Cursor c2 = db.rawQuery(
+                            "SELECT value, currency FROM account_values " + "WHERE account = ?",
+                            new String[]{acc.getName()}))
+                    {
+                        while (c2.moveToNext()) {
+                            acc.addAmount(c2.getFloat(0), c2.getString(1));
+                        }
+                    }
+                    accounts.add(acc);
+                }
+            }
+        }
+    }
+    void commitSelections(Context context) {
+        try(SQLiteDatabase db = MLDB.getWritableDatabase(context)) {
+            db.beginTransaction();
+            try {
+                for (LedgerAccount acc : accounts) {
+                    Log.d("db", String.format("Setting %s to %s", acc.getName(),
+                            acc.isHidden() ? "hidden" : "starred"));
+                    db.execSQL("UPDATE accounts SET hidden=? WHERE name=?",
+                            new Object[]{acc.isHiddenToBe() ? 1 : 0, acc.getName()});
+                }
+                db.setTransactionSuccessful();
+                for (LedgerAccount acc : accounts ) { acc.setHidden(acc.isHiddenToBe()); }
+            }
+            finally { db.endTransaction(); }
+        }
+    }
+}
+
+class AccountSummaryAdapter extends RecyclerView.Adapter<AccountSummaryAdapter
+.LedgerRowHolder> {
+    private List<LedgerAccount> accounts;
+    private boolean selectionActive;
+
+    AccountSummaryAdapter(List<LedgerAccount> accounts) {
+        this.accounts = accounts;
+        this.selectionActive = false;
+    }
+
+    public void onBindViewHolder(@NonNull LedgerRowHolder holder, int position) {
+        LedgerAccount acc = accounts.get(position);
+        Context ctx = holder.row.getContext();
+        Resources rm = ctx.getResources();
+
+        holder.tvAccountName.setText(acc.getShortName());
+        holder.tvAccountName.setPadding(
+                acc.getLevel() * rm.getDimensionPixelSize(R.dimen.activity_horizontal_margin) / 2,
+                0, 0,
+                0);
+        holder.tvAccountAmounts.setText(acc.getAmountsString());
+
+        if (acc.isHidden()) {
+            holder.tvAccountName.setTypeface(null, Typeface.ITALIC);
+            holder.tvAccountAmounts.setTypeface(null, Typeface.ITALIC);
+        }
+        else {
+            holder.tvAccountName.setTypeface(null, Typeface.NORMAL);
+            holder.tvAccountAmounts.setTypeface(null, Typeface.NORMAL);
+        }
+
+        if (position % 2 == 0) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) holder.row
+                    .setBackgroundColor(rm.getColor(R.color.table_row_even_bg, ctx.getTheme()));
+            else holder.row.setBackgroundColor(rm.getColor(R.color.table_row_even_bg));
+        }
+        else {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) holder.row
+                    .setBackgroundColor(rm.getColor(R.color.drawer_background, ctx.getTheme()));
+            else holder.row.setBackgroundColor(rm.getColor(R.color.drawer_background));
+        }
+
+        holder.selectionCb.setVisibility( selectionActive ? View.VISIBLE : View.GONE);
+        holder.selectionCb.setChecked(!acc.isHiddenToBe());
+
+        holder.row.setTag(R.id.POS, position);
+    }
+
+    @NonNull
+    @Override
+    public LedgerRowHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        View row = LayoutInflater.from(parent.getContext())
+                .inflate(R.layout.account_summary_row, parent, false);
+        return new LedgerRowHolder(row);
+    }
+
+    @Override
+    public int getItemCount() {
+        return accounts.size();
+    }
+    public void startSelection() {
+        for( LedgerAccount acc : accounts ) acc.setHiddenToBe(acc.isHidden());
+        this.selectionActive = true;
+        notifyDataSetChanged();
+    }
+
+    public void stopSelection() {
+        this.selectionActive = false;
+        notifyDataSetChanged();
+    }
+
+    public boolean isSelectionActive() {
+        return selectionActive;
+    }
+
+    public void selectItem(int position) {
+        LedgerAccount acc = accounts.get(position);
+        acc.toggleHiddenToBe();
+        toggleChildrenOf(acc, acc.isHiddenToBe());
+        notifyDataSetChanged();
+    }
+    void toggleChildrenOf(LedgerAccount parent, boolean hiddenToBe) {
+        for (LedgerAccount acc : accounts) {
+            String acc_parent = acc.getParentName();
+            if ((acc_parent != null) && acc.getParentName().equals(parent.getName())) {
+                acc.setHiddenToBe(hiddenToBe);
+                toggleChildrenOf(acc, hiddenToBe);
+            }
+        }
+    }
+    class LedgerRowHolder extends RecyclerView.ViewHolder {
+        CheckBox selectionCb;
+        TextView tvAccountName, tvAccountAmounts;
+        LinearLayout row;
+        public LedgerRowHolder(@NonNull View itemView) {
+            super(itemView);
+            this.row = (LinearLayout) itemView;
+            this.tvAccountName = itemView.findViewById(R.id.account_row_acc_name);
+            this.tvAccountAmounts = itemView.findViewById(R.id.account_row_acc_amounts);
+            this.selectionCb = itemView.findViewById(R.id.account_row_check);
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/AppCompatPreferenceActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/AppCompatPreferenceActivity.java
new file mode 100644 (file)
index 0000000..6aeb863
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * Copyright © 2018 Damyan Ivanov.
+ * This file is part of Mobile-Ledger.
+ * Mobile-Ledger 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.
+ *
+ * Mobile-Ledger 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 Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui.activity;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatDelegate;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
+ * to be used with AppCompat.
+ */
+public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
+
+    private AppCompatDelegate mDelegate;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        getDelegate().installViewFactory();
+        getDelegate().onCreate(savedInstanceState);
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void onPostCreate(Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+        getDelegate().onPostCreate(savedInstanceState);
+    }
+
+    public ActionBar getSupportActionBar() {
+        return getDelegate().getSupportActionBar();
+    }
+
+    public void setSupportActionBar(@Nullable Toolbar toolbar) {
+        getDelegate().setSupportActionBar(toolbar);
+    }
+
+    @NonNull
+    @Override
+    public MenuInflater getMenuInflater() {
+        return getDelegate().getMenuInflater();
+    }
+
+    @Override
+    public void setContentView(@LayoutRes int layoutResID) {
+        getDelegate().setContentView(layoutResID);
+    }
+
+    @Override
+    public void setContentView(View view) {
+        getDelegate().setContentView(view);
+    }
+
+    @Override
+    public void setContentView(View view, ViewGroup.LayoutParams params) {
+        getDelegate().setContentView(view, params);
+    }
+
+    @Override
+    public void addContentView(View view, ViewGroup.LayoutParams params) {
+        getDelegate().addContentView(view, params);
+    }
+
+    @Override
+    protected void onPostResume() {
+        super.onPostResume();
+        getDelegate().onPostResume();
+    }
+
+    @Override
+    protected void onTitleChanged(CharSequence title, int color) {
+        super.onTitleChanged(title, color);
+        getDelegate().setTitle(title);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        getDelegate().onConfigurationChanged(newConfig);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        getDelegate().onStop();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        getDelegate().onDestroy();
+    }
+
+    public void invalidateOptionsMenu() {
+        getDelegate().invalidateOptionsMenu();
+    }
+
+    private AppCompatDelegate getDelegate() {
+        if (mDelegate == null) {
+            mDelegate = AppCompatDelegate.create(this, null);
+        }
+        return mDelegate;
+    }
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java
new file mode 100644 (file)
index 0000000..e66cf04
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * Copyright © 2018 Damyan Ivanov.
+ * This file is part of Mobile-Ledger.
+ * Mobile-Ledger 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.
+ *
+ * Mobile-Ledger 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 Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui.activity;
+
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import net.ktnx.mobileledger.ui.account_summary.AccountSummaryFragment;
+import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.ui.transaction_list.TransactionListFragment;
+
+public class MainActivity extends AppCompatActivity {
+    DrawerLayout drawer;
+    private AccountSummaryFragment accountSummaryFragment;
+    private TransactionListFragment transactionListFragment;
+    private Fragment currentFragment = null;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_account_summary);
+        Toolbar toolbar = findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+
+        drawer = findViewById(R.id.drawer_layout);
+        ActionBarDrawerToggle toggle =
+                new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open,
+                        R.string.navigation_drawer_close);
+        drawer.addDrawerListener(toggle);
+        toggle.syncState();
+
+        android.widget.TextView ver = drawer.findViewById(R.id.drawer_version_text);
+
+        try {
+            PackageInfo pi =
+                    getApplicationContext().getPackageManager().getPackageInfo(getPackageName(), 0);
+            ver.setText(pi.versionName);
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        onAccountSummaryClicked(null);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        LinearLayout grp = drawer.findViewById(R.id.nav_actions);
+        for (int i = 0; i < grp.getChildCount(); i++) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                grp.getChildAt(i).setBackgroundColor(
+                        getResources().getColor(R.color.drawer_background, getTheme()));
+            }
+            else {
+                grp.getChildAt(i)
+                        .setBackgroundColor(getResources().getColor(R.color.drawer_background));
+            }
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            drawer.findViewById(R.id.nav_account_summary).setBackgroundColor(
+                    getResources().getColor(R.color.table_row_even_bg, getTheme()));
+        }
+        else {
+            drawer.findViewById(R.id.nav_account_summary)
+                    .setBackgroundColor(getResources().getColor(R.color.table_row_even_bg));
+        }
+    }
+
+    public void fab_new_transaction_clicked(View view) {
+        Intent intent = new Intent(this, NewTransactionActivity.class);
+        startActivity(intent);
+        overridePendingTransition(R.anim.slide_in_right, R.anim.dummy);
+    }
+
+    public void nav_exit_clicked(View view) {
+        Log.w("app", "exiting");
+        finish();
+    }
+
+    public void nav_settings_clicked(View view) {
+        Intent intent = new Intent(this, SettingsActivity.class);
+        startActivity(intent);
+    }
+    private void markDrawerItemCurrent(int id) {
+        View item = drawer.findViewById(id);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            item.setBackgroundColor(getResources().getColor(R.color.table_row_even_bg, getTheme()));
+        }
+        else {
+            item.setBackgroundColor(getResources().getColor(R.color.table_row_even_bg));
+        }
+
+        LinearLayout actions = drawer.findViewById(R.id.nav_actions);
+        for (int i = 0; i < actions.getChildCount(); i++) {
+            View view = actions.getChildAt(i);
+            if (view.getId() != id) {
+                view.setBackgroundColor(getResources().getColor(android.R.color.transparent));
+            }
+        }
+    }
+    public void onOptionsMenuClicked(MenuItem menuItem) {
+        ContextMenu.ContextMenuInfo info = menuItem.getMenuInfo();
+        switch (menuItem.getItemId()) {
+            case R.id.menu_acc_summary_cancel_selection:
+                if (accountSummaryFragment != null)
+                    accountSummaryFragment.onCancelAccSelection(menuItem);
+                break;
+            case R.id.menu_acc_summary_confirm_selection:
+                if (accountSummaryFragment != null)
+                    accountSummaryFragment.onConfirmAccSelection(menuItem);
+                break;
+            case R.id.menu_acc_summary_only_starred:
+                if (accountSummaryFragment != null)
+                    accountSummaryFragment.onShowOnlyStarredClicked(menuItem);
+                break;
+            case R.id.menu_transaction_list_filter:
+                if (transactionListFragment != null)
+                    transactionListFragment.onShowFilterClick(menuItem);
+                break;
+            default:
+                Log.e("menu", String.format("Menu item %d not handled", menuItem.getItemId()));
+        }
+    }
+    public void onViewClicked(View view) {
+        switch (view.getId()) {
+            case R.id.clearAccountNameFilter:
+                if (transactionListFragment != null)
+                    transactionListFragment.onClearAccountNameClick(view);
+                break;
+            default:
+                Log.e("click", String.format("View %d click not handled", view.getId()));
+        }
+    }
+    public void onAccountSummaryClicked(View view) {
+        markDrawerItemCurrent(R.id.nav_account_summary);
+        drawer.closeDrawers();
+
+        FragmentManager fm = getSupportFragmentManager();
+        FragmentTransaction ft = fm.beginTransaction();
+        currentFragment = accountSummaryFragment = new AccountSummaryFragment();
+        ft.replace(R.id.root_frame, accountSummaryFragment);
+        ft.commit();
+    }
+    public void onLatestTransactionsClicked(View view) {
+        markDrawerItemCurrent(R.id.nav_latest_transactions);
+        drawer.closeDrawers();
+
+        FragmentManager fm = getSupportFragmentManager();
+        FragmentTransaction ft = fm.beginTransaction();
+        currentFragment = transactionListFragment = new TransactionListFragment();
+        ft.replace(R.id.root_frame, transactionListFragment);
+        ft.commit();
+    }
+    @Override
+    public void onBackPressed() {
+        DrawerLayout drawer = findViewById(R.id.drawer_layout);
+        if (drawer.isDrawerOpen(GravityCompat.START)) {
+            drawer.closeDrawer(GravityCompat.START);
+        }
+        else {
+            super.onBackPressed();
+        }
+    }
+
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionActivity.java
new file mode 100644 (file)
index 0000000..43c10b2
--- /dev/null
@@ -0,0 +1,447 @@
+/*
+ * Copyright © 2018 Damyan Ivanov.
+ * This file is part of Mobile-Ledger.
+ * Mobile-Ledger 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.
+ *
+ * Mobile-Ledger 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 Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui.activity;
+
+import android.annotation.SuppressLint;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.support.design.widget.BaseTransientBottomBar;
+import android.support.design.widget.Snackbar;
+import android.support.v4.app.DialogFragment;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.widget.AutoCompleteTextView;
+import android.widget.EditText;
+import android.widget.ProgressBar;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+import android.widget.TextView;
+
+import net.ktnx.mobileledger.ui.OnSwipeTouchListener;
+import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.async.SaveTransactionTask;
+import net.ktnx.mobileledger.async.TaskCallback;
+import net.ktnx.mobileledger.model.LedgerTransaction;
+import net.ktnx.mobileledger.model.LedgerTransactionAccount;
+import net.ktnx.mobileledger.ui.DatePickerFragment;
+import net.ktnx.mobileledger.utils.MLDB;
+
+import java.util.Date;
+import java.util.Objects;
+
+/*
+ * TODO: nicer progress while transaction is submitted
+ * TODO: latest transactions, maybe with browsing further in the past?
+ * TODO: reports
+ * TODO: get rid of the custom session/cookie and auth code?
+ *         (the last problem with the POST was the missing content-length header)
+ * TODO: app icon
+ * TODO: nicer swiping removal with visual feedback
+ * TODO: setup wizard
+ * TODO: update accounts/check settings upon change of backend settings
+ *  */
+
+public class NewTransactionActivity extends AppCompatActivity implements TaskCallback {
+    private static SaveTransactionTask saver;
+    private TableLayout table;
+    private ProgressBar progress;
+    private TextView text_date;
+    private AutoCompleteTextView text_descr;
+    private MenuItem mSave;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_new_transaction);
+        Toolbar toolbar = findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+
+        text_date = findViewById(R.id.new_transaction_date);
+        text_date.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(View v, boolean hasFocus) {
+                if (hasFocus) pickTransactionDate(v);
+            }
+        });
+        text_descr = findViewById(R.id.new_transaction_description);
+        MLDB.hook_autocompletion_adapter(this, text_descr, MLDB.DESCRIPTION_HISTORY_TABLE,
+                "description");
+        hook_text_change_listener(text_descr);
+
+        progress = findViewById(R.id.save_transaction_progress);
+
+        Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
+        table = findViewById(R.id.new_transaction_accounts_table);
+        for (int i = 0; i < table.getChildCount(); i++) {
+            TableRow row = (TableRow) table.getChildAt(i);
+            AutoCompleteTextView acc_name_view = (AutoCompleteTextView) row.getChildAt(0);
+            TextView amount_view = (TextView) row.getChildAt(1);
+            hook_swipe_listener(row);
+            MLDB.hook_autocompletion_adapter(this, acc_name_view, MLDB.ACCOUNTS_TABLE, "name");
+            hook_text_change_listener(acc_name_view);
+            hook_text_change_listener(amount_view);
+//            Log.d("swipe", "hooked to row "+i);
+        }
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        if (text_descr.getText().toString().isEmpty()) text_descr.requestFocus();
+    }
+
+    @Override
+    public void finish() {
+        super.finish();
+        overridePendingTransition(R.anim.dummy, R.anim.slide_out_right);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home:
+                finish();
+                return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    public void save_transaction() {
+        if (mSave != null) mSave.setVisible(false);
+        toggle_all_editing(false);
+        progress.setVisibility(View.VISIBLE);
+
+        saver = new SaveTransactionTask(this);
+
+        saver.setPref(PreferenceManager.getDefaultSharedPreferences(this));
+        String date = text_date.getText().toString();
+        if (date.isEmpty()) date = String.valueOf(new Date().getDate());
+        LedgerTransaction tr = new LedgerTransaction(date, text_descr.getText().toString());
+
+        TableLayout table = findViewById(R.id.new_transaction_accounts_table);
+        for (int i = 0; i < table.getChildCount(); i++) {
+            TableRow row = (TableRow) table.getChildAt(i);
+            String acc = ((TextView) row.getChildAt(0)).getText().toString();
+            String amt = ((TextView) row.getChildAt(1)).getText().toString();
+            LedgerTransactionAccount item =
+                    amt.length() > 0 ? new LedgerTransactionAccount(acc, Float.parseFloat(amt))
+                                     : new LedgerTransactionAccount(acc);
+
+            tr.addAccount(item);
+        }
+        saver.execute(tr);
+    }
+
+    private void toggle_all_editing(boolean enabled) {
+        text_date.setEnabled(enabled);
+        text_descr.setEnabled(enabled);
+        TableLayout table = findViewById(R.id.new_transaction_accounts_table);
+        for (int i = 0; i < table.getChildCount(); i++) {
+            TableRow row = (TableRow) table.getChildAt(i);
+            for (int j = 0; j < row.getChildCount(); j++) {
+                row.getChildAt(j).setEnabled(enabled);
+            }
+        }
+    }
+
+    private void hook_swipe_listener(final TableRow row) {
+        row.getChildAt(0).setOnTouchListener(new OnSwipeTouchListener(this) {
+            public void onSwipeLeft() {
+//                Log.d("swipe", "LEFT" + row.getId());
+                if (table.getChildCount() > 2) {
+                    TableRow prev_row = (TableRow) table.getChildAt(table.indexOfChild(row) - 1);
+                    TableRow next_row = (TableRow) table.getChildAt(table.indexOfChild(row) + 1);
+                    TextView prev_amt =
+                            (prev_row != null) ? (TextView) prev_row.getChildAt(1) : text_descr;
+                    TextView next_acc =
+                            (next_row != null) ? (TextView) next_row.getChildAt(0) : null;
+
+                    if (next_acc == null) {
+                        prev_amt.setNextFocusRightId(R.id.none);
+                        prev_amt.setNextFocusForwardId(R.id.none);
+                        prev_amt.setImeOptions(EditorInfo.IME_ACTION_DONE);
+                    }
+                    else {
+                        prev_amt.setNextFocusRightId(next_acc.getId());
+                        prev_amt.setNextFocusForwardId(next_acc.getId());
+                        prev_amt.setImeOptions(EditorInfo.IME_ACTION_NEXT);
+                    }
+
+                    if (row.hasFocus()) {
+                        if (next_acc != null) next_acc.requestFocus();
+                        else prev_amt.requestFocus();
+                    }
+
+                    table.removeView(row);
+                    check_transaction_submittable();
+//                    Toast.makeText(NewTransactionActivity.this, "LEFT", Toast.LENGTH_LONG).show();
+                }
+                else {
+                    Snackbar.make(table, R.string.msg_at_least_two_accounts_are_required,
+                            Snackbar.LENGTH_LONG).setAction("Action", null).show();
+                }
+            }
+            //            @Override
+//            public boolean performClick(View view, MotionEvent m) {
+//                return true;
+//            }
+            public boolean onTouch(View view, MotionEvent m) {
+                return gestureDetector.onTouchEvent(m);
+            }
+        });
+    }
+
+    private void hook_text_change_listener(final TextView view) {
+        view.addTextChangedListener(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) {
+//                Log.d("input", "text changed");
+                check_transaction_submittable();
+            }
+        });
+
+    }
+
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Inflate the menu; this adds items to the action bar if it is present.
+        getMenuInflater().inflate(R.menu.new_transaction, menu);
+        mSave = menu.findItem(R.id.action_submit_transaction);
+        if (mSave == null) throw new AssertionError();
+
+        check_transaction_submittable();
+
+        return true;
+    }
+
+    public void pickTransactionDate(View view) {
+        DialogFragment picker = new DatePickerFragment();
+        picker.show(getSupportFragmentManager(), "datePicker");
+    }
+
+    public int dp2px(float dp) {
+        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
+                getResources().getDisplayMetrics()));
+    }
+
+    private void do_add_account_row(boolean focus) {
+        final AutoCompleteTextView acc = new AutoCompleteTextView(this);
+        acc.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT,
+                TableRow.LayoutParams.WRAP_CONTENT, 9f));
+        acc.setHint(R.string.new_transaction_account_hint);
+        acc.setWidth(0);
+        acc.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_ENTER_ACTION |
+                          EditorInfo.IME_FLAG_NAVIGATE_NEXT);
+        acc.setSingleLine(true);
+
+        final EditText amt = new EditText(this);
+        amt.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT,
+                TableRow.LayoutParams.MATCH_PARENT, 1f));
+        amt.setHint(R.string.new_transaction_amount_hint);
+        amt.setWidth(0);
+        amt.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED |
+                         InputType.TYPE_NUMBER_FLAG_DECIMAL);
+        amt.setMinWidth(dp2px(40));
+        amt.setTextAlignment(EditText.TEXT_ALIGNMENT_VIEW_END);
+        amt.setImeOptions(EditorInfo.IME_ACTION_DONE);
+
+        // forward navigation support
+        final TableRow last_row = (TableRow) table.getChildAt(table.getChildCount() - 1);
+        final TextView last_amt = (TextView) last_row.getChildAt(1);
+        last_amt.setNextFocusForwardId(acc.getId());
+        last_amt.setNextFocusRightId(acc.getId());
+        last_amt.setImeOptions(EditorInfo.IME_ACTION_NEXT);
+        acc.setNextFocusForwardId(amt.getId());
+        acc.setNextFocusRightId(amt.getId());
+
+        final TableRow row = new TableRow(this);
+        row.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT,
+                TableRow.LayoutParams.MATCH_PARENT));
+        row.setGravity(Gravity.BOTTOM);
+        row.addView(acc);
+        row.addView(amt);
+        table.addView(row);
+
+        if (focus) acc.requestFocus();
+
+        hook_swipe_listener(row);
+        MLDB.hook_autocompletion_adapter(this, acc, MLDB.ACCOUNTS_TABLE, "name");
+        hook_text_change_listener(acc);
+        hook_text_change_listener(amt);
+    }
+
+    public void addTransactionAccountFromMenu(MenuItem item) {
+        do_add_account_row(true);
+    }
+
+    public void resetTransactionFromMenu(MenuItem item) {
+        reset_form();
+    }
+
+    public void saveTransactionFromMenu(MenuItem item) {
+        save_transaction();
+    }
+
+    private boolean is_zero(float f) {
+        return (f < 0.005) && (f > -0.005);
+    }
+
+    // rules:
+    // 1) at least two account names
+    // 2) each amount must have account name
+    // 3) amounts must balance to 0, or
+    // 3a) there must be exactly one empty amount
+    // 4) empty accounts with empty amounts are ignored
+    // 5) a row with an empty account name or empty amount is guaranteed to exist
+    @SuppressLint("DefaultLocale")
+    private void check_transaction_submittable() {
+        TableLayout table = findViewById(R.id.new_transaction_accounts_table);
+        int accounts = 0;
+        int accounts_with_values = 0;
+        int amounts = 0;
+        int amounts_with_accounts = 0;
+        int empty_rows = 0;
+        TextView empty_amount = null;
+        boolean single_empty_amount = false;
+        boolean single_empty_amount_has_account = false;
+        float running_total = 0f;
+        boolean have_description =
+                !((TextView) findViewById(R.id.new_transaction_description)).getText().toString()
+                        .isEmpty();
+
+        try {
+            for (int i = 0; i < table.getChildCount(); i++) {
+                TableRow row = (TableRow) table.getChildAt(i);
+
+                TextView acc_name_v = (TextView) row.getChildAt(0);
+                TextView amount_v = (TextView) row.getChildAt(1);
+                String amt = String.valueOf(amount_v.getText());
+                String acc_name = String.valueOf(acc_name_v.getText());
+                acc_name = acc_name.trim();
+
+                if (!acc_name.isEmpty()) {
+                    accounts++;
+
+                    if (!amt.isEmpty()) {
+                        accounts_with_values++;
+                    }
+                }
+                else empty_rows++;
+
+                if (amt.isEmpty()) {
+                    amount_v.setHint(String.format("%1.2f", 0f));
+                    if (empty_amount == null) {
+                        empty_amount = amount_v;
+                        single_empty_amount = true;
+                        single_empty_amount_has_account = !acc_name.isEmpty();
+                    }
+                    else if (!acc_name.isEmpty()) single_empty_amount = false;
+                }
+                else {
+                    amounts++;
+                    if (!acc_name.isEmpty()) amounts_with_accounts++;
+                    running_total += Float.valueOf(amt);
+                }
+            }
+
+            if ((empty_rows == 0) &&
+                ((table.getChildCount() == accounts) || (table.getChildCount() == amounts)))
+            {
+                do_add_account_row(false);
+            }
+
+            Log.d("submittable", String.format("accounts=%d, accounts_with_values=%s, " +
+                                               "amounts_with_accounts=%d, amounts=%d, running_total=%1.2f, " +
+                                               "single_empty_with_acc=%s", accounts,
+                    accounts_with_values, amounts_with_accounts, amounts, running_total,
+                    (single_empty_amount && single_empty_amount_has_account) ? "true" : "false"));
+
+            if (have_description && (accounts >= 2) && (accounts_with_values >= (accounts - 1)) &&
+                (amounts_with_accounts == amounts) &&
+                (single_empty_amount && single_empty_amount_has_account || is_zero(running_total)))
+            {
+                if (mSave != null) mSave.setVisible(true);
+            }
+            else if (mSave != null) mSave.setVisible(false);
+
+            if (single_empty_amount) {
+                empty_amount.setHint(String.format("%1.2f",
+                        (Math.abs(running_total) > 0.005) ? -running_total : 0f));
+            }
+
+        }
+        catch (NumberFormatException e) {
+            if (mSave != null) mSave.setVisible(false);
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            if (mSave != null) mSave.setVisible(false);
+        }
+    }
+
+    @Override
+    public void done(String error) {
+        progress.setVisibility(View.INVISIBLE);
+        Log.d("visuals", "hiding progress");
+
+        if (error == null) reset_form();
+        else Snackbar.make(findViewById(R.id.new_transaction_accounts_table), error,
+                BaseTransientBottomBar.LENGTH_LONG).show();
+
+        toggle_all_editing(true);
+        check_transaction_submittable();
+    }
+
+    private void reset_form() {
+        text_date.setText("");
+        text_descr.setText("");
+
+        text_descr.requestFocus();
+
+        while (table.getChildCount() > 2) {
+            table.removeViewAt(2);
+        }
+        for (int i = 0; i < 2; i++) {
+            TableRow tr = (TableRow) table.getChildAt(i);
+            if (tr == null) break;
+
+            ((TextView) tr.getChildAt(0)).setText("");
+            ((TextView) tr.getChildAt(1)).setText("");
+        }
+    }
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/SettingsActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/SettingsActivity.java
new file mode 100644 (file)
index 0000000..8f6ff2f
--- /dev/null
@@ -0,0 +1,319 @@
+/*
+ * Copyright © 2018 Damyan Ivanov.
+ * This file is part of Mobile-Ledger.
+ * Mobile-Ledger 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.
+ *
+ * Mobile-Ledger 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 Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui.activity;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceManager;
+import android.preference.RingtonePreference;
+import android.support.v4.app.NavUtils;
+import android.support.v7.app.ActionBar;
+import android.text.TextUtils;
+import android.view.MenuItem;
+
+import net.ktnx.mobileledger.R;
+
+import java.util.List;
+
+/**
+ * A {@link PreferenceActivity} that presents a set of application settings. On
+ * handset devices, settings are presented as a single list. On tablets,
+ * settings are split by category, with category headers shown to the left of
+ * the list of settings.
+ * <p>
+ * See <a href="http://developer.android.com/design/patterns/settings.html">
+ * Android Design: Settings</a> for design guidelines and the <a
+ * href="http://developer.android.com/guide/topics/ui/settings.html">Settings
+ * API Guide</a> for more information on developing a Settings UI.
+ */
+public class SettingsActivity extends AppCompatPreferenceActivity {
+    public static String PREF_KEY_SHOW_ONLY_STARRED_ACCOUNTS = "pref_show_only_starred_accounts";
+
+    /**
+     * A preference value change listener that updates the preference's summary
+     * to reflect its new value.
+     */
+    private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = (preference, value) -> {
+        String stringValue = value.toString();
+
+        if (preference instanceof ListPreference) {
+            // For list preferences, look up the correct display value in
+            // the preference's 'entries' list.
+            ListPreference listPreference = (ListPreference) preference;
+            int index = listPreference.findIndexOfValue(stringValue);
+
+            // Set the summary to reflect the new value.
+            preference.setSummary(
+                    index >= 0
+                            ? listPreference.getEntries()[index]
+                            : null);
+
+        } else if (preference instanceof RingtonePreference) {
+            // For ringtone preferences, look up the correct display value
+            // using RingtoneManager.
+            if (TextUtils.isEmpty(stringValue)) {
+                // Empty values correspond to 'silent' (no ringtone).
+                preference.setSummary(R.string.pref_ringtone_silent);
+
+            } else {
+                Ringtone ringtone = RingtoneManager.getRingtone(
+                        preference.getContext(), Uri.parse(stringValue));
+
+                if (ringtone == null) {
+                    // Clear the summary if there was a lookup error.
+                    preference.setSummary(null);
+                } else {
+                    // Set the summary to reflect the new ringtone display
+                    // name.
+                    String name = ringtone.getTitle(preference.getContext());
+                    preference.setSummary(name);
+                }
+            }
+        } else {
+            // For all other preferences, set the summary to the value's
+            // simple string representation.
+            preference.setSummary(stringValue);
+        }
+
+        return true;
+    };
+
+    /**
+     * Helper method to determine if the device has an extra-large screen. For
+     * example, 10" tablets are extra-large.
+     */
+    private static boolean isXLargeTablet(Context context) {
+        return (context.getResources().getConfiguration().screenLayout
+                & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
+    }
+
+    /**
+     * Binds a preference's summary to its value. More specifically, when the
+     * preference's value is changed, its summary (line of text below the
+     * preference title) is updated to reflect the value. The summary is also
+     * immediately updated upon calling this method. The exact display format is
+     * dependent on the type of preference.
+     *
+     * @see #sBindPreferenceSummaryToValueListener
+     */
+    private static void bindPreferenceSummaryToValue(Preference preference) {
+        // Set the listener to watch for value changes.
+        preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
+
+        // Trigger the listener immediately with the preference's
+        // current value.
+        sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
+                PreferenceManager
+                        .getDefaultSharedPreferences(preference.getContext())
+                        .getString(preference.getKey(), ""));
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setupActionBar();
+    }
+
+    /**
+     * Set up the {@link android.app.ActionBar}, if the API is available.
+     */
+    private void setupActionBar() {
+        ActionBar actionBar = getSupportActionBar();
+        if (actionBar != null) {
+            // Show the Up button in the action bar.
+            actionBar.setDisplayHomeAsUpEnabled(true);
+        }
+    }
+
+    @Override
+    public boolean onMenuItemSelected(int featureId, MenuItem item) {
+        int id = item.getItemId();
+        if (id == android.R.id.home) {
+            if (!super.onMenuItemSelected(featureId, item)) {
+                NavUtils.navigateUpFromSameTask(this);
+            }
+            return true;
+        }
+        return super.onMenuItemSelected(featureId, item);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean onIsMultiPane() {
+        return isXLargeTablet(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public void onBuildHeaders(List<Header> target) {
+        loadHeadersFromResource(R.xml.pref_headers, target);
+    }
+
+    /**
+     * This method stops fragment injection in malicious applications.
+     * Make sure to deny any unknown fragments here.
+     */
+    protected boolean isValidFragment(String fragmentName) {
+        return PreferenceFragment.class.getName().equals(fragmentName)
+                || BackendPreferenceFragment.class.getName().equals(fragmentName)
+                || DataSyncPreferenceFragment.class.getName().equals(fragmentName)
+                || NotificationPreferenceFragment.class.getName().equals(fragmentName)
+                || InterfacePreferenceFragment.class.getName().equals(fragmentName);
+    }
+
+    /**
+     * This fragment shows general preferences only. It is used when the
+     * activity is showing a two-pane settings UI.
+     */
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public static class BackendPreferenceFragment extends PreferenceFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            addPreferencesFromResource(R.xml.pref_backend);
+            setHasOptionsMenu(true);
+
+            // Bind the summaries of EditText/List/Dialog/Ringtone preferences
+            // to their values. When their values change, their summaries are
+            // updated to reflect the new value, per the Android Design
+            // guidelines.
+            bindPreferenceSummaryToValue(findPreference("backend_url"));
+            bindPreferenceSummaryToValue(findPreference("backend_auth_user"));
+
+        }
+
+        @Override
+        public boolean onOptionsItemSelected(MenuItem item) {
+            int id = item.getItemId();
+            if (id == android.R.id.home) {
+                startActivity(new Intent(getActivity(), SettingsActivity.class));
+                return true;
+            }
+            return super.onOptionsItemSelected(item);
+        }
+    }
+
+    /**
+     * This fragment shows general preferences only. It is used when the
+     * activity is showing a two-pane settings UI.
+     */
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public static
+    class InterfacePreferenceFragment extends PreferenceFragment {
+        @Override
+        public
+        void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            addPreferencesFromResource(R.xml.pref_interface);
+            setHasOptionsMenu(true);
+
+            // Bind the summaries of EditText/List/Dialog/Ringtone preferences
+            // to their values. When their values change, their summaries are
+            // updated to reflect the new value, per the Android Design
+            // guidelines.
+
+        }
+
+        @Override
+        public
+        boolean onOptionsItemSelected(MenuItem item) {
+            int id = item.getItemId();
+            if (id == android.R.id.home) {
+                startActivity(new Intent(getActivity(), SettingsActivity.class));
+                return true;
+            }
+            return super.onOptionsItemSelected(item);
+        }
+    }
+
+    /**
+     * This fragment shows notification preferences only. It is used when the
+     * activity is showing a two-pane settings UI.
+     */
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public static class NotificationPreferenceFragment extends PreferenceFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            addPreferencesFromResource(R.xml.pref_notification);
+            setHasOptionsMenu(true);
+
+            // Bind the summaries of EditText/List/Dialog/Ringtone preferences
+            // to their values. When their values change, their summaries are
+            // updated to reflect the new value, per the Android Design
+            // guidelines.
+            bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone"));
+        }
+
+        @Override
+        public boolean onOptionsItemSelected(MenuItem item) {
+            int id = item.getItemId();
+            if (id == android.R.id.home) {
+                startActivity(new Intent(getActivity(), SettingsActivity.class));
+                return true;
+            }
+            return super.onOptionsItemSelected(item);
+        }
+    }
+
+    /**
+     * This fragment shows data and sync preferences only. It is used when the
+     * activity is showing a two-pane settings UI.
+     */
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public static class DataSyncPreferenceFragment extends PreferenceFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            addPreferencesFromResource(R.xml.pref_data_sync);
+            setHasOptionsMenu(true);
+
+            // Bind the summaries of EditText/List/Dialog/Ringtone preferences
+            // to their values. When their values change, their summaries are
+            // updated to reflect the new value, per the Android Design
+            // guidelines.
+            bindPreferenceSummaryToValue(findPreference("sync_frequency"));
+        }
+
+        @Override
+        public boolean onOptionsItemSelected(MenuItem item) {
+            int id = item.getItemId();
+            if (id == android.R.id.home) {
+                startActivity(new Intent(getActivity(), SettingsActivity.class));
+                return true;
+            }
+            return super.onOptionsItemSelected(item);
+        }
+    }
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java
new file mode 100644 (file)
index 0000000..7983aa5
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * Copyright © 2018 Damyan Ivanov.
+ * This file is part of Mobile-Ledger.
+ * Mobile-Ledger 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.
+ *
+ * Mobile-Ledger 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 Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui.transaction_list;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.graphics.Typeface;
+import android.support.annotation.NonNull;
+import android.support.constraint.ConstraintLayout;
+import android.support.v7.widget.AppCompatTextView;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.model.LedgerTransaction;
+import net.ktnx.mobileledger.model.LedgerTransactionAccount;
+import net.ktnx.mobileledger.utils.Globals;
+import net.ktnx.mobileledger.utils.MLDB;
+
+import static net.ktnx.mobileledger.utils.DimensionUtils.dp2px;
+
+public class TransactionListAdapter
+        extends RecyclerView.Adapter<TransactionListAdapter.TransactionRowHolder> {
+    TransactionListViewModel model;
+    private String boldAccountName;
+    public TransactionListAdapter(TransactionListViewModel model) {
+        this.model = model;
+    }
+    public void onBindViewHolder(@NonNull TransactionRowHolder holder, int position) {
+        LedgerTransaction tr = model.getTransaction(position);
+        // in a race when transaction list is reduced, but the model hasn't been notified yet
+        // the view will disappear when the notifications reaches the model, so by simply omitting
+        // the out-of-range get() call nothing bad happens - just a to-be-deleted view remains
+        // a bit longer
+        if (tr == null) return;
+
+        Context ctx = holder.row.getContext();
+
+        try (SQLiteDatabase db = MLDB.getReadableDatabase(ctx)) {
+            tr.loadData(db);
+            holder.tvDescription.setText(tr.getDescription());
+            holder.tvDate.setText(tr.getDate());
+
+            int rowIndex = 0;
+            for (LedgerTransactionAccount acc : tr.getAccounts()) {
+                LinearLayout row = (LinearLayout) holder.tableAccounts.getChildAt(rowIndex++);
+                TextView accName, accAmount;
+                if (row == null) {
+                    row = new LinearLayout(ctx);
+                    row.setLayoutParams(
+                            new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
+                                    LinearLayout.LayoutParams.WRAP_CONTENT));
+                    row.setGravity(Gravity.CENTER_VERTICAL);
+                    row.setOrientation(LinearLayout.HORIZONTAL);
+                    row.setPaddingRelative(dp2px(ctx, 8), 0, 0, 0);
+                    accName = new AppCompatTextView(ctx);
+                    accName.setLayoutParams(
+                            new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT,
+                                    5f));
+                    accName.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
+                    row.addView(accName);
+                    accAmount = new AppCompatTextView(ctx);
+                    LinearLayout.LayoutParams llp =
+                            new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
+                                    LinearLayout.LayoutParams.WRAP_CONTENT);
+                    llp.setMarginEnd(0);
+                    accAmount.setLayoutParams(llp);
+                    accAmount.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
+                    accAmount.setMinWidth(dp2px(ctx, 60));
+                    row.addView(accAmount);
+                    holder.tableAccounts.addView(row);
+                }
+                else {
+                    accName = (TextView) row.getChildAt(0);
+                    accAmount = (TextView) row.getChildAt(1);
+                }
+                accName.setText(acc.getAccountName());
+                accAmount.setText(acc.toString());
+
+                if ((boldAccountName != null) && boldAccountName.equals(acc.getAccountName())) {
+                    accName.setTypeface(null, Typeface.BOLD);
+                    accAmount.setTypeface(null, Typeface.BOLD);
+                    accName.setTextColor(Globals.primaryDark);
+                    accAmount.setTextColor(Globals.primaryDark);
+                }
+                else {
+                    accName.setTypeface(null, Typeface.NORMAL);
+                    accAmount.setTypeface(null, Typeface.NORMAL);
+                    accName.setTextColor(Globals.defaultTextColor);
+                    accAmount.setTextColor(Globals.defaultTextColor);
+                }
+
+            }
+            if (holder.tableAccounts.getChildCount() > rowIndex) {
+                holder.tableAccounts
+                        .removeViews(rowIndex, holder.tableAccounts.getChildCount() - rowIndex);
+            }
+
+            if (position % 2 == 0) {
+                holder.row.setBackgroundColor(Globals.table_row_even_bg);
+            }
+            else {
+                holder.row.setBackgroundColor(Globals.table_row_odd_bg);
+            }
+
+            Log.d("transactions", String.format("Filled position %d", position));
+        }
+    }
+
+    @NonNull
+    @Override
+    public TransactionRowHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        Log.d("perf", "onCreateViewHolder called");
+        View row = LayoutInflater.from(parent.getContext())
+                .inflate(R.layout.transaction_list_row, parent, false);
+        return new TransactionRowHolder(row);
+    }
+
+    @Override
+    public int getItemCount() {
+        return model.getTransactionCount();
+    }
+    public void setBoldAccountName(String boldAccountName) {
+        this.boldAccountName = boldAccountName;
+    }
+    public void resetBoldAccountName() {
+        this.boldAccountName = null;
+    }
+    class TransactionRowHolder extends RecyclerView.ViewHolder {
+        TextView tvDescription, tvDate;
+        LinearLayout tableAccounts;
+        ConstraintLayout row;
+        public TransactionRowHolder(@NonNull View itemView) {
+            super(itemView);
+            this.row = itemView.findViewById(R.id.transaction_row);
+            this.tvDescription = itemView.findViewById(R.id.transaction_row_description);
+            this.tvDate = itemView.findViewById(R.id.transaction_row_date);
+            this.tableAccounts = itemView.findViewById(R.id.transaction_row_acc_amounts);
+        }
+    }
+}
\ No newline at end of file
index 97d2fe8..72e07b2 100644 (file)
 package net.ktnx.mobileledger.ui.transaction_list;
 
 import android.arch.lifecycle.ViewModelProviders;
+import android.content.Context;
+import android.database.MatrixCursor;
+import android.os.Build;
 import android.os.Bundle;
+import android.preference.PreferenceManager;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v4.app.Fragment;
+import android.support.v4.widget.SwipeRefreshLayout;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
 import android.util.Log;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.AutoCompleteTextView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
 
+import net.ktnx.mobileledger.ui.activity.MainActivity;
 import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.async.RetrieveTransactionsTask;
+import net.ktnx.mobileledger.utils.Globals;
+import net.ktnx.mobileledger.utils.MLDB;
 
-public class TransactionListFragment extends Fragment {
+import java.lang.ref.WeakReference;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+
+import static android.content.Context.INPUT_METHOD_SERVICE;
 
-    private TransactionListViewModel mViewModel;
-    public static TransactionListFragment newInstance() {
-        return new TransactionListFragment();
+public class TransactionListFragment extends Fragment {
+    public TransactionListViewModel model;
+    private MainActivity mActivity;
+    private View bTransactionListCancelDownload;
+    private MenuItem menuTransactionListFilter;
+    private View vAccountFilter;
+    private SwipeRefreshLayout swiper;
+    private RecyclerView root;
+    private ProgressBar progressBar;
+    private LinearLayout progressLayout;
+    private TextView tvLastUpdate;
+    private TransactionListAdapter modelAdapter;
+    private RetrieveTransactionsTask retrieveTransactionsTask;
+    private AutoCompleteTextView accNameFilter;
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setHasOptionsMenu(true);
+    }
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mActivity = (MainActivity) context;
     }
     @Nullable
     @Override
@@ -46,8 +91,158 @@ public class TransactionListFragment extends Fragment {
     public void onActivityCreated(@Nullable Bundle savedInstanceState) {
         Log.d("flow", "TransactionListFragment.onActivityCreated called");
         super.onActivityCreated(savedInstanceState);
-        mViewModel = ViewModelProviders.of(this).get(TransactionListViewModel.class);
-        // TODO: Use the ViewModel
+
+        swiper = mActivity.findViewById(R.id.transaction_swipe);
+        if (swiper == null) throw new RuntimeException("Can't get hold on the swipe layout");
+        root = mActivity.findViewById(R.id.transaction_root);
+        if (root == null) throw new RuntimeException("Can't get hold on the transaction list view");
+        progressBar = mActivity.findViewById(R.id.transaction_list_progress_bar);
+        if (progressBar == null)
+            throw new RuntimeException("Can't get hold on the transaction list progress bar");
+        progressLayout = mActivity.findViewById(R.id.transaction_progress_layout);
+        if (progressLayout == null) throw new RuntimeException(
+                "Can't get hold on the transaction list progress bar layout");
+        tvLastUpdate = mActivity.findViewById(R.id.transactions_last_update);
+        updateLastUpdateText();
+        model = ViewModelProviders.of(this).get(TransactionListViewModel.class);
+        modelAdapter = new TransactionListAdapter(model);
+
+        RecyclerView root = mActivity.findViewById(R.id.transaction_root);
+        root.setAdapter(modelAdapter);
+
+        LinearLayoutManager llm = new LinearLayoutManager(mActivity);
+
+        llm.setOrientation(LinearLayoutManager.VERTICAL);
+        root.setLayoutManager(llm);
+
+        swiper.setOnRefreshListener(() -> {
+            Log.d("ui", "refreshing transactions via swipe");
+            update_transactions();
+        });
+
+        swiper.setColorSchemeResources(R.color.colorPrimary, R.color.colorAccent);
+
+        vAccountFilter = mActivity.findViewById(R.id.transaction_list_account_name_filter);
+        accNameFilter = mActivity.findViewById(R.id.transaction_filter_account_name);
+        bTransactionListCancelDownload =
+                mActivity.findViewById(R.id.transaction_list_cancel_download);
+
+        TransactionListFragment me = this;
+        MLDB.hook_autocompletion_adapter(mActivity, accNameFilter, "accounts", "name");
+        accNameFilter.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                Log.d("tmp", "direct onItemClick");
+                model.reloadTransactions(me);
+                MatrixCursor mc = (MatrixCursor) parent.getItemAtPosition(position);
+                modelAdapter.setBoldAccountName(mc.getString(1));
+                modelAdapter.notifyDataSetChanged();
+                Globals.hideSoftKeyboard(mActivity);
+            }
+        });
+
+        updateLastUpdateText();
+        long last_update = MLDB.get_option_value(mActivity, MLDB.OPT_TRANSACTION_LIST_STAMP, 0L);
+        Log.d("transactions", String.format("Last update = %d", last_update));
+        if (last_update == 0) {
+            update_transactions();
+        }
+        else {
+            model.reloadTransactions(this);
+        }
+    }
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        inflater.inflate(R.menu.transaction_list, menu);
+
+        menuTransactionListFilter = menu.findItem(R.id.menu_transaction_list_filter);
+        if ((menuTransactionListFilter == null)) throw new AssertionError();
+
+        super.onCreateOptionsMenu(menu, inflater);
+    }
+    private void update_transactions() {
+        retrieveTransactionsTask = new RetrieveTransactionsTask(new WeakReference<>(this));
+
+        RetrieveTransactionsTask.Params params = new RetrieveTransactionsTask.Params(
+                PreferenceManager.getDefaultSharedPreferences(mActivity));
+
+        retrieveTransactionsTask.execute(params);
+        bTransactionListCancelDownload.setEnabled(true);
+    }
+    public void onRetrieveStart() {
+        progressBar.setIndeterminate(true);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) progressBar.setProgress(0, false);
+        else progressBar.setProgress(0);
+        progressLayout.setVisibility(View.VISIBLE);
+    }
+    public void onRetrieveProgress(RetrieveTransactionsTask.Progress progress) {
+        if ((progress.getTotal() == RetrieveTransactionsTask.Progress.INDETERMINATE) ||
+            (progress.getTotal() == 0))
+        {
+            progressBar.setIndeterminate(true);
+        }
+        else {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                progressBar.setMin(0);
+            }
+            progressBar.setMax(progress.getTotal());
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+                progressBar.setProgress(progress.getProgress(), true);
+            }
+            else progressBar.setProgress(progress.getProgress());
+            progressBar.setIndeterminate(false);
+        }
     }
 
+    public void onRetrieveDone(boolean success) {
+        progressLayout.setVisibility(View.GONE);
+        swiper.setRefreshing(false);
+        updateLastUpdateText();
+        if (success) {
+            Log.d("transactions", "calling notifyDataSetChanged()");
+            modelAdapter.notifyDataSetChanged();
+        }
+    }
+    private void updateLastUpdateText() {
+        {
+            long last_update =
+                    MLDB.get_option_value(mActivity, MLDB.OPT_TRANSACTION_LIST_STAMP, 0L);
+            Log.d("transactions", String.format("Last update = %d", last_update));
+            if (last_update == 0) {
+                tvLastUpdate.setText(getString(R.string.transaction_last_update_never));
+            }
+            else {
+                Date date = new Date(last_update);
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                    tvLastUpdate.setText(date.toInstant().atZone(ZoneId.systemDefault())
+                            .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
+                }
+                else {
+                    tvLastUpdate.setText(date.toLocaleString());
+                }
+            }
+        }
+    }
+    public void onClearAccountNameClick(View view) {
+        vAccountFilter.setVisibility(View.GONE);
+        menuTransactionListFilter.setVisible(true);
+        accNameFilter.setText(null);
+        model.reloadTransactions(this);
+        modelAdapter.resetBoldAccountName();
+        modelAdapter.notifyDataSetChanged();
+        Globals.hideSoftKeyboard(mActivity);
+    }
+    public void onShowFilterClick(MenuItem menuItem) {
+        vAccountFilter.setVisibility(View.VISIBLE);
+        menuTransactionListFilter.setVisible(false);
+        accNameFilter.requestFocus();
+        InputMethodManager imm =
+                (InputMethodManager) mActivity.getSystemService(INPUT_METHOD_SERVICE);
+        imm.showSoftInput(accNameFilter, 0);
+    }
+    public void onStopTransactionRefreshClick(View view) {
+        Log.d("interactive", "Cancelling transactions refresh");
+        if (retrieveTransactionsTask != null) retrieveTransactionsTask.cancel(false);
+        bTransactionListCancelDownload.setEnabled(false);
+    }
 }
index 1680236..ec339fc 100644 (file)
@@ -17,8 +17,8 @@
 
 package net.ktnx.mobileledger.ui.transaction_list;
 
+import android.app.Activity;
 import android.arch.lifecycle.ViewModel;
-import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.util.Log;
@@ -26,7 +26,6 @@ import android.view.View;
 import android.widget.AutoCompleteTextView;
 
 import net.ktnx.mobileledger.R;
-import net.ktnx.mobileledger.TransactionListActivity;
 import net.ktnx.mobileledger.model.LedgerTransaction;
 import net.ktnx.mobileledger.utils.MLDB;
 
@@ -36,10 +35,11 @@ public class TransactionListViewModel extends ViewModel {
 
     private ArrayList<LedgerTransaction> transactions;
 
-    public void reloadTransactions(Context context) {
+    public void reloadTransactions(TransactionListFragment context) {
         ArrayList<LedgerTransaction> newList = new ArrayList<>();
 
-        TransactionListActivity act = (TransactionListActivity) context;
+        Activity act = context.getActivity();
+
         boolean hasFilter =
                 act.findViewById(R.id.transaction_list_account_name_filter).getVisibility() ==
                 View.VISIBLE;
@@ -64,7 +64,7 @@ public class TransactionListViewModel extends ViewModel {
         }
 
         Log.d("tmp", sql);
-        try (SQLiteDatabase db = MLDB.getReadableDatabase(context)) {
+        try (SQLiteDatabase db = MLDB.getReadableDatabase(act)) {
             try (Cursor cursor = db.rawQuery(sql, params)) {
                 while (cursor.moveToNext()) {
                     newList.add(new LedgerTransaction(cursor.getInt(0)));
index b7fffad..e32a432 100644 (file)
 
 package net.ktnx.mobileledger.utils;
 
+import android.app.Activity;
+import android.content.Context;
 import android.support.annotation.ColorInt;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
 
 public final class Globals {
     @ColorInt
@@ -26,5 +30,14 @@ public final class Globals {
     public static int table_row_odd_bg;
     @ColorInt
     public static int primaryDark, defaultTextColor;
+    public static void hideSoftKeyboard(Activity act) {
+        // hide the keyboard
+        View v = act.getCurrentFocus();
+        if (v != null) {
+            InputMethodManager imm =
+                    (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE);
+            imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
+        }
+    }
 
 }
\ No newline at end of file
diff --git a/app/src/main/res/layout/account_summary_fragment.xml b/app/src/main/res/layout/account_summary_fragment.xml
new file mode 100644 (file)
index 0000000..72d800c
--- /dev/null
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright © 2018 Damyan Ivanov.
+  ~ This file is part of Mobile-Ledger.
+  ~ Mobile-Ledger 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.
+  ~
+  ~ Mobile-Ledger 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 Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
+  -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/account_summary_frame"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".ui.account_summary.AccountSummaryFragment">
+
+    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:app="http://schemas.android.com/apk/res-auto"
+        xmlns:tools="http://schemas.android.com/tools"
+        android:id="@+id/content_account_summary_layout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior"
+        tools:context=".ui.activity.MainActivity"
+        tools:showIn="@layout/app_bar_account_summary">
+
+        <android.support.v4.widget.SwipeRefreshLayout
+            android:id="@+id/account_swiper"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <android.support.v7.widget.RecyclerView
+                android:id="@+id/account_root"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:choiceMode="multipleChoice"
+                android:drawSelectorOnTop="true"
+                android:orientation="vertical">
+
+            </android.support.v7.widget.RecyclerView>
+        </android.support.v4.widget.SwipeRefreshLayout>
+    </android.support.constraint.ConstraintLayout>
+</FrameLayout>
\ No newline at end of file
index c03c598..b36b0a0 100644 (file)
@@ -21,7 +21,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    tools:context=".NewTransactionActivity">
+    tools:context=".ui.activity.NewTransactionActivity">
 
     <android.support.design.widget.AppBarLayout
         android:layout_width="match_parent"
index 7e29045..dde9f39 100644 (file)
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
   ~ Copyright © 2018 Damyan Ivanov.
   ~ This file is part of Mobile-Ledger.
   ~ Mobile-Ledger is free software: you can distribute it and/or modify it
@@ -21,7 +20,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    tools:context=".AccountSummary">
+    tools:context=".ui.activity.MainActivity">
 
     <android.support.design.widget.AppBarLayout
         android:layout_width="match_parent"
 
     </android.support.design.widget.AppBarLayout>
 
-    <include layout="@layout/content_account_summary" />
+    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools"
+        android:id="@+id/root_frame"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginTop="?attr/actionBarSize"
+        tools:context=".ui.account_summary.AccountSummaryFragment"></FrameLayout>
 
     <android.support.design.widget.FloatingActionButton
         android:id="@+id/btn_add_transaction"
index f7610f3..ba38855 100644 (file)
@@ -23,7 +23,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     app:layout_behavior="@string/appbar_scrolling_view_behavior"
-    tools:context=".AccountSummary"
+    tools:context=".ui.activity.MainActivity"
     tools:showIn="@layout/app_bar_account_summary">
 
     <android.support.v4.widget.SwipeRefreshLayout
index dd7e3ff..deba44f 100644 (file)
@@ -22,7 +22,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     app:layout_behavior="@string/appbar_scrolling_view_behavior"
-    tools:context=".NewTransactionActivity"
+    tools:context=".ui.activity.NewTransactionActivity"
     tools:showIn="@layout/activity_new_transaction">
 
     <ScrollView
index f422e17..e2b35cd 100644 (file)
@@ -47,6 +47,7 @@
                     android:id="@+id/nav_account_summary"
                     style="@style/nav_button"
                     android:drawableStart="@drawable/ic_home_black_24dp"
+                    android:onClick="onAccountSummaryClicked"
                     android:text="@string/account_summary_title" />
 
                 <TextView
index afb1d2e..698db2e 100644 (file)
@@ -20,7 +20,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    tools:context=".TransactionListActivity">
+    tools:context=".ui.activity.MainActivity">
 
     <android.support.design.widget.AppBarLayout
         android:layout_width="match_parent"
index ead3956..4a41acc 100644 (file)
@@ -23,7 +23,7 @@
     android:layout_height="match_parent"
     app:layout_behavior="@string/appbar_scrolling_view_behavior"
     app:layout_constraintBottom_toBottomOf="parent"
-    tools:context=".TransactionListActivity">
+    tools:context="net.ktnx.mobileledger.ui.activity.MainActivity">
 
     <LinearLayout
         android:id="@+id/last_update_row"
                 android:layout_weight="1" />
 
             <TextView
-                android:id="@+id/textView3"
+                android:id="@+id/clearAccountNameFilter"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:background="@drawable/ic_clear_black_24dp"
                 android:clickable="true"
-                android:onClick="onClearAccountNameClick" />
+                android:onClick="onViewClicked"
+                android:focusable="true" />
 
         </LinearLayout>
 
index 98d418a..d2bd884 100644 (file)
   -->
 
 <menu xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:android="http://schemas.android.com/apk/res/android">
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context="net.ktnx.mobileledger.ui.activity.MainActivity">
 
     <item
         android:id="@+id/menu_acc_summary_only_starred"
         android:checkable="true"
         android:checked="false"
+        android:onClick="onOptionsMenuClicked"
         android:title="@string/menu_acc_summary_show_only_starred_title"
         app:actionLayout="@layout/switch_item"
-        android:onClick="onShowOnlyStarredClicked"
         app:showAsAction="never" />
     <item android:id="@+id/menu_acc_summary_hide_selected"
         android:icon="@drawable/ic_star_white_24dp"
         android:title="@string/menu_acc_summary_hide_selected_title"
         app:showAsAction="always"
         android:visible="false"
+        android:onClick="onOptionsMenuClicked"
         />
     <item android:id="@+id/menu_acc_summary_cancel_selection"
         android:title="@string/menu_acc_summary_cancel_selection_title"
         app:showAsAction="always"
         android:visible="false"
-        android:onClick="onCancelAccSelection"
+        android:onClick="onOptionsMenuClicked"
         android:icon="@drawable/ic_cancel_white_24dp" />
     <item android:id="@+id/menu_acc_summary_confirm_selection"
         android:title="@string/menu_acc_summary_confirm_selection_title"
         app:showAsAction="always"
         android:visible="false"
-        android:onClick="onConfirmAccSelection"
+        android:onClick="onOptionsMenuClicked"
         android:icon="@drawable/ic_check_white_24dp" />
 </menu>
\ No newline at end of file
index 0043337..80232e4 100644 (file)
   -->
 
 <menu xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:android="http://schemas.android.com/apk/res/android">
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
 
     <item
+        tools:context="net.ktnx.mobileledger.ui.activity.MainActivity"
         android:id="@+id/menu_transaction_list_filter"
         android:icon="@drawable/ic_filter_list_white_24dp"
-        android:onClick="onShowFilterClick"
+        android:onClick="onOptionsMenuClicked"
         android:title="Filter"
         app:showAsAction="always" />
 </menu>
\ No newline at end of file
index 8054ff3..de84c0a 100644 (file)
     <!-- These settings headers are only used on tablets. -->
 
     <header
-        android:fragment="net.ktnx.mobileledger.SettingsActivity$BackendPreferenceFragment"
+        android:fragment="net.ktnx.mobileledger.ui.activity.SettingsActivity$BackendPreferenceFragment"
         android:icon="@drawable/ic_info_black_24dp"
         android:title="@string/pref_header_backend" />
     <header
-        android:fragment="net.ktnx.mobileledger.SettingsActivity$InterfacePreferenceFragment"
+        android:fragment="net.ktnx.mobileledger.ui.activity.SettingsActivity$InterfacePreferenceFragment"
         android:icon="@drawable/ic_info_black_24dp"
         android:title="@string/interface_pref_header_title" />
 <!--
     <header
-        android:fragment="net.ktnx.mobileledger.SettingsActivity$NotificationPreferenceFragment"
+        android:fragment="net.ktnx.mobileledger.ui.activity.SettingsActivity$NotificationPreferenceFragment"
         android:icon="@drawable/ic_notifications_black_24dp"
         android:title="@string/pref_header_notifications" />
 
     <header
-        android:fragment="net.ktnx.mobileledger.SettingsActivity$DataSyncPreferenceFragment"
+        android:fragment="net.ktnx.mobileledger.ui.activity.SettingsActivity$DataSyncPreferenceFragment"
         android:icon="@drawable/ic_sync_black_24dp"
         android:title="@string/pref_header_data_sync" />
 -->