From f6c803b3c43e2601b3c5ed7f6b6a0e630a71cf6b Mon Sep 17 00:00:00 2001 From: Damyan Ivanov Date: Thu, 13 Dec 2018 20:44:08 +0000 Subject: [PATCH] major rework of the async stuff, view model, pull-to-refresh account list --- app/build.gradle | 1 + .../ktnx/mobileledger/AccountRowLayout.java | 39 --- .../net/ktnx/mobileledger/AccountSummary.java | 262 +++++------------- .../mobileledger/AccountSummaryViewModel.java | 249 +++++++++++++++++ .../net/ktnx/mobileledger/DimensionUtils.java | 12 + .../net/ktnx/mobileledger/LedgerAccount.java | 113 ++++++++ .../net/ktnx/mobileledger/LedgerAmount.java | 28 ++ .../mobileledger/MobileLedgerDatabase.java | 2 +- .../mobileledger/RecyclerItemListener.java | 56 ++++ .../mobileledger/RetrieveAccountsTask.java | 33 ++- .../res/drawable/ic_cancel_white_24dp.xml | 5 + .../main/res/drawable/ic_star_white_24dp.xml | 5 + .../main/res/layout/account_summary_row.xml | 30 ++ .../res/layout/content_account_summary.xml | 76 +---- app/src/main/res/menu/account_summary.xml | 18 +- app/src/main/res/raw/sql_5.sql | 2 + app/src/main/res/raw/sql_6.sql | 7 + app/src/main/res/values/ids.xml | 6 + app/src/main/res/values/strings.xml | 2 + 19 files changed, 627 insertions(+), 319 deletions(-) delete mode 100644 app/src/main/java/net/ktnx/mobileledger/AccountRowLayout.java create mode 100644 app/src/main/java/net/ktnx/mobileledger/AccountSummaryViewModel.java create mode 100644 app/src/main/java/net/ktnx/mobileledger/DimensionUtils.java create mode 100644 app/src/main/java/net/ktnx/mobileledger/LedgerAccount.java create mode 100644 app/src/main/java/net/ktnx/mobileledger/LedgerAmount.java create mode 100644 app/src/main/java/net/ktnx/mobileledger/RecyclerItemListener.java create mode 100644 app/src/main/res/drawable/ic_cancel_white_24dp.xml create mode 100644 app/src/main/res/drawable/ic_star_white_24dp.xml create mode 100644 app/src/main/res/layout/account_summary_row.xml create mode 100644 app/src/main/res/raw/sql_5.sql create mode 100644 app/src/main/res/raw/sql_6.sql create mode 100644 app/src/main/res/values/ids.xml diff --git a/app/build.gradle b/app/build.gradle index 35d46748..e7f61276 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,6 +30,7 @@ dependencies { 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' diff --git a/app/src/main/java/net/ktnx/mobileledger/AccountRowLayout.java b/app/src/main/java/net/ktnx/mobileledger/AccountRowLayout.java deleted file mode 100644 index 57ca6232..00000000 --- a/app/src/main/java/net/ktnx/mobileledger/AccountRowLayout.java +++ /dev/null @@ -1,39 +0,0 @@ -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; - } -} diff --git a/app/src/main/java/net/ktnx/mobileledger/AccountSummary.java b/app/src/main/java/net/ktnx/mobileledger/AccountSummary.java index 829b00b1..c1f834b1 100644 --- a/app/src/main/java/net/ktnx/mobileledger/AccountSummary.java +++ b/app/src/main/java/net/ktnx/mobileledger/AccountSummary.java @@ -1,40 +1,30 @@ 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; @@ -43,13 +33,14 @@ public class AccountSummary extends AppCompatActivity { 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) { @@ -75,8 +66,43 @@ public class AccountSummary extends AppCompatActivity { e.printStackTrace(); } + model = ViewModelProviders.of(this).get(AccountSummaryViewModel.class); + List 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); } @@ -111,7 +137,7 @@ public class AccountSummary extends AppCompatActivity { } public void nav_exit_clicked(View view) { - Log.w("mobileledger", "exiting"); + Log.w("app", "exiting"); finish(); } @@ -134,21 +160,13 @@ public class AccountSummary extends AppCompatActivity { 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); @@ -202,178 +220,34 @@ public class AccountSummary extends AppCompatActivity { } 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); } } } diff --git a/app/src/main/java/net/ktnx/mobileledger/AccountSummaryViewModel.java b/app/src/main/java/net/ktnx/mobileledger/AccountSummaryViewModel.java new file mode 100644 index 00000000..da116313 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/AccountSummaryViewModel.java @@ -0,0 +1,249 @@ +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 accounts; + + public AccountSummaryViewModel(@NonNull Application application) { + super(application); + dbh = new MobileLedgerDatabase(application); + } + + List 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 { + private List accounts; + private boolean selectionActive; + + AccountSummaryAdapter(List 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 diff --git a/app/src/main/java/net/ktnx/mobileledger/DimensionUtils.java b/app/src/main/java/net/ktnx/mobileledger/DimensionUtils.java new file mode 100644 index 00000000..f8e6d941 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/DimensionUtils.java @@ -0,0 +1,12 @@ +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())); + } + +} diff --git a/app/src/main/java/net/ktnx/mobileledger/LedgerAccount.java b/app/src/main/java/net/ktnx/mobileledger/LedgerAccount.java new file mode 100644 index 00000000..27ab1d82 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/LedgerAccount.java @@ -0,0 +1,113 @@ +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 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(); + 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; + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/LedgerAmount.java b/app/src/main/java/net/ktnx/mobileledger/LedgerAmount.java new file mode 100644 index 00000000..6afea7d0 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/LedgerAmount.java @@ -0,0 +1,28 @@ +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); + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/MobileLedgerDatabase.java b/app/src/main/java/net/ktnx/mobileledger/MobileLedgerDatabase.java index 301b946f..72a68eea 100644 --- a/app/src/main/java/net/ktnx/mobileledger/MobileLedgerDatabase.java +++ b/app/src/main/java/net/ktnx/mobileledger/MobileLedgerDatabase.java @@ -18,7 +18,7 @@ class MobileLedgerDatabase extends SQLiteOpenHelper implements AutoCloseable { 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; diff --git a/app/src/main/java/net/ktnx/mobileledger/RecyclerItemListener.java b/app/src/main/java/net/ktnx/mobileledger/RecyclerItemListener.java new file mode 100644 index 00000000..df65bb50 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/RecyclerItemListener.java @@ -0,0 +1,56 @@ +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/RetrieveAccountsTask.java b/app/src/main/java/net/ktnx/mobileledger/RetrieveAccountsTask.java index 21963dd6..2c06849c 100644 --- a/app/src/main/java/net/ktnx/mobileledger/RetrieveAccountsTask.java +++ b/app/src/main/java/net/ktnx/mobileledger/RetrieveAccountsTask.java @@ -1,6 +1,5 @@ package net.ktnx.mobileledger; -import android.content.Context; import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; import android.util.Log; @@ -10,19 +9,20 @@ import java.io.FileNotFoundException; 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 { +class RetrieveAccountsTask extends android.os.AsyncTask { int error; private SharedPreferences pref; - private final Context mContext; + WeakReference mContext; - RetrieveAccountsTask(Context context) { + RetrieveAccountsTask(WeakReference context) { mContext = context; error = 0; } @@ -37,7 +37,7 @@ abstract public class RetrieveAccountsTask extends android.os.AsyncTask + + diff --git a/app/src/main/res/drawable/ic_star_white_24dp.xml b/app/src/main/res/drawable/ic_star_white_24dp.xml new file mode 100644 index 00000000..0c87e523 --- /dev/null +++ b/app/src/main/res/drawable/ic_star_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/account_summary_row.xml b/app/src/main/res/layout/account_summary_row.xml new file mode 100644 index 00000000..9617db65 --- /dev/null +++ b/app/src/main/res/layout/account_summary_row.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/content_account_summary.xml b/app/src/main/res/layout/content_account_summary.xml index c30125ee..dc59bb5d 100644 --- a/app/src/main/res/layout/content_account_summary.xml +++ b/app/src/main/res/layout/content_account_summary.xml @@ -9,79 +9,19 @@ tools:context=".AccountSummary" tools:showIn="@layout/app_bar_account_summary"> - - - - - + android:layout_height="match_parent"> - - - - - - - - - - - - - - - - - - - + + \ No newline at end of file diff --git a/app/src/main/res/menu/account_summary.xml b/app/src/main/res/menu/account_summary.xml index 17f85258..0a4b4361 100644 --- a/app/src/main/res/menu/account_summary.xml +++ b/app/src/main/res/menu/account_summary.xml @@ -2,12 +2,6 @@ - + + \ No newline at end of file diff --git a/app/src/main/res/raw/sql_5.sql b/app/src/main/res/raw/sql_5.sql new file mode 100644 index 00000000..309f82ef --- /dev/null +++ b/app/src/main/res/raw/sql_5.sql @@ -0,0 +1,2 @@ +alter table accounts add level integer; +alter table accounts add parent varchar; \ No newline at end of file diff --git a/app/src/main/res/raw/sql_6.sql b/app/src/main/res/raw/sql_6.sql new file mode 100644 index 00000000..c8e86348 --- /dev/null +++ b/app/src/main/res/raw/sql_6.sql @@ -0,0 +1,7 @@ +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; diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml new file mode 100644 index 00000000..31d8b4fb --- /dev/null +++ b/app/src/main/res/values/ids.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 903582cf..c0540162 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -79,4 +79,6 @@ Interface Hidden accounts are not visible in the account list Hidden accounts are shown in the account list + Hide selected accounts + Cancel selection -- 2.39.2