From: Damyan Ivanov Date: Sat, 15 Dec 2018 13:38:27 +0000 (+0000) Subject: working transaction list retrieval X-Git-Tag: v0.3~236 X-Git-Url: https://git.ktnx.net/?p=mobile-ledger.git;a=commitdiff_plain;h=a983099d6150044a08a1fbc601d9af9bd8d0c0a8 working transaction list retrieval --- diff --git a/app/src/main/java/net/ktnx/mobileledger/TransactionListActivity.java b/app/src/main/java/net/ktnx/mobileledger/TransactionListActivity.java index a948715e..1dc5f026 100644 --- a/app/src/main/java/net/ktnx/mobileledger/TransactionListActivity.java +++ b/app/src/main/java/net/ktnx/mobileledger/TransactionListActivity.java @@ -17,28 +17,90 @@ package net.ktnx.mobileledger; +import android.arch.lifecycle.ViewModelProviders; +import android.os.Build; import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.util.Log; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; -import net.ktnx.mobileledger.ui.transaction_list.TransactionListFragment; +import net.ktnx.mobileledger.async.RetrieveTransactionsTask; +import net.ktnx.mobileledger.model.LedgerTransaction; +import net.ktnx.mobileledger.ui.transaction_list.TransactionListViewModel; +import net.ktnx.mobileledger.utils.MobileLedgerDatabase; -public class TransactionListActivity extends AppCompatActivity { +import java.lang.ref.WeakReference; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.List; +public class TransactionListActivity extends AppCompatActivity { + private SwipeRefreshLayout swiper; + private RecyclerView root; + private ProgressBar progressBar; + private TransactionListViewModel model; + private TextView tvLastUpdate; + private TransactionListAdapter modelAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.transaction_list_activity); - if (savedInstanceState == null) { - getSupportFragmentManager().beginTransaction() - .replace(R.id.container, TransactionListFragment.newInstance()).commitNow(); - } + Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); setupActionBar(); + + swiper = findViewById(R.id.transaction_swipe); + if (swiper == null) throw new RuntimeException("Can't get hold on the swipe layout"); + root = findViewById(R.id.transaction_root); + if (root == null) throw new RuntimeException("Can't get hold on the transaction list view"); + progressBar = findViewById(R.id.transaction_progress_bar); + if (progressBar == null) + throw new RuntimeException("Can't get hold on the transaction list progress bar"); + tvLastUpdate = findViewById(R.id.transactions_last_update); + { + long last_update = (new MobileLedgerDatabase(this)) + .get_option_value("transaction_list_last_update", 0); + if (last_update == 0) tvLastUpdate.setText("never"); + else { + Date date = new Date(last_update); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + tvLastUpdate.setText(date.toInstant().atZone(ZoneId.systemDefault()) + .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + } + else { + tvLastUpdate.setText(date.toLocaleString()); + } + } + } + model = ViewModelProviders.of(this).get(TransactionListViewModel.class); + List transactions = + model.getTransactions(new MobileLedgerDatabase(this)); + modelAdapter = new TransactionListAdapter(transactions); + + RecyclerView root = findViewById(R.id.transaction_root); + root.setAdapter(modelAdapter); + + LinearLayoutManager llm = new LinearLayoutManager(this); + llm.setOrientation(LinearLayoutManager.VERTICAL); + root.setLayoutManager(llm); + + ((SwipeRefreshLayout) findViewById(R.id.transaction_swipe)).setOnRefreshListener(() -> { + Log.d("ui", "refreshing transactions via swipe"); + update_transactions(); + }); + +// update_transactions(); } private void setupActionBar() { ActionBar actionBar = getSupportActionBar(); @@ -53,4 +115,55 @@ public class TransactionListActivity extends AppCompatActivity { Log.d("visuals", "finishing"); overridePendingTransition(R.anim.dummy, R.anim.slide_out_right); } + private void update_transactions() { + RetrieveTransactionsTask task = new RetrieveTransactionsTask(new WeakReference<>(this)); + + RetrieveTransactionsTask.Params params = new RetrieveTransactionsTask.Params( + PreferenceManager.getDefaultSharedPreferences(this)); + + task.execute(params); + } + + public void onRetrieveStart() { + progressBar.setIndeterminate(true); + progressBar.setVisibility(View.VISIBLE); + } + public void onRetrieveProgress(RetrieveTransactionsTask.Progress progress) { + if ((progress.getTotal() == RetrieveTransactionsTask.Progress.INDETERMINATE) || + (progress.getTotal() == 0)) + { + progressBar.setIndeterminate(true); + } + else { + progressBar.setIndeterminate(false); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + progressBar.setMin(0); + } + progressBar.setMax(progress.getTotal()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + progressBar.setProgress(progress.getProgress(), true); + } + else progressBar.setProgress(progress.getProgress()); + } + } + + public void onRetrieveDone(boolean success) { + progressBar.setVisibility(View.GONE); + SwipeRefreshLayout srl = findViewById(R.id.transaction_swipe); + srl.setRefreshing(false); + if (success) { + MobileLedgerDatabase dbh = new MobileLedgerDatabase(this); + Date now = new Date(); + dbh.set_option_value("transaction_list_last_update", now.getTime()); + updateLastUpdateText(now); + } + } + private void updateLastUpdateText(Date now) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + tvLastUpdate.setText(now.toInstant().atZone(ZoneId.systemDefault()).toString()); + } + else { + tvLastUpdate.setText(now.toLocaleString()); + } + } } diff --git a/app/src/main/java/net/ktnx/mobileledger/TransactionListAdapter.java b/app/src/main/java/net/ktnx/mobileledger/TransactionListAdapter.java new file mode 100644 index 00000000..f190efcd --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/TransactionListAdapter.java @@ -0,0 +1,89 @@ +/* + * Copyright © 2018 Damyan Ivanov. + * This file is part of Mobile-Ledger. + * Mobile-Ledger is free software: you can distribute it and/or modify it + * under the term of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your opinion), any later version. + * + * Mobile-Ledger is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License terms for details. + * + * You should have received a copy of the GNU General Public License + * along with Mobile-Ledger. If not, see . + */ + +package net.ktnx.mobileledger; + +import android.content.Context; +import android.content.res.Resources; +import android.os.Build; +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.LinearLayout; +import android.widget.TableLayout; +import android.widget.TextView; + +import net.ktnx.mobileledger.model.LedgerTransaction; + +import java.util.List; + +class TransactionListAdapter + extends RecyclerView.Adapter { + private List transactions; + + TransactionListAdapter(List transactions) { + this.transactions = transactions; + } + + public void onBindViewHolder(@NonNull TransactionRowHolder holder, int position) { + LedgerTransaction tr = transactions.get(position); + Context ctx = holder.row.getContext(); + Resources rm = ctx.getResources(); + + holder.tvDescription.setText(String.format("%s\n%s", tr.getDescription(), tr.getDate())); +// holder.tableAccounts.setText(acc.getAmountsString()); + + 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.row.setTag(R.id.POS, position); + } + + @NonNull + @Override + public TransactionRowHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View row = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.transaction_list_row, parent, false); + return new TransactionRowHolder(row); + } + + @Override + public int getItemCount() { + return transactions.size(); + } + class TransactionRowHolder extends RecyclerView.ViewHolder { + TextView tvDescription; + TableLayout tableAccounts; + LinearLayout row; + public TransactionRowHolder(@NonNull View itemView) { + super(itemView); + this.row = (LinearLayout) itemView; + this.tvDescription = itemView.findViewById(R.id.transaction_row_description); + this.tableAccounts = itemView.findViewById(R.id.transaction_row_acc_amounts); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java b/app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java index aeb49a5f..2e3ca764 100644 --- a/app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java +++ b/app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java @@ -17,12 +17,15 @@ package net.ktnx.mobileledger.async; +import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; import android.os.AsyncTask; +import android.util.Log; import net.ktnx.mobileledger.R; +import net.ktnx.mobileledger.TransactionListActivity; import net.ktnx.mobileledger.model.LedgerTransaction; import net.ktnx.mobileledger.model.LedgerTransactionItem; import net.ktnx.mobileledger.utils.MobileLedgerDatabase; @@ -40,51 +43,55 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -class RetrieveTransactionsTask extends AsyncTask { - class Params { - static final int DEFAULT_LIMIT = 100; - private SharedPreferences backendPref; - private String accountsRoot; - private int limit; - - Params(SharedPreferences backendPref) { - this.backendPref = backendPref; - this.accountsRoot = null; - this.limit = DEFAULT_LIMIT; - } - Params(SharedPreferences backendPref, String accountsRoot) { - this(backendPref, accountsRoot, DEFAULT_LIMIT); - } - Params(SharedPreferences backendPref, String accountsRoot, int limit) { - this.backendPref = backendPref; - this.accountsRoot = accountsRoot; - this.limit = limit; - } - String getAccountsRoot() { - return accountsRoot; - } - SharedPreferences getBackendPref() { - return backendPref; - } - int getLimit() { - return limit; - } - } - private static final Pattern transactionStartPattern = Pattern.compile("([\\d.-]+)"); +public class RetrieveTransactionsTask extends + AsyncTask { + private static final Pattern transactionStartPattern = Pattern.compile("([\\d.-]+)"); private static final Pattern transactionDescriptionPattern = Pattern.compile(" contextRef; + private static final Pattern endPattern = Pattern.compile("\\bid=\"addmodal\""); + protected WeakReference contextRef; protected int error; + private boolean success; + public RetrieveTransactionsTask(WeakReference contextRef) { + this.contextRef = contextRef; + } + private static final void L(String msg) { + Log.d("transaction-parser", msg); + } + @Override + protected void onProgressUpdate(Progress... values) { + super.onProgressUpdate(values); + TransactionListActivity context = getContext(); + if (context == null) return; + context.onRetrieveProgress(values[0]); + } + @Override + protected void onPreExecute() { + super.onPreExecute(); + TransactionListActivity context = getContext(); + if (context == null) return; + context.onRetrieveStart(); + } + @Override + protected void onPostExecute(Void aVoid) { + super.onPostExecute(aVoid); + TransactionListActivity context = getContext(); + if (context == null) return; + context.onRetrieveDone(success); + } + @SuppressLint("DefaultLocale") @Override protected Void doInBackground(Params... params) { + Progress progress = new Progress(); + success = false; try { HttpURLConnection http = NetworkUtil.prepare_connection(params[0].getBackendPref(), "journal"); http.setAllowUserInteraction(false); - publishProgress(0); + publishProgress(progress); Context ctx = contextRef.get(); if (ctx == null) return null; try (MobileLedgerDatabase dbh = new MobileLedgerDatabase(ctx)) { @@ -95,18 +102,7 @@ class RetrieveTransactionsTask extends AsyncTaskGeneral Journal")) + case ParserState.EXPECTING_JOURNAL: + if (line.equals("

General Journal

")) { state = ParserState.EXPECTING_TRANSACTION; - continue; - } - case ParserState.EXPECTING_TRANSACTION: { - Matcher m = transactionStartPattern.matcher(line); + L("→ expecting transaction"); + } + break; + case ParserState.EXPECTING_TRANSACTION: + m = transactionStartPattern.matcher(line); if (m.find()) { - transactionId = m.group(1); + transactionId = Integer.valueOf(m.group(1)); state = ParserState.EXPECTING_TRANSACTION_DESCRIPTION; + L(String.format("found transaction %d → expecting " + + "description", transactionId)); + progress.setProgress(++transactionCount); + if (progress.getTotal() == Progress.INDETERMINATE) + progress.setTotal(transactionId); + publishProgress(progress); } - } - case ParserState.EXPECTING_TRANSACTION_DESCRIPTION: { - Matcher m = transactionDescriptionPattern.matcher(line); + m = endPattern.matcher(line); if (m.find()) { - if (transactionId == null) + L("--- transaction list complete ---"); + success = true; + break LINES; + } + break; + case ParserState.EXPECTING_TRANSACTION_DESCRIPTION: + m = transactionDescriptionPattern.matcher(line); + if (m.find()) { + if (transactionId == 0) throw new TransactionParserException( - "Transaction Id is null while expecting description"); + "Transaction Id is 0 while expecting " + + "description"); transaction = new LedgerTransaction(transactionId, m.group(1), m.group(2)); state = ParserState.EXPECTING_TRANSACTION_DETAILS; + L(String.format("transaction %d created for %s (%s) →" + + " expecting details", transactionId, + m.group(1), m.group(2))); } - } - case ParserState.EXPECTING_TRANSACTION_DETAILS: { - if (transaction == null) - throw new TransactionParserException( - "Transaction is null while expecting details"); + break; + case ParserState.EXPECTING_TRANSACTION_DETAILS: if (line.isEmpty()) { // transaction data collected transaction.insertInto(db); state = ParserState.EXPECTING_TRANSACTION; - publishProgress(++transactionCount); + L(String.format("transaction %s saved → expecting " + + "transaction", transaction.getId())); } else { - Matcher m = transactionDetailsPattern.matcher(line); + m = transactionDetailsPattern.matcher(line); if (m.find()) { String acc_name = m.group(1); String amount = m.group(2); @@ -163,11 +177,12 @@ class RetrieveTransactionsTask extends AsyncTask getContextRef() { - return contextRef; + TransactionListActivity getContext() { + return contextRef.get(); + } + + public static class Params { + static final int DEFAULT_LIMIT = 100; + private SharedPreferences backendPref; + private String accountsRoot; + private int limit; + + public Params(SharedPreferences backendPref) { + this.backendPref = backendPref; + this.accountsRoot = null; + this.limit = DEFAULT_LIMIT; + } + Params(SharedPreferences backendPref, String accountsRoot) { + this(backendPref, accountsRoot, DEFAULT_LIMIT); + } + Params(SharedPreferences backendPref, String accountsRoot, int limit) { + this.backendPref = backendPref; + this.accountsRoot = accountsRoot; + this.limit = limit; + } + String getAccountsRoot() { + return accountsRoot; + } + SharedPreferences getBackendPref() { + return backendPref; + } + int getLimit() { + return limit; + } + } + + public class Progress { + public static final int INDETERMINATE = -1; + private int progress; + private int total; + Progress() { + this(INDETERMINATE, INDETERMINATE); + } + Progress(int progress, int total) { + this.progress = progress; + this.total = total; + } + public int getProgress() { + return progress; + } + protected void setProgress(int progress) { + this.progress = progress; + } + public int getTotal() { + return total; + } + protected void setTotal(int total) { + this.total = total; + } } private class TransactionParserException extends IllegalStateException { diff --git a/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java b/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java index a103ab39..8aa169dc 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java @@ -19,45 +19,63 @@ package net.ktnx.mobileledger.model; import android.database.sqlite.SQLiteDatabase; +import net.ktnx.mobileledger.utils.Digest; + +import java.nio.charset.Charset; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Comparator; import java.util.Iterator; -import java.util.List; public class LedgerTransaction { + private static final String DIGEST_TYPE = "SHA-256"; + public final Comparator comparator = + new Comparator() { + @Override + public int compare(LedgerTransactionItem o1, LedgerTransactionItem o2) { + int res = o1.getAccountName().compareTo(o2.getAccountName()); + if (res != 0) return res; + res = o1.getCurrency().compareTo(o2.getCurrency()); + if (res != 0) return res; + return Float.compare(o1.getAmount(), o2.getAmount()); + } + }; private String id; private String date; private String description; - private List items; - + private ArrayList items; + private String dataHash; public LedgerTransaction(String id, String date, String description) { this.id = id; this.date = date; this.description = description; this.items = new ArrayList<>(); + this.dataHash = null; + } + public LedgerTransaction(int id, String date, String description) { + this(String.valueOf(id), date, description); } public LedgerTransaction(String date, String description) { this(null, date, description); } public void add_item(LedgerTransactionItem item) { items.add(item); + dataHash = null; } - public String getDate() { return date; } - public void setDate(String date) { this.date = date; + dataHash = null; } - public String getDescription() { return description; } - public void setDescription(String description) { this.description = description; + dataHash = null; } - public Iterator getItemsIterator() { return new Iterator() { private int pointer = 0; @@ -75,15 +93,39 @@ public class LedgerTransaction { public String getId() { return id; } - public void insertInto(SQLiteDatabase db) { - db.execSQL("INSERT INTO transactions(id, date, " + "description) values(?, ?, ?)", + fillDataHash(); + db.execSQL("INSERT INTO transactions(id, date, description, data_hash) values(?,?,?,?)", new String[]{id, date, description}); - for(LedgerTransactionItem item : items) { - db.execSQL("INSERT INTO transaction_accounts(transaction_id, account_name, amount, " - + "currency) values(?, ?, ?, ?)", new Object[]{id, item.getAccountName(), - item.getAmount(), item.getCurrency()}); + for (LedgerTransactionItem item : items) { + db.execSQL("INSERT INTO transaction_accounts(transaction_id, account_name, amount, " + + "currency) values(?, ?, ?, ?)", + new Object[]{id, item.getAccountName(), item.getAmount(), item.getCurrency()}); + } + } + private void fillDataHash() { + if (dataHash != null) return; + try { + Digest sha = new Digest(DIGEST_TYPE); + StringBuilder data = new StringBuilder(); + data.append(getId()); + data.append('\0'); + data.append(getDescription()); + data.append('\0'); + for (LedgerTransactionItem item : items) { + data.append(item.getAccountName()); + data.append('\0'); + data.append(item.getCurrency()); + data.append('\0'); + data.append(item.getAmount()); + } + sha.update(data.toString().getBytes(Charset.forName("UTF-8"))); + dataHash = sha.digestToHexString(); + } + catch (NoSuchAlgorithmException e) { + throw new RuntimeException( + String.format("Unable to get instance of %s digest", DIGEST_TYPE), e); } } } diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListFragment.java index 8cd758f7..97d2fe80 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListFragment.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListFragment.java @@ -22,6 +22,7 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -43,6 +44,7 @@ public class TransactionListFragment extends Fragment { @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { + Log.d("flow", "TransactionListFragment.onActivityCreated called"); super.onActivityCreated(savedInstanceState); mViewModel = ViewModelProviders.of(this).get(TransactionListViewModel.class); // TODO: Use the ViewModel diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListViewModel.java b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListViewModel.java index 10aa2e9b..35b01145 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListViewModel.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListViewModel.java @@ -18,7 +18,43 @@ package net.ktnx.mobileledger.ui.transaction_list; import android.arch.lifecycle.ViewModel; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import net.ktnx.mobileledger.model.LedgerTransaction; +import net.ktnx.mobileledger.utils.MobileLedgerDatabase; + +import java.util.ArrayList; +import java.util.List; public class TransactionListViewModel extends ViewModel { - // TODO: Implement the ViewModel + + private List transactions; + + public List getTransactions(MobileLedgerDatabase dbh) { + if (transactions == null) { + transactions = new ArrayList<>(); + reloadTransactions(dbh); + } + + return transactions; + } + private void reloadTransactions(MobileLedgerDatabase dbh) { + transactions.clear(); + String sql = "SELECT id, date, description FROM transactions"; + sql += " ORDER BY date desc, id desc"; + + try (SQLiteDatabase db = dbh.getReadableDatabase()) { + try (Cursor cursor = db.rawQuery(sql, null)) { + while (cursor.moveToNext()) { + LedgerTransaction tr = + new LedgerTransaction(cursor.getString(0), cursor.getString(1), + cursor.getString(2)); + // TODO: fill accounts and amounts + transactions.add(tr); + } + } + } + + } } diff --git a/app/src/main/java/net/ktnx/mobileledger/utils/Digest.java b/app/src/main/java/net/ktnx/mobileledger/utils/Digest.java new file mode 100644 index 00000000..c25ca59e --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/utils/Digest.java @@ -0,0 +1,89 @@ +/* + * Copyright © 2018 Damyan Ivanov. + * This file is part of Mobile-Ledger. + * Mobile-Ledger is free software: you can distribute it and/or modify it + * under the term of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your opinion), any later version. + * + * Mobile-Ledger is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License terms for details. + * + * You should have received a copy of the GNU General Public License + * along with Mobile-Ledger. If not, see . + */ + +package net.ktnx.mobileledger.utils; + +import java.nio.ByteBuffer; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class Digest { + private MessageDigest digest; + public Digest(String type) throws NoSuchAlgorithmException { + digest = MessageDigest.getInstance(type); + } + public static char[] hexDigitsFor(byte x) { + return hexDigitsFor((int) ((x<0) ? 256+x : x)); + } + public static char[] hexDigitsFor(int x) { + if ((x < 0) || (x > 255)) throw new ArithmeticException( + String.format("Hex digits must be between 0 and 255 (argument: %d)", x)); + char[] result = new char[]{0, 0}; + result[0] = hexDigitFor(x / 16); + result[1] = hexDigitFor(x % 16); + + return result; + } + public static char hexDigitFor(int x) { + if (x < 0) throw new ArithmeticException( + String.format("Hex digits can't be negative (argument: %d)", x)); + if (x < 10) return (char) ('0' + x); + if (x < 16) return (char) ('a' + x - 10); + throw new ArithmeticException( + String.format("Hex digits can't be greater than 15 (argument: %d)", x)); + } + public void update(byte input) { + digest.update(input); + } + public void update(byte[] input, int offset, int len) { + digest.update(input, offset, len); + } + public void update(byte[] input) { + digest.update(input); + } + public void update(ByteBuffer input) { + digest.update(input); + } + public byte[] digest() { + return digest.digest(); + } + public int digest(byte[] buf, int offset, int len) throws DigestException { + return digest.digest(buf, offset, len); + } + public byte[] digest(byte[] input) { + return digest.digest(input); + } + public String digestToHexString() { + byte[] digest = digest(); + StringBuilder result = new StringBuilder(); + for (int i = 0; i < getDigestLength(); i++) { + result.append(hexDigitsFor(digest[i])); + } + return result.toString(); + } + public void reset() { + digest.reset(); + } + public int getDigestLength() { + return digest.getDigestLength(); + } + @Override + public Object clone() throws CloneNotSupportedException { + return digest.clone(); + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/utils/MobileLedgerDatabase.java b/app/src/main/java/net/ktnx/mobileledger/utils/MobileLedgerDatabase.java index cae65b0f..e73c45a6 100644 --- a/app/src/main/java/net/ktnx/mobileledger/utils/MobileLedgerDatabase.java +++ b/app/src/main/java/net/ktnx/mobileledger/utils/MobileLedgerDatabase.java @@ -35,7 +35,7 @@ public class MobileLedgerDatabase extends SQLiteOpenHelper implements AutoClosea public static final String DB_NAME = "mobile-ledger.db"; public static final String ACCOUNTS_TABLE = "accounts"; public static final String DESCRIPTION_HISTORY_TABLE = "description_history"; - public static final int LATEST_REVISION = 7; + public static final int LATEST_REVISION = 8; private final Context mContext; diff --git a/app/src/main/res/layout/transaction_list_activity.xml b/app/src/main/res/layout/transaction_list_activity.xml index 1fd9399b..afb1d2ef 100644 --- a/app/src/main/res/layout/transaction_list_activity.xml +++ b/app/src/main/res/layout/transaction_list_activity.xml @@ -15,8 +15,7 @@ ~ along with Mobile-Ledger. If not, see . --> - - + diff --git a/app/src/main/res/layout/transaction_list_fragment.xml b/app/src/main/res/layout/transaction_list_fragment.xml index e76ff29b..6906ac1a 100644 --- a/app/src/main/res/layout/transaction_list_fragment.xml +++ b/app/src/main/res/layout/transaction_list_fragment.xml @@ -1,5 +1,4 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/raw/sql_8.sql b/app/src/main/res/raw/sql_8.sql new file mode 100644 index 00000000..0f065390 --- /dev/null +++ b/app/src/main/res/raw/sql_8.sql @@ -0,0 +1,2 @@ +alter table transactions add data_hash varchar; +delete from transactions; \ 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 c6d1d8a6..da63a4cb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -100,4 +100,5 @@ Cancel selection Confirm selectin Transactions + Last update: