X-Git-Url: https://git.ktnx.net/?p=mobile-ledger.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fnet%2Fktnx%2Fmobileledger%2Fui%2Factivity%2FNewTransactionActivity.java;h=c39ec9ae4e4664f493dc9e34042c84cd078ca51a;hp=5f30a0fe62e35a20246f1181702bad3318653b11;hb=a9f669055c4458c54e3ece1d2896e5ce0c28b920;hpb=998dd32a089d199a2569069415755eb3169b35b0 diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionActivity.java index 5f30a0fe..c39ec9ae 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionActivity.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionActivity.java @@ -18,16 +18,12 @@ package net.ktnx.mobileledger.ui.activity; import android.annotation.SuppressLint; +import android.database.Cursor; +import android.os.AsyncTask; import android.os.Bundle; -import android.support.design.widget.BaseTransientBottomBar; -import android.support.design.widget.Snackbar; -import android.support.v4.app.DialogFragment; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; -import android.util.Log; import android.util.TypedValue; import android.view.Gravity; import android.view.Menu; @@ -43,48 +39,62 @@ import android.widget.TableRow; import android.widget.TextView; import android.widget.Toast; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.BaseTransientBottomBar; +import com.google.android.material.snackbar.Snackbar; + +import net.ktnx.mobileledger.App; +import net.ktnx.mobileledger.BuildConfig; import net.ktnx.mobileledger.R; -import net.ktnx.mobileledger.async.SaveTransactionTask; +import net.ktnx.mobileledger.async.DescriptionSelectedCallback; +import net.ktnx.mobileledger.async.SendTransactionTask; import net.ktnx.mobileledger.async.TaskCallback; import net.ktnx.mobileledger.model.Data; import net.ktnx.mobileledger.model.LedgerTransaction; import net.ktnx.mobileledger.model.LedgerTransactionAccount; +import net.ktnx.mobileledger.model.MobileLedgerProfile; import net.ktnx.mobileledger.ui.DatePickerFragment; import net.ktnx.mobileledger.ui.OnSwipeTouchListener; import net.ktnx.mobileledger.utils.Globals; import net.ktnx.mobileledger.utils.MLDB; import java.text.ParseException; +import java.util.ArrayList; import java.util.Date; +import java.util.Locale; import java.util.Objects; +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.DialogFragment; + +import static net.ktnx.mobileledger.utils.Logger.debug; + /* * TODO: nicer progress while transaction is submitted * TODO: reports * TODO: get rid of the custom session/cookie and auth code? * (the last problem with the POST was the missing content-length header) - * TODO: nicer swiping removal with visual feedback - * TODO: setup wizard - * TODO: update accounts/check settings upon change of backend settings * */ -public class NewTransactionActivity extends AppCompatActivity implements TaskCallback { - private static SaveTransactionTask saver; +public class NewTransactionActivity extends ProfileThemedActivity + implements TaskCallback, DescriptionSelectedCallback { + private static SendTransactionTask saver; private TableLayout table; private ProgressBar progress; + private FloatingActionButton fab; private TextView tvDate; private AutoCompleteTextView tvDescription; - private MenuItem mSave; private static boolean isZero(float f) { return (f < 0.005) && (f > -0.005); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setContentView(R.layout.activity_new_transaction); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); - toolbar.setSubtitle(Data.profile.get().getName()); + toolbar.setSubtitle(mProfile.getName()); tvDate = findViewById(R.id.new_transaction_date); tvDate.setOnFocusChangeListener((v, hasFocus) -> { @@ -92,10 +102,12 @@ public class NewTransactionActivity extends AppCompatActivity implements TaskCal }); tvDescription = findViewById(R.id.new_transaction_description); MLDB.hookAutocompletionAdapter(this, tvDescription, MLDB.DESCRIPTION_HISTORY_TABLE, - "description", false, findViewById(R.id.new_transaction_acc_1)); + "description", false, findViewById(R.id.new_transaction_acc_1), this, mProfile); hookTextChangeListener(tvDescription); progress = findViewById(R.id.save_transaction_progress); + fab = findViewById(R.id.fab); + fab.setOnClickListener(v -> saveTransaction()); Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); table = findViewById(R.id.new_transaction_accounts_table); @@ -105,13 +117,22 @@ public class NewTransactionActivity extends AppCompatActivity implements TaskCal TextView tvAmount = (TextView) row.getChildAt(1); hookSwipeListener(row); MLDB.hookAutocompletionAdapter(this, tvAccountName, MLDB.ACCOUNTS_TABLE, "name", true, - tvAmount); + tvAmount, null, mProfile); hookTextChangeListener(tvAccountName); hookTextChangeListener(tvAmount); -// Log.d("swipe", "hooked to row "+i); +// debug("swipe", "hooked to row "+i); } } + @Override + protected void initProfile() { + String profileUUID = getIntent().getStringExtra("profile_uuid"); + if (profileUUID != null) { + mProfile = Data.getProfile(profileUUID); + if (mProfile == null) finish(); + } + else super.initProfile(); + } @Override public void finish() { super.finish(); @@ -133,48 +154,62 @@ public class NewTransactionActivity extends AppCompatActivity implements TaskCal if (tvDescription.getText().toString().isEmpty()) tvDescription.requestFocus(); } public void saveTransaction() { - if (mSave != null) mSave.setVisible(false); + if (fab != null) fab.setEnabled(false); toggleAllEditing(false); progress.setVisibility(View.VISIBLE); try { - saver = new SaveTransactionTask(this); + saver = new SendTransactionTask(this, mProfile); String dateString = tvDate.getText().toString(); Date date; if (dateString.isEmpty()) date = new Date(); else date = Globals.parseLedgerDate(dateString); - LedgerTransaction tr = new LedgerTransaction(date, tvDescription.getText().toString()); + LedgerTransaction tr = new LedgerTransaction(null, date, tvDescription.getText().toString(), mProfile); TableLayout table = findViewById(R.id.new_transaction_accounts_table); + LedgerTransactionAccount emptyAmountAccount = null; + float emptyAmountAccountBalance = 0; for (int i = 0; i < table.getChildCount(); i++) { TableRow row = (TableRow) table.getChildAt(i); String acc = ((TextView) row.getChildAt(0)).getText().toString(); + if (acc.isEmpty()) continue; + String amt = ((TextView) row.getChildAt(1)).getText().toString(); - LedgerTransactionAccount item = - amt.length() > 0 ? new LedgerTransactionAccount(acc, Float.parseFloat(amt)) - : new LedgerTransactionAccount(acc); + LedgerTransactionAccount item; + if (amt.length() > 0) { + final float amount = Float.parseFloat(amt); + item = new LedgerTransactionAccount(acc, amount); + emptyAmountAccountBalance += amount; + } + else { + item = new LedgerTransactionAccount(acc); + emptyAmountAccount = item; + } tr.addAccount(item); } + + if (emptyAmountAccount != null) + emptyAmountAccount.setAmount(-emptyAmountAccountBalance); saver.execute(tr); } catch (ParseException e) { - Log.d("new-transaction", "Parse error", e); + debug("new-transaction", "Parse error", e); Toast.makeText(this, getResources().getString(R.string.error_invalid_date), Toast.LENGTH_LONG).show(); tvDate.requestFocus(); progress.setVisibility(View.GONE); toggleAllEditing(true); - if (mSave != null) mSave.setVisible(true); + if (fab != null) fab.setEnabled(true); } catch (Exception e) { - Log.d("new-transaction", "Unknown error", e); + debug("new-transaction", "Unknown error", e); progress.setVisibility(View.GONE); toggleAllEditing(true); - if (mSave != null) mSave.setVisible(true); + if (fab != null) fab.setEnabled(true); } } private void toggleAllEditing(boolean enabled) { @@ -190,8 +225,7 @@ public class NewTransactionActivity extends AppCompatActivity implements TaskCal } private void hookSwipeListener(final TableRow row) { row.getChildAt(0).setOnTouchListener(new OnSwipeTouchListener(this) { - public void onSwipeLeft() { -// Log.d("swipe", "LEFT" + row.getId()); + private void onSwipeAside() { if (table.getChildCount() > 2) { TableRow prev_row = (TableRow) table.getChildAt(table.indexOfChild(row) - 1); TableRow next_row = (TableRow) table.getChildAt(table.indexOfChild(row) + 1); @@ -225,6 +259,12 @@ public class NewTransactionActivity extends AppCompatActivity implements TaskCal Snackbar.LENGTH_LONG).setAction("Action", null).show(); } } + public void onSwipeLeft() { + onSwipeAside(); + } + public void onSwipeRight() { + onSwipeAside(); + } // @Override // public boolean performClick(View view, MotionEvent m) { // return true; @@ -235,12 +275,17 @@ public class NewTransactionActivity extends AppCompatActivity implements TaskCal }); } + public void simulateCrash(MenuItem item) { + debug("crash", "Will crash intentionally"); + new AsyncCrasher().execute(); + } public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.new_transaction, menu); - mSave = menu.findItem(R.id.action_submit_transaction); - if (mSave == null) throw new AssertionError(); + if (BuildConfig.DEBUG) { + menu.findItem(R.id.action_simulate_crash).setVisible(true); + } check_transaction_submittable(); return true; @@ -269,13 +314,13 @@ public class NewTransactionActivity extends AppCompatActivity implements TaskCal @Override public void afterTextChanged(Editable s) { -// Log.d("input", "text changed"); +// debug("input", "text changed"); check_transaction_submittable(); } }); } - private void doAddAccountRow(boolean focus) { + private TableRow doAddAccountRow(boolean focus) { final AutoCompleteTextView acc = new AutoCompleteTextView(this); acc.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT, 9f)); @@ -295,6 +340,7 @@ public class NewTransactionActivity extends AppCompatActivity implements TaskCal amt.setMinWidth(dp2px(40)); amt.setTextAlignment(EditText.TEXT_ALIGNMENT_VIEW_END); amt.setImeOptions(EditorInfo.IME_ACTION_DONE); + amt.setSelectAllOnFocus(true); // forward navigation support final TableRow last_row = (TableRow) table.getChildAt(table.getChildCount() - 1); @@ -316,9 +362,12 @@ public class NewTransactionActivity extends AppCompatActivity implements TaskCal if (focus) acc.requestFocus(); hookSwipeListener(row); - MLDB.hookAutocompletionAdapter(this, acc, MLDB.ACCOUNTS_TABLE, "name", true, amt); + MLDB.hookAutocompletionAdapter(this, acc, MLDB.ACCOUNTS_TABLE, "name", true, amt, null, + mProfile); hookTextChangeListener(acc); hookTextChangeListener(amt); + + return row; } public void addTransactionAccountFromMenu(MenuItem item) { doAddAccountRow(true); @@ -393,7 +442,7 @@ public class NewTransactionActivity extends AppCompatActivity implements TaskCal doAddAccountRow(false); } - Log.d("submittable", String.format("accounts=%d, accounts_with_values=%s, " + + debug("submittable", String.format("accounts=%d, accounts_with_values=%s, " + "amounts_with_accounts=%d, amounts=%d, running_total=%1.2f, " + "single_empty_with_acc=%s", accounts, accounts_with_values, amounts_with_accounts, amounts, running_total, @@ -403,9 +452,14 @@ public class NewTransactionActivity extends AppCompatActivity implements TaskCal (amounts_with_accounts == amounts) && (single_empty_amount && single_empty_amount_has_account || isZero(running_total))) { - if (mSave != null) mSave.setVisible(true); + if (fab != null) { + fab.show(); + fab.setEnabled(true); + } + } + else { + if (fab != null) fab.hide(); } - else if (mSave != null) mSave.setVisible(false); if (single_empty_amount) { empty_amount.setHint(String.format("%1.2f", @@ -414,18 +468,18 @@ public class NewTransactionActivity extends AppCompatActivity implements TaskCal } catch (NumberFormatException e) { - if (mSave != null) mSave.setVisible(false); + if (fab != null) fab.hide(); } catch (Exception e) { e.printStackTrace(); - if (mSave != null) mSave.setVisible(false); + if (fab != null) fab.hide(); } } @Override public void done(String error) { progress.setVisibility(View.INVISIBLE); - Log.d("visuals", "hiding progress"); + debug("visuals", "hiding progress"); if (error == null) resetForm(); else Snackbar.make(findViewById(R.id.new_transaction_accounts_table), error, @@ -452,4 +506,92 @@ public class NewTransactionActivity extends AppCompatActivity implements TaskCal ((TextView) tr.getChildAt(1)).setText(""); } } + @Override + public void descriptionSelected(String description) { + debug("descr selected", description); + if (!inputStateIsInitial()) return; + + String accFilter = mProfile.getPreferredAccountsFilter(); + + ArrayList params = new ArrayList<>(); + StringBuilder sb = new StringBuilder( + "select t.profile, t.id from transactions t where t.description=?"); + params.add(description); + + if (accFilter != null) { + sb.append(" AND EXISTS (").append("SELECT 1 FROM transaction_accounts ta ") + .append("WHERE ta.profile = t.profile").append(" AND ta.transaction_id = t.id") + .append(" AND UPPER(ta.account_name) LIKE '%'||?||'%')"); + params.add(accFilter.toUpperCase()); + } + + sb.append(" ORDER BY date desc limit 1"); + + final String sql = sb.toString(); + debug("descr", sql); + debug("descr", params.toString()); + + try (Cursor c = App.getDatabase().rawQuery(sql, params.toArray(new String[]{}))) { + if (!c.moveToNext()) return; + + String profileUUID = c.getString(0); + int transactionId = c.getInt(1); + LedgerTransaction tr; + MobileLedgerProfile profile = Data.getProfile(profileUUID); + if (profile == null) throw new RuntimeException(String.format( + "Unable to find profile %s, which is supposed to contain " + + "transaction %d with description %s", profileUUID, transactionId, description)); + + tr = profile.loadTransaction(transactionId); + table = findViewById(R.id.new_transaction_accounts_table); + ArrayList accounts = tr.getAccounts(); + TableRow firstNegative = null; + int negativeCount = 0; + for (int i = 0; i < accounts.size(); i++) { + LedgerTransactionAccount acc = accounts.get(i); + TableRow row = (TableRow) table.getChildAt(i); + if (row == null) row = doAddAccountRow(false); + + ((TextView) row.getChildAt(0)).setText(acc.getAccountName()); + ((TextView) row.getChildAt(1)) + .setText(String.format(Locale.US, "%1.2f", acc.getAmount())); + + if (acc.getAmount() < 0.005) { + if (firstNegative == null) firstNegative = row; + negativeCount++; + } + } + + if (negativeCount == 1) { + ((TextView) firstNegative.getChildAt(1)).setText(null); + } + + check_transaction_submittable(); + + EditText firstAmount = (EditText) ((TableRow) table.getChildAt(0)).getChildAt(1); + String amtString = String.valueOf(firstAmount.getText()); + firstAmount.requestFocus(); + firstAmount.setSelection(0, amtString.length()); + } + + } + private boolean inputStateIsInitial() { + table = findViewById(R.id.new_transaction_accounts_table); + + if (table.getChildCount() != 2) return false; + + for (int i = 0; i < 2; i++) { + TableRow row = (TableRow) table.getChildAt(i); + if (((TextView) row.getChildAt(0)).getText().length() > 0) return false; + if (((TextView) row.getChildAt(1)).getText().length() > 0) return false; + } + + return true; + } + private class AsyncCrasher extends AsyncTask { + @Override + protected Void doInBackground(Void... voids) { + throw new RuntimeException("Simulated crash"); + } + } }