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<LedgerTransaction> 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();
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());
+ }
+ }
}
--- /dev/null
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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<TransactionListAdapter.TransactionRowHolder> {
+ private List<LedgerTransaction> transactions;
+
+ TransactionListAdapter(List<LedgerTransaction> 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
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;
import java.util.regex.Pattern;
-class RetrieveTransactionsTask extends AsyncTask<RetrieveTransactionsTask.Params, Integer, Void> {
- 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("<tr class=\"title\" "
- + "id=\"transaction-(\\d+)\"><td class=\"date\"[^\\\"]*>([\\d.-]+)</td>");
+public class RetrieveTransactionsTask extends
+ AsyncTask<RetrieveTransactionsTask.Params, RetrieveTransactionsTask.Progress, Void> {
+ private static final Pattern transactionStartPattern = Pattern.compile("<tr class=\"title\" " +
+ "id=\"transaction-(\\d+)\"><td class=\"date\"[^\\\"]*>([\\d.-]+)</td>");
private static final Pattern transactionDescriptionPattern =
Pattern.compile("<tr class=\"posting\" title=\"(\\S+)\\s(.+)");
private static final Pattern transactionDetailsPattern =
Pattern.compile("^\\s+" + "(\\S[\\S\\s]+\\S)\\s\\s+([-+]?\\d[\\d,.]*)");
- protected WeakReference<Context> contextRef;
+ private static final Pattern endPattern = Pattern.compile("\\bid=\"addmodal\"");
+ protected WeakReference<TransactionListActivity> contextRef;
protected int error;
+ private boolean success;
+ public RetrieveTransactionsTask(WeakReference<TransactionListActivity> 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)) {
db.beginTransaction();
try {
String root = params[0].getAccountsRoot();
- if (root == null) db.execSQL("DELETE FROM transaction_history;");
- else {
- StringBuilder sql = new StringBuilder();
- sql.append("DELETE FROM transaction_history ");
- sql.append(
- "where id in (select transactions.id from transactions ");
- sql.append("join transaction_accounts ");
- sql.append(
- "on transactions.id=transaction_accounts.transaction_id ");
- sql.append("where transaction_accounts.account_name like ?||'%'");
- db.execSQL(sql.toString(), new String[]{root});
- }
+ db.execSQL("DELETE FROM transactions;");
int state = ParserState.EXPECTING_JOURNAL;
String line;
new BufferedReader(new InputStreamReader(resp, "UTF-8"));
int transactionCount = 0;
- String transactionId = null;
+ int transactionId = 0;
LedgerTransaction transaction = null;
+ LINES:
while ((line = buf.readLine()) != null) {
+ Matcher m;
+ L(String.format("State is %d", state));
switch (state) {
- case ParserState.EXPECTING_JOURNAL: {
- if (line.equals("<h2>General Journal</h2>"))
+ case ParserState.EXPECTING_JOURNAL:
+ if (line.equals("<h2>General Journal</h2>")) {
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);
transaction.add_item(
new LedgerTransactionItem(acc_name,
Float.valueOf(amount)));
+ L(String.format("%s = %s", acc_name, amount));
}
else throw new IllegalStateException(String.format(
- "Can't" + " parse transaction details"));
+ "Can't parse transaction details"));
}
- }
+ break;
default:
throw new RuntimeException(
String.format("Unknown " + "parser state %d",
}
return null;
}
- WeakReference<Context> 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 {
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<LedgerTransactionItem> comparator =
+ new Comparator<LedgerTransactionItem>() {
+ @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<LedgerTransactionItem> items;
-
+ private ArrayList<LedgerTransactionItem> 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<LedgerTransactionItem> getItemsIterator() {
return new Iterator<LedgerTransactionItem>() {
private int pointer = 0;
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);
}
}
}
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;
@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
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<LedgerTransaction> transactions;
+
+ public List<LedgerTransaction> 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);
+ }
+ }
+ }
+
+ }
}
--- /dev/null
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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();
+ }
+}
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;
~ along with Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
-->
-<android.support.design.widget.CoordinatorLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
</android.support.design.widget.AppBarLayout>
- <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".TransactionListActivity" />
+ <include layout="@layout/transaction_list_fragment" />
</android.support.design.widget.CoordinatorLayout>
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright © 2018 Damyan Ivanov.
~ This file is part of Mobile-Ledger.
~ Mobile-Ledger is free software: you can distribute it and/or modify it
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/transaction_list"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".ui.transaction_list.TransactionListFragment">
+ android:layout_height="wrap_content"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior"
+ tools:context=".TransactionListActivity">
- <TextView
- android:id="@+id/message"
- android:layout_width="wrap_content"
+ <LinearLayout
+ android:id="@+id/last_update_row"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:text="TransactionListFragment"
- app:layout_constraintBottom_toBottomOf="parent"
+ android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
+ app:layout_constraintTop_toTopOf="parent">
+ <TextView
+ android:id="@+id/transaction_last_update_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="8dp"
+ android:text="@string/transactions_last_update_label"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <TextView
+ android:id="@+id/transactions_last_update"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="TextView" />
+ </LinearLayout>
+
+ <ProgressBar
+ android:id="@+id/transaction_progress_bar"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:indeterminate="true"
+ android:indeterminateBehavior="cycle"
+ android:progress="40"
+ android:visibility="gone"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/last_update_row" />
+
+ <android.support.v4.widget.SwipeRefreshLayout
+ android:id="@+id/transaction_swipe"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ app:layout_constraintTop_toBottomOf="@+id/transaction_progress_bar">
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/transaction_root"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ </android.support.v4.widget.SwipeRefreshLayout>
</android.support.constraint.ConstraintLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+
+<!--
+ ~ 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 <https://www.gnu.org/licenses/>.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/transaction_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/transaction_list_fragment">
+
+ <!--android:button="@drawable/checkbox_star_black"-->
+
+ <TextView
+ android:id="@+id/transaction_row_description"
+ style="@style/account_summary_account_name"
+ android:text="Sample description goes here."
+ tools:ignore="HardcodedText" />
+
+ <TableLayout
+ android:id="@+id/transaction_row_acc_amounts"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent">
+
+ <TableRow>
+
+ <TextView
+ style="@style/account_summary_amounts"
+ android:text="Sample account name"
+ tools:ignore="HardcodedText" />
+
+ <TextView
+ style="@style/account_summary_amounts"
+ android:text="123,45\n678,90"
+ tools:ignore="HardcodedText" />
+ </TableRow>
+
+ <TableRow>
+
+ <TextView
+ style="@style/account_summary_amounts"
+ android:text="Sample account name"
+ tools:ignore="HardcodedText" />
+
+ <TextView
+ style="@style/account_summary_amounts"
+ android:text="123,45\n678,90"
+ tools:ignore="HardcodedText" />
+ </TableRow>
+ </TableLayout>
+</LinearLayout>
\ No newline at end of file
--- /dev/null
+alter table transactions add data_hash varchar;
+delete from transactions;
\ No newline at end of file
<string name="menu_acc_summary_cancel_selection_title">Cancel selection</string>
<string name="menu_acc_summary_confirm_selection_title">Confirm selectin</string>
<string name="title_activity_transaction_list">Transactions</string>
+ <string name="transactions_last_update_label">Last update:</string>
</resources>