]> git.ktnx.net Git - mobile-ledger.git/commitdiff
put account expansion bracket after the name, fix rotation animation
authorDamyan Ivanov <dam+mobileledger@ktnx.net>
Thu, 14 Mar 2019 20:05:02 +0000 (22:05 +0200)
committerDamyan Ivanov <dam+mobileledger@ktnx.net>
Thu, 14 Mar 2019 20:54:57 +0000 (22:54 +0200)
14 files changed:
app/src/main/java/net/ktnx/mobileledger/async/CommitAccountsTask.java
app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java
app/src/main/java/net/ktnx/mobileledger/async/UpdateAccountsTask.java
app/src/main/java/net/ktnx/mobileledger/model/LedgerAccount.java
app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java
app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryAdapter.java
app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java
app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryViewModel.java
app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java
app/src/main/java/net/ktnx/mobileledger/utils/MLDB.java
app/src/main/res/drawable/ic_expand_less_black_24dp.xml [new file with mode: 0644]
app/src/main/res/layout/account_summary_fragment.xml
app/src/main/res/layout/account_summary_row.xml
app/src/main/res/raw/sql_19.sql [new file with mode: 0644]

index f038f3aa30890ff6db423783c70f5617a344674b..7368d712d88ce19eabddc12353d50753b9133372 100644 (file)
@@ -40,12 +40,12 @@ public class CommitAccountsTask
             try {
                 for (LedgerAccount acc : params[0].accountList) {
                     Log.d("CAT", String.format("Setting %s to %s", acc.getName(),
-                            acc.isHiddenToBe() ? "hidden" : "starred"));
+                            acc.isHiddenByStarToBe() ? "hidden" : "starred"));
                     db.execSQL("UPDATE accounts SET hidden=? WHERE profile=? AND name=?",
-                            new Object[]{acc.isHiddenToBe() ? 1 : 0, profile, acc.getName()});
+                            new Object[]{acc.isHiddenByStarToBe() ? 1 : 0, profile, acc.getName()});
 
-                    acc.setHidden(acc.isHiddenToBe());
-                    if (!params[0].showOnlyStarred || !acc.isHidden()) newList.add(acc);
+                    acc.setHiddenByStar(acc.isHiddenByStarToBe());
+                    if (!params[0].showOnlyStarred || !acc.isHiddenByStar()) newList.add(acc);
                 }
                 db.setTransactionSuccessful();
             }
index 47154b0b8744ca66302172db2380a3a337525e46..dfb1f74548a891e04a6c802c26b376be356aef0d 100644 (file)
@@ -169,11 +169,11 @@ public class RetrieveTransactionsTask
                                     acct_name = acct_name.replace("\"", "");
                                     L(String.format("found account: %s", acct_name));
 
-                                    lastAccount = profile.loadAccount(acct_name);
+                                    lastAccount = profile.tryLoadAccount(db, acct_name);
                                     if (lastAccount == null) {
                                         lastAccount = new LedgerAccount(acct_name);
-                                        profile.storeAccount(db, lastAccount);
                                     }
+                                    profile.storeAccount(db, lastAccount);
 
                                     // make sure the parent account(s) are present,
                                     // synthesising them if necessary
@@ -189,8 +189,8 @@ public class RetrieveTransactionsTask
                                         while (!toAppend.isEmpty()) {
                                             String aName = toAppend.pop();
                                             LedgerAccount acc = new LedgerAccount(aName);
-                                            acc.setHidden(lastAccount.isHidden());
-                                            if (!onlyStarred || !acc.isHidden())
+                                            acc.setHiddenByStar(lastAccount.isHiddenByStar());
+                                            if (!onlyStarred || !acc.isHiddenByStar())
                                                 accountList.add(acc);
                                             L(String.format("gap-filling with %s", aName));
                                             accountNames.put(aName, null);
@@ -198,7 +198,7 @@ public class RetrieveTransactionsTask
                                         }
                                     }
 
-                                    if (!onlyStarred || !lastAccount.isHidden())
+                                    if (!onlyStarred || !lastAccount.isHiddenByStar())
                                         accountList.add(lastAccount);
                                     accountNames.put(acct_name, null);
 
index 8d17a548d447cd8d21ff81e7818a09755e4c71d9..ad030ef6a1d44a26d529ad73befcba4443c1eace 100644 (file)
@@ -24,6 +24,7 @@ import android.util.Log;
 
 import net.ktnx.mobileledger.model.Data;
 import net.ktnx.mobileledger.model.LedgerAccount;
+import net.ktnx.mobileledger.model.MobileLedgerProfile;
 import net.ktnx.mobileledger.utils.MLDB;
 
 import java.util.ArrayList;
@@ -32,13 +33,14 @@ public class UpdateAccountsTask extends AsyncTask<Void, Void, ArrayList<LedgerAc
     protected ArrayList<LedgerAccount> doInBackground(Void... params) {
         Data.backgroundTaskCount.incrementAndGet();
         try {
-            String profileUUID = Data.profile.get().getUuid();
+            MobileLedgerProfile profile = Data.profile.get();
+            String profileUUID = profile.getUuid();
             boolean onlyStarred = Data.optShowOnlyStarred.get();
             ArrayList<LedgerAccount> newList = new ArrayList<>();
 
-            String sql = "SELECT name, hidden FROM accounts WHERE profile = ?";
-            if (onlyStarred) sql += " AND hidden = 0";
-            sql += " ORDER BY name";
+            String sql = "SELECT a.name from accounts a WHERE a.profile = ?";
+            if (onlyStarred) sql += " AND a.hidden = 0";
+            sql += " ORDER BY a.name";
 
             SQLiteDatabase db = MLDB.getReadableDatabase();
             try (Cursor cursor = db.rawQuery(sql, new String[]{profileUUID})) {
@@ -46,17 +48,8 @@ public class UpdateAccountsTask extends AsyncTask<Void, Void, ArrayList<LedgerAc
                     final String accName = cursor.getString(0);
 //                    Log.d("accounts",
 //                            String.format("Read account '%s' from DB [%s]", accName, profileUUID));
-                    LedgerAccount acc = new LedgerAccount(accName);
-                    acc.setHidden(cursor.getInt(1) == 1);
-                    try (Cursor c2 = db.rawQuery(
-                            "SELECT value, currency FROM account_values WHERE profile = ? " +
-                            "AND account = ?", new String[]{profileUUID, acc.getName()}))
-                    {
-                        while (c2.moveToNext()) {
-                            acc.addAmount(c2.getFloat(0), c2.getString(1));
-                        }
-                    }
-                    newList.add(acc);
+                    LedgerAccount acc = profile.loadAccount(db, accName);
+                    if (acc.isVisible(newList)) newList.add(acc);
                 }
             }
 
index 6f3b21ac236728f8bfb46569b4a5f80c47455033..93c78de2a13e90b29336346bde0f8dcd610438bd 100644 (file)
@@ -25,40 +25,56 @@ import java.util.regex.Pattern;
 import androidx.annotation.NonNull;
 
 public class LedgerAccount {
+    static Pattern reHigherAccount = Pattern.compile("^[^:]+:");
     private String name;
     private String shortName;
     private int level;
     private String parentName;
-    private boolean hidden;
-    private boolean hiddenToBe;
+    private boolean hiddenByStar;
+    private boolean hiddenByStarToBe;
+    private boolean expanded;
     private List<LedgerAmount> amounts;
-    static Pattern reHigherAccount = Pattern.compile("^[^:]+:");
+    private boolean hasSubAccounts;
 
     public LedgerAccount(String name) {
         this.setName(name);
-        hidden = false;
-    }
-
-    public boolean isHidden() {
-        return hidden;
-    }
-
-    public void setHidden(boolean hidden) {
-        this.hidden = hidden;
+        hiddenByStar = false;
     }
 
     public LedgerAccount(String name, float amount) {
         this.setName(name);
-        this.hidden = false;
+        this.hiddenByStar = false;
+        this.expanded = true;
         this.amounts = new ArrayList<LedgerAmount>();
         this.addAmount(amount);
     }
+    // an account is visible if:
+    //  - it is starred (not hidden by a star)
+    //  - and it has an expanded parent or is a top account
+    public boolean isVisible() {
+        if (hiddenByStar) return false;
 
-    public void setName(String name) {
-        this.name = name;
-        stripName();
-    }
+        if (level == 0) return true;
 
+        return isVisible(Data.accounts.get());
+    }
+    public boolean isVisible(ArrayList<LedgerAccount> list) {
+        for (LedgerAccount acc : list) {
+            if (acc.isParentOf(this)) {
+                if (!acc.isExpanded()) return false;
+            }
+        }
+        return true;
+    }
+    public boolean isParentOf(LedgerAccount potentialChild) {
+        return potentialChild.getName().startsWith(name + ":");
+    }
+    public boolean isHiddenByStar() {
+        return hiddenByStar;
+    }
+    public void setHiddenByStar(boolean hiddenByStar) {
+        this.hiddenByStar = hiddenByStar;
+    }
     private void stripName() {
         level = 0;
         shortName = name;
@@ -76,13 +92,15 @@ public class LedgerAccount {
             parentName = parentBuilder.substring(0, parentBuilder.length() - 1);
         else parentName = null;
     }
-
     public String getName() {
         return name;
     }
-
+    public void setName(String name) {
+        this.name = name;
+        stripName();
+    }
     public void addAmount(float amount, String currency) {
-        if (amounts == null ) amounts = new ArrayList<>();
+        if (amounts == null) amounts = new ArrayList<>();
         amounts.add(new LedgerAmount(amount, currency));
     }
     public void addAmount(float amount) {
@@ -93,7 +111,7 @@ public class LedgerAccount {
         if ((amounts == null) || amounts.isEmpty()) return "";
 
         StringBuilder builder = new StringBuilder();
-        for( LedgerAmount amount : amounts ) {
+        for (LedgerAmount amount : amounts) {
             String amt = amount.toString();
             if (builder.length() > 0) builder.append('\n');
             builder.append(amt);
@@ -115,16 +133,31 @@ public class LedgerAccount {
         return parentName;
     }
     public void togglehidden() {
-        hidden = !hidden;
+        hiddenByStar = !hiddenByStar;
     }
 
-    public boolean isHiddenToBe() {
-        return hiddenToBe;
+    public boolean isHiddenByStarToBe() {
+        return hiddenByStarToBe;
     }
-    public void setHiddenToBe(boolean hiddenToBe) {
-        this.hiddenToBe = hiddenToBe;
+    public void setHiddenByStarToBe(boolean hiddenByStarToBe) {
+        this.hiddenByStarToBe = hiddenByStarToBe;
     }
     public void toggleHiddenToBe() {
-        setHiddenToBe(!hiddenToBe);
+        setHiddenByStarToBe(!hiddenByStarToBe);
+    }
+    public boolean hasSubAccounts() {
+        return hasSubAccounts;
+    }
+    public void setHasSubAccounts(boolean hasSubAccounts) {
+        this.hasSubAccounts = hasSubAccounts;
+    }
+    public boolean isExpanded() {
+        return expanded;
+    }
+    public void setExpanded(boolean expanded) {
+        this.expanded = expanded;
+    }
+    public void toggleExpanded() {
+        expanded = !expanded;
     }
 }
index ec204d9a47272042ba3b15b5815fb0e9aba7eeaa..17b6ac7ae9b8128007c95fefd93a17ab0f978a42 100644 (file)
@@ -29,6 +29,9 @@ import java.util.Date;
 import java.util.List;
 import java.util.UUID;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 public final class MobileLedgerProfile {
     private String uuid;
     private String name;
@@ -185,12 +188,16 @@ public final class MobileLedgerProfile {
     public void storeAccount(SQLiteDatabase db, LedgerAccount acc) {
         // replace into is a bad idea because it would reset hidden to its default value
         // we like the default, but for new accounts only
-        db.execSQL("update accounts set level = ?, keep = 1 where profile=? and name = ?",
-                new Object[]{acc.getLevel(), uuid, acc.getName()});
-        db.execSQL("insert into accounts(profile, name, name_upper, parent_name, level, keep) " +
-                   "select ?,?,?,?,?,1 where (select changes() = 0)",
+        db.execSQL("update accounts set level = ?, keep = 1, hidden=?, expanded=? " +
+                   "where profile=? and name = ?",
+                new Object[]{acc.getLevel(), acc.isHiddenByStar(), acc.isExpanded(), uuid,
+                             acc.getName()
+                });
+        db.execSQL(
+                "insert into accounts(profile, name, name_upper, parent_name, level, hidden, expanded, keep) " +
+                "select ?,?,?,?,?,?,?,1 where (select changes() = 0)",
                 new Object[]{uuid, acc.getName(), acc.getName().toUpperCase(), acc.getParentName(),
-                             acc.getLevel()
+                             acc.getLevel(), acc.isHiddenByStar(), acc.isExpanded()
                 });
 //        Log.d("accounts", String.format("Stored account '%s' in DB [%s]", acc.getName(), uuid));
     }
@@ -278,20 +285,50 @@ public final class MobileLedgerProfile {
         Log.d("db", String.format("removing progile %s from DB", uuid));
         db.execSQL("delete from profiles where uuid=?", new Object[]{uuid});
     }
+    @NonNull
     public LedgerAccount loadAccount(String name) {
         SQLiteDatabase db = MLDB.getReadableDatabase();
-        try (Cursor cursor = db.rawQuery("SELECT hidden from accounts where profile=? and name=?",
-                new String[]{uuid, name}))
+        return loadAccount(db, name);
+    }
+    @Nullable
+    public LedgerAccount tryLoadAccount(String acct_name) {
+        SQLiteDatabase db = MLDB.getReadableDatabase();
+        return loadAccount(acct_name);
+    }
+    @NonNull
+    public LedgerAccount loadAccount(SQLiteDatabase db, String accName) {
+        LedgerAccount acc = tryLoadAccount(db, accName);
+
+        if (acc == null) throw new RuntimeException("Unable to load account with name "+accName);
+
+        return acc;
+    }
+    @Nullable
+    public LedgerAccount tryLoadAccount(SQLiteDatabase db, String accName) {
+        try (Cursor cursor = db.rawQuery(
+                "SELECT a.hidden, a.expanded, (select 1 from accounts a2 " +
+                "where a2.profile = a.profile and a2.name like a.name||':%' limit 1) " +
+                "FROM accounts a WHERE a.profile = ? and a.name=?", new String[]{uuid, accName}))
         {
             if (cursor.moveToFirst()) {
-                LedgerAccount acc = new LedgerAccount(name);
-                acc.setHidden(cursor.getInt(0) == 1);
+                LedgerAccount acc = new LedgerAccount(accName);
+                acc.setHiddenByStar(cursor.getInt(0) == 1);
+                acc.setExpanded(cursor.getInt(1) == 1);
+                acc.setHasSubAccounts(cursor.getInt(2) == 1);
+
+                try (Cursor c2 = db.rawQuery(
+                        "SELECT value, currency FROM account_values WHERE profile = ? " +
+                        "AND account = ?", new String[]{uuid, accName}))
+                {
+                    while (c2.moveToNext()) {
+                        acc.addAmount(c2.getFloat(0), c2.getString(1));
+                    }
+                }
 
                 return acc;
             }
+            return null;
         }
-
-        return null;
     }
     public LedgerTransaction loadTransaction(int transactionId) {
         LedgerTransaction tr = new LedgerTransaction(transactionId, this.uuid);
@@ -344,4 +381,40 @@ public final class MobileLedgerProfile {
         setLongOption(MLDB.OPT_LAST_SCRAPE, now.getTime());
         Data.lastUpdateDate.set(now);
     }
+    public List<LedgerAccount> loadChildAccountsOf(LedgerAccount acc) {
+        List<LedgerAccount> result = new ArrayList<>();
+        SQLiteDatabase db = MLDB.getReadableDatabase();
+        try (Cursor c = db.rawQuery(
+                "SELECT a.name FROM accounts a WHERE a.profile = ? and a.name like ?||':%'",
+                new String[]{uuid, acc.getName()}))
+        {
+            while (c.moveToNext()) {
+                LedgerAccount a = loadAccount(db, c.getString(0));
+                result.add(a);
+            }
+        }
+
+        return result;
+    }
+    public List<LedgerAccount> loadVisibleChildAccountsOf(LedgerAccount acc) {
+        List<LedgerAccount> result = new ArrayList<>();
+        ArrayList<LedgerAccount> visibleList = new ArrayList<>();
+        visibleList.add(acc);
+
+        SQLiteDatabase db = MLDB.getReadableDatabase();
+        try (Cursor c = db.rawQuery(
+                "SELECT a.name FROM accounts a WHERE a.profile = ? and a.name like ?||':%'",
+                new String[]{uuid, acc.getName()}))
+        {
+            while (c.moveToNext()) {
+                LedgerAccount a = loadAccount(db, c.getString(0));
+                if (a.isVisible(visibleList)) {
+                    result.add(a);
+                    visibleList.add(a);
+                }
+            }
+        }
+
+        return result;
+    }
 }
index 086f0cb1cb34b3983ea79657462b50dce1e7ba6f..d9df87d5741bf77d75923f3b3b07af6081afee74 100644 (file)
@@ -24,20 +24,22 @@ import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.CheckBox;
-import android.widget.LinearLayout;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
 import android.widget.TextView;
 
 import net.ktnx.mobileledger.R;
 import net.ktnx.mobileledger.model.Data;
 import net.ktnx.mobileledger.model.LedgerAccount;
-import net.ktnx.mobileledger.utils.Colors;
 
 import java.util.List;
 
 import androidx.annotation.NonNull;
+import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.recyclerview.widget.RecyclerView;
 
-class AccountSummaryAdapter extends RecyclerView.Adapter<AccountSummaryAdapter.LedgerRowHolder> {
+public class AccountSummaryAdapter
+        extends RecyclerView.Adapter<AccountSummaryAdapter.LedgerRowHolder> {
     private boolean selectionActive;
 
     AccountSummaryAdapter() {
@@ -51,15 +53,20 @@ class AccountSummaryAdapter extends RecyclerView.Adapter<AccountSummaryAdapter.L
             Context ctx = holder.row.getContext();
             Resources rm = ctx.getResources();
 
+            holder.row.setTag(acc);
             holder.row.setVisibility(View.VISIBLE);
             holder.vTrailer.setVisibility(View.GONE);
             holder.tvAccountName.setText(acc.getShortName());
-            holder.tvAccountName.setPadding(
-                    acc.getLevel() * rm.getDimensionPixelSize(R.dimen.activity_horizontal_margin) /
-                    2, 0, 0, 0);
+            ConstraintLayout.LayoutParams lp =
+                    (ConstraintLayout.LayoutParams) holder.tvAccountName.getLayoutParams();
+            lp.setMarginStart(
+                    acc.getLevel() * rm.getDimensionPixelSize(R.dimen.thumb_row_height) / 2);
+            holder.expanderContainer
+                    .setVisibility(acc.hasSubAccounts() ? View.VISIBLE : View.INVISIBLE);
+            holder.expanderContainer.setRotation(acc.isExpanded() ? 0 : 180);
             holder.tvAccountAmounts.setText(acc.getAmountsString());
 
-            if (acc.isHidden()) {
+            if (acc.isHiddenByStar()) {
                 holder.tvAccountName.setTypeface(null, Typeface.ITALIC);
                 holder.tvAccountAmounts.setTypeface(null, Typeface.ITALIC);
             }
@@ -68,15 +75,8 @@ class AccountSummaryAdapter extends RecyclerView.Adapter<AccountSummaryAdapter.L
                 holder.tvAccountAmounts.setTypeface(null, Typeface.NORMAL);
             }
 
-            if (position % 2 == 0) {
-                holder.row.setBackgroundColor(Colors.tableRowDarkBG);
-            }
-            else {
-                holder.row.setBackgroundColor(Colors.tableRowLightBG);
-            }
-
             holder.selectionCb.setVisibility(selectionActive ? View.VISIBLE : View.GONE);
-            holder.selectionCb.setChecked(!acc.isHiddenToBe());
+            holder.selectionCb.setChecked(!acc.isHiddenByStarToBe());
 
             holder.row.setTag(R.id.POS, position);
         }
@@ -99,7 +99,7 @@ class AccountSummaryAdapter extends RecyclerView.Adapter<AccountSummaryAdapter.L
         return Data.accounts.get().size() + 1;
     }
     public void startSelection() {
-        for (LedgerAccount acc : Data.accounts.get()) acc.setHiddenToBe(acc.isHidden());
+        for (LedgerAccount acc : Data.accounts.get()) acc.setHiddenByStarToBe(acc.isHiddenByStar());
         this.selectionActive = true;
         notifyDataSetChanged();
     }
@@ -116,14 +116,14 @@ class AccountSummaryAdapter extends RecyclerView.Adapter<AccountSummaryAdapter.L
     public void selectItem(int position) {
         LedgerAccount acc = Data.accounts.get().get(position);
         acc.toggleHiddenToBe();
-        toggleChildrenOf(acc, acc.isHiddenToBe(), position);
+        toggleChildrenOf(acc, acc.isHiddenByStarToBe(), position);
         notifyItemChanged(position);
     }
     void toggleChildrenOf(LedgerAccount parent, boolean hiddenToBe, int parentPosition) {
         int i = parentPosition + 1;
         for (LedgerAccount acc : Data.accounts.get()) {
             if (acc.getName().startsWith(parent.getName() + ":")) {
-                acc.setHiddenToBe(hiddenToBe);
+                acc.setHiddenByStarToBe(hiddenToBe);
                 notifyItemChanged(i);
                 toggleChildrenOf(acc, hiddenToBe, i);
                 i++;
@@ -134,8 +134,10 @@ class AccountSummaryAdapter extends RecyclerView.Adapter<AccountSummaryAdapter.L
     class LedgerRowHolder extends RecyclerView.ViewHolder {
         CheckBox selectionCb;
         TextView tvAccountName, tvAccountAmounts;
-        LinearLayout row;
+        ConstraintLayout row;
         View vTrailer;
+        FrameLayout expanderContainer;
+        ImageView expander;
         public LedgerRowHolder(@NonNull View itemView) {
             super(itemView);
             this.row = itemView.findViewById(R.id.account_summary_row);
@@ -143,6 +145,19 @@ class AccountSummaryAdapter extends RecyclerView.Adapter<AccountSummaryAdapter.L
             this.tvAccountAmounts = itemView.findViewById(R.id.account_row_acc_amounts);
             this.selectionCb = itemView.findViewById(R.id.account_row_check);
             this.vTrailer = itemView.findViewById(R.id.account_summary_trailer);
+            this.expanderContainer = itemView.findViewById(R.id.account_expander_container);
+            this.expander = itemView.findViewById(R.id.account_expander);
+
+            expanderContainer.addOnLayoutChangeListener(
+                    (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                        int w = right - left;
+                        int h = bottom - top;
+                        if (h > w) {
+                            int p = (h - w) / 2;
+                            v.setPadding(0, p, 0, p);
+                        }
+                        else v.setPadding(0, 0, 0, 0);
+                    });
         }
     }
 }
index dd4e4d49ae37561288fd02d00197f9f702c25f11..5fd47f14c322cc9b8067c6c731081c68492489a7 100644 (file)
@@ -33,17 +33,15 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
 
 import net.ktnx.mobileledger.R;
 import net.ktnx.mobileledger.model.Data;
-import net.ktnx.mobileledger.model.LedgerAccount;
 import net.ktnx.mobileledger.ui.MobileLedgerListFragment;
-import net.ktnx.mobileledger.ui.RecyclerItemListener;
 import net.ktnx.mobileledger.ui.activity.MainActivity;
 import net.ktnx.mobileledger.utils.Colors;
 
-import java.util.List;
 import java.util.Observer;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.DividerItemDecoration;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -52,7 +50,7 @@ import static net.ktnx.mobileledger.ui.activity.SettingsActivity.PREF_KEY_SHOW_O
 public class AccountSummaryFragment extends MobileLedgerListFragment {
 
     MenuItem mShowOnlyStarred;
-    private AccountSummaryAdapter modelAdapter;
+    public AccountSummaryAdapter modelAdapter;
     private Menu optMenu;
     private FloatingActionButton fab;
     private Observer backgroundTaskCountObserver;
@@ -101,48 +99,51 @@ public class AccountSummaryFragment extends MobileLedgerListFragment {
 
         modelAdapter = new AccountSummaryAdapter();
 
+        mActivity.mAccountSummaryFragment = this;
         root = mActivity.findViewById(R.id.account_root);
         LinearLayoutManager llm = new LinearLayoutManager(mActivity);
         llm.setOrientation(RecyclerView.VERTICAL);
         root.setLayoutManager(llm);
         root.setAdapter(modelAdapter);
+        DividerItemDecoration did = new DividerItemDecoration(mActivity, DividerItemDecoration.VERTICAL);
+        root.addItemDecoration(did);
 
         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("value", String.format("item %d clicked", position));
-                        if (modelAdapter.isSelectionActive()) {
-                            modelAdapter.selectItem(position);
-                        }
-                        else {
-                            List<LedgerAccount> accounts = Data.accounts.get();
-                            if (accounts != null) {
-                                LedgerAccount account = accounts.get(position);
-
-                                mActivity.showAccountTransactions(account);
-                            }
-                        }
-                    }
-
-                    @Override
-                    public void onLongClickItem(View v, int position) {
-                        Log.d("value", 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.addOnItemTouchListener(new RecyclerItemListener(mActivity, root,
+//                new RecyclerItemListener.RecyclerTouchListener() {
+//                    @Override
+//                    public void onClickItem(View v, int position) {
+//                        Log.d("value", String.format("item %d clicked", position));
+//                        if (modelAdapter.isSelectionActive()) {
+//                            modelAdapter.selectItem(position);
+//                        }
+//                        else {
+//                            List<LedgerAccount> accounts = Data.accounts.get();
+//                            if (accounts != null) {
+//                                LedgerAccount account = accounts.get(position);
+//
+//                                mActivity.showAccountTransactions(account);
+//                            }
+//                        }
+//                    }
+//
+//                    @Override
+//                    public void onLongClickItem(View v, int position) {
+//                        Log.d("value", 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();
+//                        }
+//                    }
+//                }));
 
         mActivity.fabShouldShow();
         root.addOnScrollListener(new RecyclerView.OnScrollListener() {
@@ -155,8 +156,7 @@ public class AccountSummaryFragment extends MobileLedgerListFragment {
             }
         });
         swiper = mActivity.findViewById(R.id.account_swiper);
-        Colors.themeWatch.addObserver(
-                (o, arg) -> swiper.setColorSchemeColors(Colors.primary));
+        Colors.themeWatch.addObserver((o, arg) -> swiper.setColorSchemeColors(Colors.primary));
         swiper.setColorSchemeColors(Colors.primary);
         swiper.setOnRefreshListener(() -> {
             Log.d("ui", "refreshing accounts via swipe");
index 4d210534c5ce1bc2ba0faec6e17269d76b9685c9..7a7164f3102e55535023cb1a2dd137101cca5a4b 100644 (file)
@@ -31,13 +31,13 @@ import java.util.ArrayList;
 
 import androidx.lifecycle.ViewModel;
 
-class AccountSummaryViewModel extends ViewModel {
+public class AccountSummaryViewModel extends ViewModel {
     static void commitSelections(Context context) {
         CAT task = new CAT();
         task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
                 new CommitAccountsTaskParams(Data.accounts.get(), Data.optShowOnlyStarred.get()));
     }
-    static void scheduleAccountListReload() {
+    static public void scheduleAccountListReload() {
         if (Data.profile.get() == null) return;
 
         UAT task = new UAT();
index 4202a84cc437d9f074ceaa2c42073038eb1ce483..877740731df959a7c7a5114c8740bda69e41d888 100644 (file)
@@ -27,6 +27,7 @@ import android.os.Bundle;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 import android.widget.LinearLayout;
@@ -52,7 +53,9 @@ import net.ktnx.mobileledger.utils.MLDB;
 
 import java.lang.ref.WeakReference;
 import java.text.DateFormat;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 import java.util.Observable;
 import java.util.Observer;
 
@@ -70,6 +73,7 @@ import androidx.viewpager.widget.ViewPager;
 public class MainActivity extends ProfileThemedActivity {
     private static final String STATE_CURRENT_PAGE = "current_page";
     private static final String BUNDLE_SAVED_STATE = "bundle_savedState";
+    public AccountSummaryFragment mAccountSummaryFragment;
     DrawerLayout drawer;
     private LinearLayout profileListContainer;
     private View profileListHeadArrow, profileListHeadMore, profileListHeadCancel;
@@ -86,7 +90,6 @@ public class MainActivity extends ProfileThemedActivity {
     private boolean profileModificationEnabled = false;
     private boolean profileListExpanded = false;
     private ProfilesRecyclerViewAdapter mProfileListAdapter;
-
     @Override
     protected void onStart() {
         super.onStart();
@@ -551,6 +554,68 @@ public class MainActivity extends ProfileThemedActivity {
         // FIXME disable rearranging
 
     }
+    public void onAccountSummaryRowViewClicked(View view) {
+        ViewGroup row = (ViewGroup) view.getParent();
+        if (view.getId() == R.id.account_expander_container) {
+            Log.d("accounts", "Account expander clicked");
+            LedgerAccount acc = (LedgerAccount) row.getTag();
+            if (!acc.hasSubAccounts()) return;
+
+            boolean wasExpanded = acc.isExpanded();
+
+            view.clearAnimation();
+            ViewPropertyAnimator animator = view.animate();
+
+            acc.toggleExpanded();
+            Data.profile.get().storeAccount(MLDB.getWritableDatabase(), acc);
+
+            if (wasExpanded) {
+                Log.d("accounts", String.format("Collapsing account '%s'", acc.getName()));
+                animator.rotationBy(180);
+
+                // removing all child accounts from the view
+                int start = -1, count = 0;
+                int i = 0;
+                final ArrayList<LedgerAccount> accountList = Data.accounts.get();
+                for (LedgerAccount a : accountList) {
+                    if (acc.isParentOf(a)) {
+                        if (start == -1) {
+                            start = i;
+                        }
+                        count++;
+                    }
+                    else {
+                        if (start != -1) {
+                            break;
+                        }
+                    }
+                    i++;
+                }
+
+                if (start != -1) {
+                    for (int j = 0; j < count; j++) {
+                        Log.d("accounts", String.format("Removing item %d: %s", start + j,
+                                accountList.get(start).getName()));
+                        accountList.remove(start);
+                    }
+
+                    mAccountSummaryFragment.modelAdapter.notifyItemRangeRemoved(start, count);
+                }
+            }
+            else {
+                Log.d("accounts", String.format("Expanding account '%s'", acc.getName()));
+                animator.rotationBy(-180);
+                ArrayList<LedgerAccount> accounts = Data.accounts.get();
+                List<LedgerAccount> children = Data.profile.get().loadVisibleChildAccountsOf(acc);
+                int parentPos = accounts.indexOf(acc);
+                if (parentPos == -1) throw new RuntimeException(
+                        "Can't find index of clicked account " + acc.getName());
+                accounts.addAll(parentPos + 1, children);
+                mAccountSummaryFragment.modelAdapter
+                        .notifyItemRangeInserted(parentPos + 1, children.size());
+            }
+        }
+    }
 
     public class SectionsPagerAdapter extends FragmentPagerAdapter {
 
@@ -563,7 +628,8 @@ public class MainActivity extends ProfileThemedActivity {
             Log.d("main", String.format("Switching to fragment %d", position));
             switch (position) {
                 case 0:
-                    return new AccountSummaryFragment();
+//                    Log.d("flow", "Creating account summary fragment");
+                    return mAccountSummaryFragment = new AccountSummaryFragment();
                 case 1:
                     return new TransactionListFragment();
                 default:
@@ -577,5 +643,4 @@ public class MainActivity extends ProfileThemedActivity {
             return 2;
         }
     }
-
 }
index 328d8c978aa04cac0d29c12143cd74e6e71f523f..7da81f979063e0f9c458a07b4d9ca8604ac9c5c4 100644 (file)
@@ -222,7 +222,7 @@ public final class MLDB {
 
 class MobileLedgerDatabase extends SQLiteOpenHelper implements AutoCloseable {
     public static final String DB_NAME = "MoLe.db";
-    public static final int LATEST_REVISION = 18;
+    public static final int LATEST_REVISION = 19;
 
     private final Application mContext;
 
diff --git a/app/src/main/res/drawable/ic_expand_less_black_24dp.xml b/app/src/main/res/drawable/ic_expand_less_black_24dp.xml
new file mode 100644 (file)
index 0000000..14e4ec1
--- /dev/null
@@ -0,0 +1,23 @@
+<!--
+  ~ Copyright Google Inc.
+  ~
+  ~ Licensed under the Apache License, version 2.0 ("the License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the license at:
+  ~
+  ~ https://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distribution under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  ~ Modified/adapted by Damyan Ivanov for MoLe
+  -->
+
+<vector android:height="24dp" android:tint="?colorAccent"
+    android:viewportHeight="24.0" android:viewportWidth="24.0"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FF000000" android:pathData="M12,8l-6,6 1.41,1.41L12,10.83l4.59,4.58L18,14z"/>
+</vector>
index 4b1e9269e9b0d33b7ed0d7669b8e6ff935caace1..441c60c95d5e06f1bf4d73beab9754cc6b621272 100644 (file)
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
   ~ Copyright © 2019 Damyan Ivanov.
   ~ This file is part of MoLe.
   ~ MoLe is free software: you can distribute it and/or modify it
                 android:id="@+id/account_root"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:scrollbars="vertical"
                 android:choiceMode="multipleChoice"
                 android:drawSelectorOnTop="true"
-                android:orientation="vertical">
+                android:orientation="vertical"
+                android:scrollbars="vertical">
 
             </androidx.recyclerview.widget.RecyclerView>
         </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
index ff5a5ec1201569bcc6429f8d7c2e6203d946d1a0..2ff3b4e6621b25d77d28f4c92f1e1fcf41f67ec3 100644 (file)
 <androidx.constraintlayout.widget.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/account_summary_row"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    tools:showIn="@id/account_root">
+    android:layout_height="wrap_content">
 
-    <LinearLayout
-        android:id="@+id/account_summary_row"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
+    <CheckBox
+        android:id="@+id/account_row_check"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:button="@drawable/checkbox_star_black"
+        android:onClick="onAccountSummaryRowViewClicked"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/account_row_acc_name"
+        style="@style/account_summary_account_name"
+        android:layout_width="wrap_content"
+        android:layout_height="0dp"
+        android:layout_weight="1"
         android:gravity="center_vertical"
-        android:minHeight="?attr/android:actionBarSize"
-        android:orientation="horizontal"
+        android:onClick="onAccountSummaryRowViewClicked"
         android:paddingStart="8dp"
-        android:paddingEnd="8dp"
+        android:text="Account name, a really long one. A very very very long one. It may even spawn on more than two lines -- three, four or more."
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintStart_toEndOf="@id/account_row_check"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:ignore="HardcodedText" />
+
+    <FrameLayout
+        android:id="@+id/account_expander_container"
+        android:layout_width="@dimen/thumb_row_height"
+        android:layout_height="@dimen/thumb_row_height"
+        android:foregroundGravity="center_vertical"
+        android:minHeight="@dimen/thumb_row_height"
+        android:onClick="onAccountSummaryRowViewClicked"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toEndOf="@id/account_row_acc_name"
         app:layout_constraintTop_toTopOf="parent">
 
-        <CheckBox
-            android:id="@+id/account_row_check"
-            android:layout_width="wrap_content"
+        <ImageView
+            android:id="@+id/account_expander"
+            android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:button="@drawable/checkbox_star_black" />
+            android:layout_margin="8dp"
+            android:background="@drawable/ic_expand_less_black_24dp" />
+    </FrameLayout>
 
-        <TextView
-            android:id="@+id/account_row_acc_name"
-            style="@style/account_summary_account_name"
-            android:text="Account name, a really long one. A very very very long one. It may even spawn on more than two lines -- three, four or more."
-            tools:ignore="HardcodedText" />
+    <TextView
+        android:id="@+id/account_row_filler1"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/account_row_acc_amounts"
+        app:layout_constraintStart_toEndOf="@id/account_expander_container"
+        app:layout_constraintTop_toTopOf="parent" />
 
-        <TextView
-            android:id="@+id/account_row_acc_amounts"
-            style="@style/account_summary_amounts"
-            android:text="123,45\n678,90"
-            tools:ignore="HardcodedText" />
-    </LinearLayout>
+    <TextView
+        android:id="@+id/account_row_acc_amounts"
+        style="@style/account_summary_amounts"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
+        android:layout_weight="0"
+        android:gravity="center_vertical"
+        android:onClick="onAccountSummaryRowViewClicked"
+        android:text="123,45\n678,90"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:ignore="HardcodedText" />
 
     <View
         android:id="@+id/account_summary_trailer"
diff --git a/app/src/main/res/raw/sql_19.sql b/app/src/main/res/raw/sql_19.sql
new file mode 100644 (file)
index 0000000..c2bdbaf
--- /dev/null
@@ -0,0 +1,2 @@
+alter table accounts add expanded default 1;
+update accounts set expanded = 1;
\ No newline at end of file