implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+ implementation 'android.arch.lifecycle:extensions:1.1.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+++ /dev/null
-package net.ktnx.mobileledger;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.LinearLayout;
-
-class AccountRowLayout extends LinearLayout {
- private String accountName;
-
- public
- AccountRowLayout(Context context, String accountName) {
- super(context);
- this.accountName = accountName;
- }
-
- public
- AccountRowLayout(Context context, AttributeSet attrs, String accountName) {
- super(context, attrs);
- this.accountName = accountName;
- }
-
- public
- AccountRowLayout(Context context, AttributeSet attrs, int defStyleAttr, String accountName) {
- super(context, attrs, defStyleAttr);
- this.accountName = accountName;
- }
-
- public
- AccountRowLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
- String accountName) {
- super(context, attrs, defStyleAttr, defStyleRes);
- this.accountName = accountName;
- }
-
- public
- String getAccountName() {
- return accountName;
- }
-}
package net.ktnx.mobileledger;
-import android.annotation.SuppressLint;
+import android.arch.lifecycle.ViewModelProviders;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
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.util.TypedValue;
-import android.view.ContextMenu;
-import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
-import android.view.ViewGroup;
-import android.widget.EditText;
import android.widget.LinearLayout;
-import android.widget.ProgressBar;
-import android.widget.TextView;
+import java.lang.ref.WeakReference;
import java.util.Date;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static android.view.View.GONE;
+import java.util.List;
public class AccountSummary extends AppCompatActivity {
DrawerLayout drawer;
private static boolean account_list_needs_update = true;
MenuItem mShowHiddenAccounts;
SharedPreferences.OnSharedPreferenceChangeListener sBindPreferenceSummaryToValueListener;
- private AccountRowLayout clickedAccountRow;
private MobileLedgerDatabase dbh;
+ private AccountSummaryViewModel model;
+ private AccountSummaryAdapter modelAdapter;
+ private Menu optMenu;
public static void preferences_changed() {
account_list_needs_update = true;
}
- MenuItem mRefresh;
@Override
protected void onCreate(Bundle savedInstanceState) {
e.printStackTrace();
}
+ model = ViewModelProviders.of(this).get(AccountSummaryViewModel.class);
+ List<LedgerAccount> accounts = model.getAccounts();
+ 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_hide_selected).setVisible(true);
+ }
+ }
+ }));
+
+ ((SwipeRefreshLayout) findViewById(R.id.account_swiper)).setOnRefreshListener(() -> {
+ Log.d("ui", "refreshing accounts via swipe");
+ update_accounts(true);
+ });
prepare_db();
- update_account_table();
+// update_account_table();
update_accounts(false);
}
}
public void nav_exit_clicked(View view) {
- Log.w("mobileledger", "exiting");
+ Log.w("app", "exiting");
finish();
}
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);
- mRefresh = menu.findItem(R.id.menu_acc_summary_refresh);
- if (mRefresh == null) throw new AssertionError();
+ optMenu = menu;
mShowHiddenAccounts = menu.findItem(R.id.menu_acc_summary_show_hidden);
if (mShowHiddenAccounts == null) throw new AssertionError();
- sBindPreferenceSummaryToValueListener =
- new SharedPreferences.OnSharedPreferenceChangeListener() {
- @Override
- public
- void onSharedPreferenceChanged(SharedPreferences preference, String value) {
- mShowHiddenAccounts
- .setChecked(preference.getBoolean("show_hidden_accounts", false));
- }
- };
+ sBindPreferenceSummaryToValueListener = (preference, value) -> mShowHiddenAccounts
+ .setChecked(preference.getBoolean("show_hidden_accounts", false));
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
pref.registerOnSharedPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
}
private void update_accounts() {
- if (mRefresh != null) mRefresh.setVisible(false);
- Resources rm = getResources();
-
- ProgressBar pb = findViewById(R.id.progressBar);
- pb.setVisibility(View.VISIBLE);
- TextView pt = findViewById(R.id.textProgress);
- pt.setVisibility(View.VISIBLE);
- pb.setIndeterminate(true);
-
- RetrieveAccountsTask task = new RetrieveAccountsTask() {
- @Override
- protected void onProgressUpdate(Integer... values) {
- if ( values[0] == 0 )
- pt.setText(R.string.progress_connecting);
- else
- pt.setText(String.format(getResources().getString(R.string.progress_N_accounts_loaded), values[0]));
- }
-
- @Override
- protected void onPostExecute(Void result) {
- pb.setVisibility(GONE);
- pt.setVisibility(GONE);
- if (mRefresh != null) mRefresh.setVisible(true);
- if (this.error != 0) {
- String err_text = rm.getString(this.error);
- Log.d("visual", String.format("showing snackbar: %s", err_text));
- Snackbar.make(drawer, err_text, Snackbar.LENGTH_LONG ).show();
- }
- else {
- dbh.set_option_value("last_refresh", new Date().getTime() );
- update_account_table();
- }
- }
- };
+ RetrieveAccountsTask task = new RetrieveAccountsTask(new WeakReference<>(this));
task.setPref(PreferenceManager.getDefaultSharedPreferences(this));
task.execute();
}
-
- public int dp2px(float dp) {
- return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics()));
- }
-
- Pattern higher_account = Pattern.compile("^[^:]+:");
-
- private String strip_higher_accounts(String acc_name, int[] count) {
- count[0] = 0;
- while (true) {
- Matcher m = higher_account.matcher(acc_name);
- if (m.find()) {
- count[0]++;
- acc_name = m.replaceFirst("");
- }
- else break;
+ 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();
}
-
- return acc_name;
- }
-
- public void hideAccountClicked(MenuItem item) {
- try(SQLiteDatabase db = dbh.getWritableDatabase()) {
- db.execSQL("update accounts set hidden=1 where name=?", new Object[]{clickedAccountRow.getAccountName()});
+ else {
+ dbh.set_option_value("last_refresh", new Date().getTime() );
+ update_account_table();
}
- update_account_table();
}
-
- @SuppressLint("DefaultLocale")
private void update_account_table() {
- LinearLayout root = findViewById(R.id.account_root);
- root.removeAllViewsInLayout();
-
- View.OnCreateContextMenuListener ccml = new View.OnCreateContextMenuListener() {
- @Override
- public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
- clickedAccountRow = (AccountRowLayout) v;
- getMenuInflater().inflate(R.menu.account_summary_account_menu, menu);
- }
- };
-
- int rowHeight =
- (int) (getTheme().obtainStyledAttributes(new int[]{android.R.attr.actionBarSize})
- .getDimensionPixelSize(0, dp2px(56)) * 0.75);
-
- boolean showingHiddenAccounts = PreferenceManager.getDefaultSharedPreferences(this)
- .getBoolean("show_hidden_accounts", false);
- Log.d("pref", "show_hidden_accounts is " + (showingHiddenAccounts ? "true" : "false"));
-
- try(SQLiteDatabase db = dbh.getReadableDatabase()) {
- try (Cursor cursor = db
- .rawQuery("SELECT name, hidden FROM accounts ORDER BY name;", null))
- {
- boolean even = false;
- String skippingAccountName = null;
- while (cursor.moveToNext()) {
- String acc_name = cursor.getString(0);
- if (skippingAccountName != null) {
- if (acc_name.startsWith(skippingAccountName + ":")) continue;
-
- skippingAccountName = null;
- }
-
- boolean is_hidden = cursor.getInt(1) == 1;
-
- if (!showingHiddenAccounts && is_hidden) {
- skippingAccountName = acc_name;
- continue;
- }
-
- LinearLayout r = new AccountRowLayout(this, acc_name);
- r.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
- r.setGravity(Gravity.CENTER_VERTICAL);
- r.setPadding(getResources().getDimensionPixelSize(R.dimen.activity_horizontal_margin), dp2px(3),
- getResources().getDimensionPixelSize(R.dimen.activity_horizontal_margin),
- dp2px(4));
- r.setMinimumHeight(rowHeight);
-
- if (even) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- r.setBackgroundColor(
- getResources().getColor(R.color.table_row_even_bg, getTheme()));
- }
- else {
- r.setBackgroundColor(getResources().getColor(R.color.table_row_even_bg));
- }
- }
- even = !even;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- r.setContextClickable(true);
- }
- r.setOnCreateContextMenuListener(ccml);
-
-
- TextView acc_tv = new TextView(this, null, R.style.account_summary_account_name);
- acc_tv.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.MATCH_PARENT, 5f));
- acc_tv.setGravity(Gravity.CENTER_VERTICAL);
- int[] indent_level = new int[]{0};
- String short_acc_name = strip_higher_accounts(acc_name, indent_level);
- acc_tv.setPadding(indent_level[0] * getResources().getDimensionPixelSize(R.dimen.activity_horizontal_margin) / 2, 0, 0,
- 0);
- acc_tv.setText(short_acc_name);
- if (is_hidden) acc_tv.setTypeface(null, Typeface.ITALIC);
- r.addView(acc_tv);
-
- TextView amt_tv = new TextView(this, null, R.style.account_summary_amounts);
- amt_tv.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.MATCH_PARENT, 1f));
- amt_tv.setTextAlignment(EditText.TEXT_ALIGNMENT_VIEW_END);
- amt_tv.setGravity(Gravity.CENTER_VERTICAL);
-// amt_tv.setGravity(Gravity.CENTER);
- amt_tv.setMinWidth(dp2px(60f));
- StringBuilder amt_text = new StringBuilder();
- try (Cursor cAmounts = db.rawQuery(
- "SELECT currency, value FROM account_values WHERE account = ?", new String[]{acc_name}))
- {
- while (cAmounts.moveToNext()) {
- String curr = cAmounts.getString(0);
- Float amt = cAmounts.getFloat(1);
- if (amt_text.length() != 0) amt_text.append('\n');
- amt_text.append(String.format("%s %,1.2f", curr, amt));
- }
- }
- amt_tv.setText(amt_text.toString());
- if (is_hidden) amt_tv.setTypeface(null, Typeface.ITALIC);
-
- r.addView(amt_tv);
-
- root.addView(r);
- }
- }
+ model.reloadAccounts();
+ modelAdapter.notifyDataSetChanged();
+ }
+ public void onCancelAccSelection(MenuItem item) {
+ modelAdapter.stopSelection();
+ if (optMenu != null) {
+ optMenu.findItem(R.id.menu_acc_summary_cancel_selection).setVisible(false);
+ optMenu.findItem(R.id.menu_acc_summary_hide_selected).setVisible(false);
}
}
}
--- /dev/null
+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.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class AccountSummaryViewModel extends AndroidViewModel {
+ private MobileLedgerDatabase dbh;
+ private List<LedgerAccount> accounts;
+
+ public AccountSummaryViewModel(@NonNull Application application) {
+ super(application);
+ dbh = new MobileLedgerDatabase(application);
+ }
+
+ List<LedgerAccount> getAccounts() {
+ if (accounts == null) {
+ accounts = new ArrayList<>();
+ reloadAccounts();
+ }
+
+ return accounts;
+ }
+
+ void reloadAccounts() {
+ accounts.clear();
+ boolean showingHiddenAccounts =
+ PreferenceManager.getDefaultSharedPreferences(getApplication())
+ .getBoolean("show_hidden_accounts", false);
+ String sql = "SELECT name, hidden FROM accounts";
+ if (!showingHiddenAccounts) sql += " WHERE hidden = 0";
+ sql += " ORDER BY name";
+
+ try (SQLiteDatabase db = dbh.getReadableDatabase()) {
+ 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);
+ }
+ }
+ }
+ }
+}
+
+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.isSelected());
+
+ 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.setSelected(false);
+ this.selectionActive = true;
+ notifyDataSetChanged();
+ }
+
+ public void stopSelection() {
+ this.selectionActive = false;
+ notifyDataSetChanged();
+ }
+
+ public boolean isSelectionActive() {
+ return selectionActive;
+ }
+
+ public void selectItem(int position) {
+ accounts.get(position).toggleSelected();
+ notifyItemChanged(position);
+ }
+
+// @NonNull
+// @Override
+// public View getView(int position, @Nullable View row, @NonNull ViewGroup parent) {
+// LedgerAccount acc = getItem(position);
+// LedgerRowHolder holder;
+// if (row == null) {
+// holder = new LedgerRowHolder();
+// LayoutInflater vi = getSystemService(this.getContext(), LayoutInflater.class);
+// MenuInflater mi = getSystemService(this.getContext(), MenuInflater.class);
+//
+// if (vi == null)
+// throw new IllegalStateException("Unable to instantiate the inflater " + "service");
+// row = vi.inflate(R.layout.account_summary_row, parent, false);
+// holder.tvAccountName = row.findViewById(R.id.account_row_acc_name);
+// holder.tvAccountAmounts = row.findViewById(R.id.account_row_acc_amounts);
+// row.setTag(R.id.VH, holder);
+//
+// row.setPadding(context.getResources()
+// .getDimensionPixelSize(R.dimen.activity_horizontal_margin)/2, dp2px
+// (context, 3),
+// context.getResources()
+// .getDimensionPixelSize(R.dimen.activity_horizontal_margin)/2,
+// dp2px(context, 4));
+// View.OnCreateContextMenuListener ccml = new View.OnCreateContextMenuListener() {
+// @Override
+// public void onCreateContextMenu(ContextMenu menu, View v,
+// ContextMenu.ContextMenuInfo menuInfo) {
+// final ListView parent = (ListView) v.getParent();
+// int pos = parent.getPositionForView(v);
+// parent.setItemChecked(pos, true);
+// Log.d("list", String.format("checking pos %d", pos));
+// }
+// };
+// row.setOnCreateContextMenuListener(ccml);
+//
+// }
+// else holder = (LedgerRowHolder) row.getTag(R.id.VH);
+//
+// holder.tvAccountName.setText(acc.getShortName());
+// holder.tvAccountName.setPadding(acc.getLevel() * context.getResources()
+// .getDimensionPixelSize(R.dimen.activity_horizontal_margin), 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);
+// }
+//
+// int real_pos = ((ListView)parent).getPositionForView(row);
+// Log.d("model", String.format("%s: real_pos=%d, position=%d", acc.getName(), real_pos,
+// position));
+// if (real_pos == -1) real_pos = position+1;
+// else real_pos = real_pos + 1;
+//
+// if ( real_pos % 2 == 0) {
+// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+// row.setBackgroundColor(context.getResources()
+// .getColor(R.color.table_row_even_bg, context.getTheme()));
+// }
+// else {
+// row.setBackgroundColor(
+// context.getResources().getColor(R.color.table_row_even_bg));
+// }
+// }
+// else {
+// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+// row.setBackgroundColor(context.getResources()
+// .getColor(R.color.drawer_background, context.getTheme()));
+// }
+// else {
+// row.setBackgroundColor(context.getResources().getColor(R.color.drawer_background));
+// }
+// }
+//
+// row.setTag(R.id.POS, position);
+//
+// return row;
+// }
+
+ 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
--- /dev/null
+package net.ktnx.mobileledger;
+
+import android.content.Context;
+import android.util.TypedValue;
+
+public class DimensionUtils {
+ public static int dp2px(Context context, float dp) {
+ return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
+ context.getResources().getDisplayMetrics()));
+ }
+
+}
--- /dev/null
+package net.ktnx.mobileledger;
+
+import android.support.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+class LedgerAccount {
+ private String name;
+ private String shortName;
+ private int level;
+ private String parentName;
+ private boolean hidden;
+ private List<LedgerAmount> amounts;
+ private boolean selected;
+ static Pattern higher_account = Pattern.compile("^[^:]+:");
+
+ LedgerAccount(String name) {
+ this.setName(name);
+ hidden = false;
+ selected = false;
+ }
+
+ public boolean isHidden() {
+ return hidden;
+ }
+
+ public void setHidden(boolean hidden) {
+ this.hidden = hidden;
+ }
+
+ LedgerAccount(String name, float amount) {
+ this.setName(name);
+ this.hidden = false;
+ this.amounts = new ArrayList<LedgerAmount>();
+ this.addAmount(amount);
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ stripName();
+ }
+
+ private void stripName() {
+ level = 0;
+ shortName = name;
+ StringBuilder parentBuilder = new StringBuilder();
+ while (true) {
+ Matcher m = higher_account.matcher(shortName);
+ if (m.find()) {
+ level++;
+ parentBuilder.append(m.group(0));
+ shortName = m.replaceFirst("");
+ }
+ else break;
+ }
+ if (parentBuilder.length() > 0)
+ parentName = parentBuilder.substring(0, parentBuilder.length() - 1);
+ else parentName = null;
+ }
+
+ String getName() {
+ return name;
+ }
+
+ void addAmount(float amount, String currency) {
+ if (amounts == null ) amounts = new ArrayList<>();
+ amounts.add(new LedgerAmount(amount, currency));
+ }
+ void addAmount(float amount) {
+ this.addAmount(amount, null);
+ }
+
+ String getAmountsString() {
+ if ((amounts == null) || amounts.isEmpty()) return "";
+
+ StringBuilder builder = new StringBuilder();
+ for( LedgerAmount amount : amounts ) {
+ String amt = amount.toString();
+ if (builder.length() > 0) builder.append('\n');
+ builder.append(amt);
+ }
+
+ return builder.toString();
+ }
+
+ public int getLevel() {
+ return level;
+ }
+
+ @NonNull
+ public String getShortName() {
+ return shortName;
+ }
+
+ public String getParentName() {
+ return parentName;
+ }
+
+ public boolean isSelected() {
+ return selected;
+ }
+
+ public void setSelected(boolean selected) {
+ this.selected = selected;
+ }
+
+ public void toggleSelected() {
+ selected = !selected;
+ }
+}
--- /dev/null
+package net.ktnx.mobileledger;
+
+import android.annotation.SuppressLint;
+import android.support.annotation.NonNull;
+
+class LedgerAmount {
+ private String currency;
+ private float amount;
+
+ public
+ LedgerAmount(float amount, @NonNull String currency) {
+ this.currency = currency;
+ this.amount = amount;
+ }
+
+ public
+ LedgerAmount(float amount) {
+ this.amount = amount;
+ this.currency = null;
+ }
+
+ @SuppressLint("DefaultLocale")
+ @NonNull
+ public String toString() {
+ if (currency == null) return String.format("%,1.2f", amount);
+ else return String.format("%s %,1.2f", currency, amount);
+ }
+}
static final String DB_NAME = "mobile-ledger.db";
static final String ACCOUNTS_TABLE = "accounts";
static final String DESCRIPTION_HISTORY_TABLE = "description_history";
- static final int LATEST_REVISION = 4;
+ static final int LATEST_REVISION = 6;
final Context mContext;
--- /dev/null
+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) {
+
+ }
+}
package net.ktnx.mobileledger;
-import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URLDecoder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-abstract public class RetrieveAccountsTask extends android.os.AsyncTask<Void, Integer, Void> {
+class RetrieveAccountsTask extends android.os.AsyncTask<Void, Integer, Void> {
int error;
private SharedPreferences pref;
- private final Context mContext;
+ WeakReference<AccountSummary> mContext;
- RetrieveAccountsTask(Context context) {
+ RetrieveAccountsTask(WeakReference<AccountSummary> context) {
mContext = context;
error = 0;
}
http.setAllowUserInteraction(false);
http.setRequestProperty("Accept-Charset", "UTF-8");
publishProgress(0);
- try(MobileLedgerDatabase dbh = new MobileLedgerDatabase(mContext)) {
+ try(MobileLedgerDatabase dbh = new MobileLedgerDatabase(mContext.get())) {
try(SQLiteDatabase db = dbh.getWritableDatabase()) {
try (InputStream resp = http.getInputStream()) {
Log.d("update_accounts", String.valueOf(http.getResponseCode()));
acct_name = acct_name.replace("\"", "");
Log.d("account-parser", acct_name);
- db.execSQL(
- "insert or replace into accounts(name, name_upper, "
- + "keep) values(?, ?, 1)",
- new Object[]{acct_name, acct_name.toUpperCase()});
+ addAccount(db, acct_name);
publishProgress(++count);
last_account_name = acct_name;
return null;
}
- abstract protected void onProgressUpdate(Integer... values);
+ private void addAccount(SQLiteDatabase db, String name) {
+ do {
+ LedgerAccount acc = new LedgerAccount(name);
+ db.execSQL(
+ "insert or replace into accounts(name, name_upper, level, parent_name, keep) "
+ + "values(?, ?, ?, ?, 1)",
+ new Object[]{name, name.toUpperCase(), acc.getLevel(), acc.getParentName()});
+ name = acc.getParentName();
+ } while (name != null);
+ }
+ @Override
+ protected void onPostExecute(Void result) {
+ AccountSummary ctx = mContext.get();
+ if (ctx == null) return;
+ ctx.onAccountRefreshDone(this.error);
+ }
- abstract protected void onPostExecute(Void result);
}
--- /dev/null
+<vector android:height="24dp" android:tint="#EEEEEE"
+ android:viewportHeight="24.0" android:viewportWidth="24.0"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#FF000000" android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/>
+</vector>
--- /dev/null
+<vector android:height="24dp" android:tint="#EEEEEE"
+ android:viewportHeight="24.0" android:viewportWidth="24.0"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#FF000000" android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
+</vector>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/account_summary_row"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:minHeight="36dp"
+ android:orientation="horizontal"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ tools:showIn="@layout/content_account_summary">
+
+ <CheckBox
+ android:id="@+id/account_row_check"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent" />
+
+ <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." />
+
+ <TextView
+ android:id="@+id/account_row_acc_amounts"
+ style="@style/account_summary_amounts"
+ android:text="123,45\n678,90" />
+</LinearLayout>
\ No newline at end of file
tools:context=".AccountSummary"
tools:showIn="@layout/app_bar_account_summary">
- <TextView
- android:id="@+id/textProgress"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="TextView"
- android:visibility="gone"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- tools:ignore="HardcodedText" />
-
- <ProgressBar
- android:id="@+id/progressBar"
- style="?android:attr/progressBarStyle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="8dp"
- android:layout_marginEnd="8dp"
- android:layout_marginBottom="16dp"
- android:visibility="gone"
- app:layout_constraintBottom_toTopOf="@+id/textProgress"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent" />
-
- <ScrollView
- android:id="@+id/account_root_scroller"
+ <android.support.v4.widget.SwipeRefreshLayout
+ android:id="@+id/account_swiper"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- app:layout_constraintBottom_toTopOf="@+id/textProgress"
- app:layout_constraintTop_toTopOf="parent">
+ android:layout_height="match_parent">
- <LinearLayout
+ <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">
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_vertical"
- android:orientation="horizontal">
-
- <TextView
- android:id="@+id/textView"
- 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." />
-
- <TextView
- android:id="@+id/textView2"
- style="@style/account_summary_amounts"
- android:text="123,45\n678,90" />
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center_vertical"
- android:orientation="horizontal">
-
- <TextView
- android:id="@+id/textView3"
- style="@style/account_summary_account_name"
- android:text="TextView" />
-
- <TextView
- android:id="@+id/textView4"
- style="@style/account_summary_amounts"
- android:text="123,45\n678,90" />
-
- </LinearLayout>
-
- </LinearLayout>
- </ScrollView>
-
+ </android.support.v7.widget.RecyclerView>
+ </android.support.v4.widget.SwipeRefreshLayout>
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
- <item
- android:id="@+id/menu_acc_summary_refresh"
- android:icon="@drawable/ic_refresh_white_24dp"
- android:title="@string/menu_acc_summary_refresh_title"
- android:onClick="onRefreshAccountSummaryClicked"
- app:showAsAction="ifRoom" />
<item
android:id="@+id/menu_acc_summary_show_hidden"
android:checkable="true"
app:actionLayout="@layout/switch_item"
android:onClick="onShowHiddenAccountsClicked"
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"
+ />
+ <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:icon="@drawable/ic_cancel_white_24dp" />
</menu>
\ No newline at end of file
--- /dev/null
+alter table accounts add level integer;
+alter table accounts add parent varchar;
\ No newline at end of file
--- /dev/null
+drop index idx_accounts_name;
+create table accounts_tmp(name varchar not null, name_upper varchar not null primary key, hidden boolean not null default 0, level integer not null default 0, parent_name varchar);
+insert or replace into accounts_tmp(name, name_upper, hidden, level, parent_name) select name, name_upper, hidden, level, parent from accounts;
+drop table accounts;
+create table accounts(name varchar not null, name_upper varchar not null primary key, hidden boolean not null default 0, level integer not null default 0, parent_name varchar, keep boolean default 1);
+insert into accounts(name, name_upper, hidden, level, parent_name) select name, name_upper, hidden, level, parent_name from accounts_tmp;
+drop table accounts_tmp;
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <item name="VH" type="id" />
+ <item name="POS" type="id" />
+ <item name="SELECTING" type="id" />
+</resources>
\ No newline at end of file
<string name="interface_pref_header_title">Interface</string>
<string name="pref_show_hidden_accounts_off_summary">Hidden accounts are not visible in the account list</string>
<string name="pref_show_hidden_accounts_on_summary">Hidden accounts are shown in the account list</string>
+ <string name="menu_acc_summary_hide_selected_title">Hide selected accounts</string>
+ <string name="menu_acc_summary_cancel_selection_title">Cancel selection</string>
</resources>