somewhat complete profile implementation
authorDamyan Ivanov <dam+mobileledger@ktnx.net>
Mon, 7 Jan 2019 21:03:17 +0000 (21:03 +0000)
committerDamyan Ivanov <dam+mobileledger@ktnx.net>
Mon, 7 Jan 2019 21:03:17 +0000 (21:03 +0000)
most of the breakages are fixed, some minor remain

35 files changed:
app/build.gradle
app/src/main/AndroidManifest.xml
app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java
app/src/main/java/net/ktnx/mobileledger/async/SaveTransactionTask.java
app/src/main/java/net/ktnx/mobileledger/async/UpdateAccountsTask.java
app/src/main/java/net/ktnx/mobileledger/async/UpdateTransactionsTask.java
app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java
app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java
app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java
app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java
app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionActivity.java
app/src/main/java/net/ktnx/mobileledger/ui/activity/ProfileListActivity.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/ui/activity/SettingsActivity.java
app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailActivity.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java
app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListFragment.java
app/src/main/java/net/ktnx/mobileledger/utils/MLDB.java
app/src/main/java/net/ktnx/mobileledger/utils/NetworkUtil.java
app/src/main/res/drawable/ic_mode_edit_black_24dp.xml [new file with mode: 0644]
app/src/main/res/drawable/ic_view_list_black_24dp.xml [new file with mode: 0644]
app/src/main/res/layout-w900dp/profile_list.xml [new file with mode: 0644]
app/src/main/res/layout/activity_main.xml
app/src/main/res/layout/activity_profile_detail.xml [new file with mode: 0644]
app/src/main/res/layout/activity_profile_list.xml [new file with mode: 0644]
app/src/main/res/layout/profile_detail.xml [new file with mode: 0644]
app/src/main/res/layout/profile_list.xml [new file with mode: 0644]
app/src/main/res/layout/profile_list_content.xml [new file with mode: 0644]
app/src/main/res/raw/sql_13.sql [new file with mode: 0644]
app/src/main/res/raw/sql_14.sql [new file with mode: 0644]
app/src/main/res/raw/sql_15.sql [new file with mode: 0644]
app/src/main/res/values-bg/strings.xml
app/src/main/res/values/dimens.xml
app/src/main/res/values/strings.xml
app/src/main/res/xml/pref_backend.xml [deleted file]

index db13022..14d4d17 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2018 Damyan Ivanov.
+ * Copyright © 2019 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
@@ -51,6 +51,7 @@ dependencies {
     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'
+    implementation 'com.android.support:recyclerview-v7:28.0.0'
     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'
index 7171718..de59e4f 100644 (file)
@@ -13,7 +13,7 @@
   ~
   ~ You should have received a copy of the GNU General Public License
   ~ along with Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
--->
+  -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="net.ktnx.mobileledger">
 
                 android:name="android.support.PARENT_ACTIVITY"
                 android:value="net.ktnx.mobileledger.ui.activity.MainActivity" />
         </activity>
+        <activity
+            android:name=".ui.activity.ProfileListActivity"
+            android:label="@string/title_profile_list"
+            android:theme="@style/AppTheme.NoActionBar"></activity>
+        <activity
+            android:name=".ui.profiles.ProfileDetailActivity"
+            android:label="@string/title_profile_details"
+            android:parentActivityName=".ui.activity.ProfileListActivity"
+            android:theme="@style/AppTheme.NoActionBar">
+            <meta-data
+                android:name="android.support.PARENT_ACTIVITY"
+                android:value="net.ktnx.mobileledger.ui.activity.ProfileListActivity" />
+        </activity>
     </application>
 
 </manifest>
\ No newline at end of file
index 12e53cb..508190c 100644 (file)
@@ -160,7 +160,7 @@ public class RetrieveTransactionsTask
                                         acct_name = acct_name.replace("\"", "");
                                         L(String.format("found account: %s", acct_name));
 
-                                        addAccount(db, acct_name);
+                                        profile.storeAccount(acct_name);
                                         lastAccount = new LedgerAccount(acct_name);
                                         accountList.add(lastAccount);
 
@@ -181,11 +181,8 @@ public class RetrieveTransactionsTask
                                         if (currency == null) currency = "";
                                         value = value.replace(',', '.');
                                         L("curr=" + currency + ", value=" + value);
-                                        db.execSQL(
-                                                "insert or replace into account_values(account, currency, value, keep) values(?, ?, ?, 1);",
-                                                new Object[]{lastAccount.getName(), currency,
-                                                             Float.valueOf(value)
-                                                });
+                                        profile.storeAccountValue(lastAccount.getName(), currency,
+                                                Float.valueOf(value));
                                         lastAccount.addAmount(Float.parseFloat(value), currency);
                                     }
 
@@ -244,16 +241,21 @@ public class RetrieveTransactionsTask
                                     if (line.isEmpty()) {
                                         // transaction data collected
                                         if (transaction.existsInDb(db)) {
-                                            db.execSQL("UPDATE transactions SET keep = 1 WHERE id" +
-                                                       "=?", new Integer[]{transaction.getId()});
+                                            db.execSQL("UPDATE transactions SET keep = 1 WHERE " +
+                                                       "profile = ? and id=?",
+                                                    new Object[]{profile.getUuid(),
+                                                                 transaction.getId()
+                                                    });
                                             matchedTransactionsCount++;
 
                                             if (matchedTransactionsCount ==
                                                 MATCHING_TRANSACTIONS_LIMIT)
                                             {
                                                 db.execSQL("UPDATE transactions SET keep=1 WHERE " +
-                                                           "id < ?",
-                                                        new Integer[]{transaction.getId()});
+                                                           "profile = ? and id < ?",
+                                                        new Object[]{profile.getUuid(),
+                                                                     transaction.getId()
+                                                        });
                                                 success = true;
                                                 progress.setTotal(progress.getProgress());
                                                 publishProgress(progress);
@@ -261,12 +263,7 @@ public class RetrieveTransactionsTask
                                             }
                                         }
                                         else {
-                                            db.execSQL("DELETE from transactions WHERE id=?",
-                                                    new Integer[]{transaction.getId()});
-                                            db.execSQL("DELETE from transaction_accounts WHERE " +
-                                                       "transaction_id=?",
-                                                    new Integer[]{transaction.getId()});
-                                            transaction.insertInto(db);
+                                            profile.storeTransaction(transaction);
                                             matchedTransactionsCount = 0;
                                             progress.setTotal(maxTransactionId);
                                         }
@@ -275,6 +272,7 @@ public class RetrieveTransactionsTask
                                         L(String.format(
                                                 "transaction %s saved → expecting transaction",
                                                 transaction.getId()));
+                                        transaction.finishLoading();
                                         transactionList.add(transaction);
 
 // sounds like a good idea, but transaction-1 may not be the first one chronologically
@@ -291,11 +289,13 @@ public class RetrieveTransactionsTask
                                             String acc_name = m.group(1);
                                             String amount = m.group(2);
                                             String currency = m.group(3);
+                                            if (currency == null) currency = "";
                                             amount = amount.replace(',', '.');
                                             transaction.addAccount(
                                                     new LedgerTransactionAccount(acc_name,
                                                             Float.valueOf(amount), currency));
-                                            L(String.format("%s = %s", acc_name, amount));
+                                            L(String.format("%d: %s = %s", transaction.getId(),
+                                                    acc_name, amount));
                                         }
                                         else throw new IllegalStateException(
                                                 String.format("Can't parse transaction %d details",
@@ -311,12 +311,13 @@ public class RetrieveTransactionsTask
 
                         throwIfCancelled();
 
-                        db.execSQL("DELETE FROM transactions WHERE keep = 0");
+                        db.execSQL("DELETE FROM transactions WHERE profile=? AND keep = 0",
+                                new String[]{profile.getUuid()});
                         db.setTransactionSuccessful();
 
                         Log.d("db", "Updating transaction value stamp");
                         Date now = new Date();
-                        MLDB.set_option_value(MLDB.OPT_TRANSACTION_LIST_STAMP, now.getTime());
+                        profile.set_option_value(MLDB.OPT_LAST_SCRAPE, now.getTime());
                         Data.lastUpdateDate.set(now);
                         Data.transactions.set(transactionList);
                     }
@@ -350,17 +351,6 @@ public class RetrieveTransactionsTask
     private MainActivity getContext() {
         return contextRef.get();
     }
-    private void addAccount(SQLiteDatabase db, String name) {
-        do {
-            LedgerAccount acc = new LedgerAccount(name);
-            db.execSQL("update accounts set level = ?, keep = 1 where name = ?",
-                    new Object[]{acc.getLevel(), name});
-            db.execSQL("insert into accounts(name, name_upper, parent_name, level) select ?,?," +
-                       "?,? " + "where (select changes() = 0)",
-                    new Object[]{name, name.toUpperCase(), acc.getParentName(), acc.getLevel()});
-            name = acc.getParentName();
-        } while (name != null);
-    }
     private void throwIfCancelled() {
         if (isCancelled()) throw new OperationCanceledException(null);
     }
index 5ccbaac..913fddc 100644 (file)
 
 package net.ktnx.mobileledger.async;
 
-import android.content.SharedPreferences;
 import android.os.AsyncTask;
 import android.util.Log;
 
+import net.ktnx.mobileledger.model.Data;
 import net.ktnx.mobileledger.model.LedgerTransaction;
 import net.ktnx.mobileledger.model.LedgerTransactionAccount;
 import net.ktnx.mobileledger.utils.NetworkUtil;
@@ -48,11 +48,6 @@ public class SaveTransactionTask extends AsyncTask<LedgerTransaction, Void, Void
     private LedgerTransaction ltr;
     protected String error;
 
-    private SharedPreferences pref;
-    public void setPref(SharedPreferences pref) {
-        this.pref = pref;
-    }
-
     public SaveTransactionTask(TaskCallback callback) {
         task_callback = callback;
     }
@@ -140,7 +135,7 @@ public class SaveTransactionTask extends AsyncTask<LedgerTransaction, Void, Void
     protected Void doInBackground(LedgerTransaction... ledgerTransactions) {
         error = null;
         try {
-            backend_url = pref.getString("backend_url", "");
+            backend_url = Data.profile.get().getUrl();
             ltr = ledgerTransactions[0];
 
             int tried = 0;
index c63dd32..1ade3d5 100644 (file)
@@ -31,21 +31,22 @@ import java.util.ArrayList;
 public class UpdateAccountsTask extends AsyncTask<Boolean, Void, ArrayList<LedgerAccount>> {
     protected ArrayList<LedgerAccount> doInBackground(Boolean[] onlyStarred) {
         Data.backgroundTaskCount.incrementAndGet();
+        String profileUUID = Data.profile.get().getUuid();
         try {
             ArrayList<LedgerAccount> newList = new ArrayList<>();
 
-            String sql = "SELECT name, hidden FROM accounts";
-            if (onlyStarred[0]) sql += " WHERE hidden = 0";
+            String sql = "SELECT name, hidden FROM accounts WHERE profile = ?";
+            if (onlyStarred[0]) sql += " AND hidden = 0";
             sql += " ORDER BY name";
 
             SQLiteDatabase db = MLDB.getReadableDatabase();
-            try (Cursor cursor = db.rawQuery(sql, null)) {
+            try (Cursor cursor = db.rawQuery(sql, new String[]{profileUUID})) {
                 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()}))
+                            "SELECT value, currency FROM account_values WHERE profile = ? " +
+                            "AND account = ?", new String[]{profileUUID, acc.getName()}))
                     {
                         while (c2.moveToNext()) {
                             acc.addAmount(c2.getFloat(0), c2.getString(1));
index b3e2e0c..2251156 100644 (file)
@@ -32,6 +32,7 @@ import java.util.List;
 public class UpdateTransactionsTask extends AsyncTask<String, Void, List<LedgerTransaction>> {
     protected List<LedgerTransaction> doInBackground(String[] filterAccName) {
         Data.backgroundTaskCount.incrementAndGet();
+        String profile_uuid = Data.profile.get().getUuid();
         try {
             ArrayList<LedgerTransaction> newList = new ArrayList<>();
 
@@ -41,26 +42,29 @@ public class UpdateTransactionsTask extends AsyncTask<String, Void, List<LedgerT
             String sql;
             String[] params;
 
-            sql = "SELECT id FROM transactions  ORDER BY date desc, id desc";
-            params = null;
+            sql = "SELECT id FROM transactions WHERE profile=? ORDER BY date desc, id desc";
+            params = new String[]{profile_uuid};
 
             if (hasFilter) {
                 sql = "SELECT distinct tr.id from transactions tr JOIN transaction_accounts ta " +
-                      "ON ta.transaction_id=tr.id WHERE ta.account_name LIKE ?||'%' AND ta" +
+                      "ON ta.transaction_id=tr.id AND ta.profile=tr.profile WHERE tr.profile=? " +
+                      "and ta" + ".account_name LIKE ?||'%' AND ta" +
                       ".amount <> 0 ORDER BY tr.date desc, tr.id desc";
                 params = filterAccName;
             }
 
-            Log.d("tmp", sql);
+            Log.d("UTT", sql);
             SQLiteDatabase db = MLDB.getReadableDatabase();
             try (Cursor cursor = db.rawQuery(sql, params)) {
                 while (cursor.moveToNext()) {
                     if (isCancelled()) return null;
 
-                    newList.add(new LedgerTransaction(cursor.getInt(0)));
+                    int transaction_id = cursor.getInt(0);
+                    newList.add(new LedgerTransaction(transaction_id));
+                    Log.d("UTT", String.format("got transaction %d", transaction_id));
                 }
                 Data.transactions.set(newList);
-                Log.d("transactions", "transaction value updated");
+                Log.d("UTT", "transaction list value updated");
             }
 
             return newList;
index 9db9f29..3e5a45f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2018 Damyan Ivanov.
+ * Copyright © 2019 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
@@ -41,11 +41,15 @@ public class LedgerTransaction {
                     return Float.compare(o1.getAmount(), o2.getAmount());
                 }
             };
+    private String profile;
     private Integer id;
     private String date;
     private String description;
     private ArrayList<LedgerTransactionAccount> accounts;
+    private String dataHash;
+    private boolean dataLoaded;
     public LedgerTransaction(Integer id, String date, String description) {
+        this.profile = Data.profile.get().getUuid();
         this.id = id;
         this.date = date;
         this.description = description;
@@ -53,17 +57,15 @@ public class LedgerTransaction {
         this.dataHash = null;
         dataLoaded = false;
     }
-    private String dataHash;
-    private boolean dataLoaded;
-    public ArrayList<LedgerTransactionAccount> getAccounts() {
-        return accounts;
-    }
     public LedgerTransaction(String date, String description) {
         this(null, date, description);
     }
     public LedgerTransaction(int id) {
         this(id, null, null);
     }
+    public ArrayList<LedgerTransactionAccount> getAccounts() {
+        return accounts;
+    }
     public void addAccount(LedgerTransactionAccount item) {
         accounts.add(item);
         dataHash = null;
@@ -85,22 +87,12 @@ public class LedgerTransaction {
     public int getId() {
         return id;
     }
-    public void insertInto(SQLiteDatabase db) {
-        fillDataHash();
-        db.execSQL("INSERT INTO transactions(id, date, description, data_hash) values(?,?,?,?)",
-                new Object[]{id, date, description, dataHash});
-
-        for (LedgerTransactionAccount item : accounts) {
-            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() {
+    protected void fillDataHash() {
         if (dataHash != null) return;
         try {
             Digest sha = new Digest(DIGEST_TYPE);
             StringBuilder data = new StringBuilder();
+            data.append(profile);
             data.append(getId());
             data.append('\0');
             data.append(getDescription());
@@ -134,26 +126,37 @@ public class LedgerTransaction {
     public void loadData(SQLiteDatabase db) {
         if (dataLoaded) return;
 
-        try (Cursor cTr = db.rawQuery("SELECT date, description from transactions WHERE id=?",
-                new String[]{String.valueOf(id)}))
+        try (Cursor cTr = db
+                .rawQuery("SELECT date, description from transactions WHERE profile=? AND id=?",
+                        new String[]{profile, String.valueOf(id)}))
         {
             if (cTr.moveToFirst()) {
                 date = cTr.getString(0);
                 description = cTr.getString(1);
 
                 try (Cursor cAcc = db.rawQuery("SELECT account_name, amount, currency FROM " +
-                                               "transaction_accounts WHERE transaction_id = ?",
-                        new String[]{String.valueOf(id)}))
+                                               "transaction_accounts WHERE " +
+                                               "profile=? AND transaction_id = ?",
+                        new String[]{profile, String.valueOf(id)}))
                 {
                     while (cAcc.moveToNext()) {
+//                        Log.d("transactions",
+//                                String.format("Loaded %d: %s %1.2f %s", id, cAcc.getString(0),
+//                                        cAcc.getFloat(1), cAcc.getString(2)));
                         addAccount(new LedgerTransactionAccount(cAcc.getString(0), cAcc.getFloat(1),
                                 cAcc.getString(2)));
                     }
 
-                    dataLoaded = true;
+                    finishLoading();
                 }
             }
         }
 
     }
+    public String getDataHash() {
+        return dataHash;
+    }
+    public void finishLoading() {
+        dataLoaded = true;
+    }
 }
index af78c59..72097a3 100644 (file)
@@ -19,28 +19,39 @@ package net.ktnx.mobileledger.model;
 
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
+import android.util.Log;
 
 import net.ktnx.mobileledger.utils.MLDB;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.UUID;
 
 public final class MobileLedgerProfile {
     private String uuid;
     private String name;
     private String url;
-    private boolean useAuthentication;
+    private boolean authEnabled;
     private String authUserName;
     private String authPassword;
-    public MobileLedgerProfile(String uuid, String name, String url, boolean useAuthentication,
+    public MobileLedgerProfile(String uuid, String name, String url, boolean authEnabled,
                                String authUserName, String authPassword) {
         this.uuid = uuid;
         this.name = name;
         this.url = url;
-        this.useAuthentication = useAuthentication;
+        this.authEnabled = authEnabled;
         this.authUserName = authUserName;
         this.authPassword = authPassword;
     }
+    public MobileLedgerProfile(CharSequence name, CharSequence url, boolean authEnabled,
+                               CharSequence authUserName, CharSequence authPassword) {
+        this.uuid = String.valueOf(UUID.randomUUID());
+        this.name = String.valueOf(name);
+        this.url = String.valueOf(url);
+        this.authEnabled = authEnabled;
+        this.authUserName = String.valueOf(authUserName);
+        this.authPassword = String.valueOf(authPassword);
+    }
     public static List<MobileLedgerProfile> loadAllFromDB() {
         List<MobileLedgerProfile> result = new ArrayList<>();
         SQLiteDatabase db = MLDB.getReadableDatabase();
@@ -48,13 +59,21 @@ public final class MobileLedgerProfile {
                                          "auth_password FROM profiles", null))
         {
             while (cursor.moveToNext()) {
-                result.add(new MobileLedgerProfile(cursor.getString(0), cursor.getString(1),
-                        cursor.getString(2), cursor.getInt(3) == 1, cursor.getString(4),
+                result.add(new MobileLedgerProfile(cursor.getString(0), cursor.getString(1), cursor.getString(2), cursor.getInt(3) == 1, cursor.getString(4),
                         cursor.getString(5)));
             }
         }
         return result;
     }
+    public static List<MobileLedgerProfile> createInitialProfileList() {
+        List<MobileLedgerProfile> result = new ArrayList<>();
+        MobileLedgerProfile first =
+                new MobileLedgerProfile(UUID.randomUUID().toString(), "default", "", false, "", "");
+        first.storeInDB();
+        result.add(first);
+
+        return result;
+    }
     public static MobileLedgerProfile loadUUIDFromDB(String profileUUID) {
         SQLiteDatabase db = MLDB.getReadableDatabase();
         String name;
@@ -90,27 +109,53 @@ public final class MobileLedgerProfile {
     public String getName() {
         return name;
     }
+    public void setName(CharSequence text) {
+        setName(String.valueOf(text));
+    }
+    public void setName(String name) {
+        this.name = name;
+    }
     public String getUrl() {
         return url;
     }
-    public boolean isUseAuthentication() {
-        return useAuthentication;
+    public void setUrl(CharSequence text) {
+        setUrl(String.valueOf(text));
+    }
+    public void setUrl(String url) {
+        this.url = url;
+    }
+    public boolean isAuthEnabled() {
+        return authEnabled;
+    }
+    public void setAuthEnabled(boolean authEnabled) {
+        this.authEnabled = authEnabled;
     }
     public String getAuthUserName() {
         return authUserName;
     }
+    public void setAuthUserName(CharSequence text) {
+        setAuthUserName(String.valueOf(text));
+    }
+    public void setAuthUserName(String authUserName) {
+        this.authUserName = authUserName;
+    }
     public String getAuthPassword() {
         return authPassword;
     }
+    public void setAuthPassword(CharSequence text) {
+        setAuthPassword(String.valueOf(text));
+    }
+    public void setAuthPassword(String authPassword) {
+        this.authPassword = authPassword;
+    }
     public void storeInDB() {
         SQLiteDatabase db = MLDB.getWritableDatabase();
         db.beginTransaction();
         try {
             db.execSQL("REPLACE INTO profiles(uuid, name, url, use_authentication, auth_user, " +
                        "auth_password) VALUES(?, ?, ?, ?, ?, ?)",
-                    new Object[]{uuid, name, url, useAuthentication,
-                                 useAuthentication ? authUserName : null,
-                                 useAuthentication ? authPassword : null
+                    new Object[]{uuid, name, url, authEnabled, authEnabled ? authUserName : null,
+                                 authEnabled ? authPassword : null
                     });
             db.setTransactionSuccessful();
         }
@@ -118,4 +163,95 @@ public final class MobileLedgerProfile {
             db.endTransaction();
         }
     }
+    public void storeAccount(String name) {
+        SQLiteDatabase db = MLDB.getWritableDatabase();
+
+        do {
+            LedgerAccount acc = new LedgerAccount(name);
+            db.execSQL("replace into accounts(profile, name, name_upper, level, keep) values(?, " +
+                       "?, ?, ?, 1)",
+                    new Object[]{this.uuid, name, name.toUpperCase(), acc.getLevel()});
+            name = acc.getParentName();
+        } while (name != null);
+    }
+    public void storeAccountValue(String name, String currency, Float amount) {
+        SQLiteDatabase db = MLDB.getWritableDatabase();
+        db.execSQL("replace into account_values(profile, account, " +
+                   "currency, value, keep) values(?, ?, ?, ?, 1);",
+                new Object[]{uuid, name, currency, amount});
+    }
+    public void storeTransaction(LedgerTransaction tr) {
+        SQLiteDatabase db = MLDB.getWritableDatabase();
+        tr.fillDataHash();
+        db.execSQL("DELETE from transactions WHERE profile=? and id=?",
+                new Object[]{uuid, tr.getId()});
+        db.execSQL("DELETE from transaction_accounts WHERE profile = ? and transaction_id=?",
+                new Object[]{uuid, tr.getId()});
+
+        db.execSQL("INSERT INTO transactions(profile, id, date, description, data_hash, keep) " +
+                   "values(?,?,?,?,?,1)",
+                new Object[]{uuid, tr.getId(), tr.getDate(), tr.getDescription(), tr.getDataHash()
+                });
+
+        for (LedgerTransactionAccount item : tr.getAccounts()) {
+            db.execSQL("INSERT INTO transaction_accounts(profile, transaction_id, " +
+                       "account_name, amount, currency) values(?, ?, ?, ?, ?)",
+                    new Object[]{uuid, tr.getId(), item.getAccountName(), item.getAmount(),
+                                 item.getCurrency()
+                    });
+        }
+        Log.d("profile", String.format("Transaction %d stored", tr.getId()));
+    }
+    public String get_option_value(String name, String default_value) {
+        SQLiteDatabase db = MLDB.getReadableDatabase();
+        try (Cursor cursor = db.rawQuery("select value from options where profile = ? and name=?",
+                new String[]{uuid, name}))
+        {
+            if (cursor.moveToFirst()) {
+                String result = cursor.getString(0);
+
+                if (result == null) {
+                    Log.d("profile", "returning default value for " + name);
+                    result = default_value;
+                }
+                else Log.d("profile", String.format("option %s=%s", name, result));
+
+                return result;
+            }
+            else return default_value;
+        }
+        catch (Exception e) {
+            Log.d("db", "returning default value for " + name, e);
+            return default_value;
+        }
+    }
+    public long get_option_value(String name, long default_value) {
+        long longResult;
+        String result = get_option_value(name, "");
+        if ((result == null) || result.isEmpty()) {
+            Log.d("profile", String.format("Returning default value for option %s", name));
+            longResult = default_value;
+        }
+        else {
+            try {
+                longResult = Long.parseLong(result);
+                Log.d("profile", String.format("option %s=%s", name, result));
+            }
+            catch (Exception e) {
+                Log.d("profile", String.format("Returning default value for option %s", name), e);
+                longResult = default_value;
+            }
+        }
+
+        return longResult;
+    }
+    public void set_option_value(String name, String value) {
+        Log.d("profile", String.format("setting option %s=%s", name, value));
+        SQLiteDatabase db = MLDB.getWritableDatabase();
+        db.execSQL("insert or replace into options(profile, name, value) values(?, ?, ?);",
+                new String[]{uuid, name, value});
+    }
+    public void set_option_value(String name, long value) {
+        set_option_value(name, String.valueOf(value));
+    }
 }
index 830bf63..1f43c61 100644 (file)
@@ -174,6 +174,12 @@ public class AccountSummaryFragment extends MobileLedgerListFragment {
                 mActivity.runOnUiThread(() -> modelAdapter.notifyDataSetChanged());
             }
         });
+        Data.profile.addObserver(new Observer() {
+            @Override
+            public void update(Observable o, Object arg) {
+                mActivity.runOnUiThread(() -> model.scheduleAccountListReload(mActivity));
+            }
+        });
         update_account_table();
     }
     private void update_account_table() {
index 5615a2c..7386de0 100644 (file)
@@ -54,9 +54,9 @@ import java.lang.ref.WeakReference;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.util.Date;
+import java.util.List;
 import java.util.Observable;
 import java.util.Observer;
-import java.util.UUID;
 
 public class MainActivity extends AppCompatActivity {
     public MobileLedgerListFragment currentFragment = null;
@@ -108,28 +108,7 @@ public class MainActivity extends AppCompatActivity {
             }
         });
 
-        String profileUUID = MLDB.get_option_value(MLDB.OPT_PROFILE_UUID, null);
-        if (profileUUID == null) {
-            SharedPreferences backend = getSharedPreferences("backend", MODE_PRIVATE);
-            Log.d("profiles", "Migrating from preferences to profiles");
-            // migration to multiple profiles
-            profileUUID = UUID.randomUUID().toString();
-            MobileLedgerProfile profile = new MobileLedgerProfile(profileUUID, "default",
-                    backend.getString("backend_url", ""),
-                    backend.getBoolean("backend_use_http_auth", false),
-                    backend.getString("backend_auth_user", null),
-                    backend.getString("backend_auth_password", null));
-            profile.storeInDB();
-            SharedPreferences.Editor editor = backend.edit();
-            editor.clear();
-            editor.apply();
-            Data.profile.set(profile);
-            MLDB.set_option_value(MLDB.OPT_PROFILE_UUID, profileUUID);
-        }
-        else {
-            MobileLedgerProfile profile = MobileLedgerProfile.loadUUIDFromDB(profileUUID);
-            Data.profile.set(profile);
-        }
+        setupProfile();
 
         drawer = findViewById(R.id.drawer_layout);
         ActionBarDrawerToggle toggle =
@@ -204,6 +183,54 @@ public class MainActivity extends AppCompatActivity {
             }
         });
     }
+    private void setupProfile() {
+        List<MobileLedgerProfile> profiles = MobileLedgerProfile.loadAllFromDB();
+        MobileLedgerProfile profile = null;
+
+        String profileUUID = MLDB.get_option_value(MLDB.OPT_PROFILE_UUID, null);
+        if (profileUUID == null) {
+            if (profiles.isEmpty()) {
+                profiles = MobileLedgerProfile.createInitialProfileList();
+                profile = profiles.get(0);
+
+                SharedPreferences backend = getSharedPreferences("backend", MODE_PRIVATE);
+                Log.d("profiles", "Migrating from preferences to profiles");
+                // migration to multiple profiles
+                if (profile.getUrl().isEmpty()) {
+                    // no legacy config
+                    Intent intent = new Intent(this, ProfileListActivity.class);
+                    startActivity(intent);
+                }
+                profile.setUrl(backend.getString("backend_url", ""));
+                profile.setAuthEnabled(backend.getBoolean("backend_use_http_auth", false));
+                profile.setAuthUserName(backend.getString("backend_auth_user", null));
+                profile.setAuthPassword(backend.getString("backend_auth_password", null));
+                profile.storeInDB();
+                SharedPreferences.Editor editor = backend.edit();
+                editor.clear();
+                editor.apply();
+            }
+        }
+        else {
+            profile = MobileLedgerProfile.loadUUIDFromDB(profileUUID);
+        }
+
+        if (profile == null) profile = profiles.get(0);
+
+        if (profile == null) throw new AssertionError("profile must have a value");
+
+        Data.profile.set(profile);
+        MLDB.set_option_value(MLDB.OPT_PROFILE_UUID, profile.getUuid());
+
+        if (profile.getUrl().isEmpty()) {
+            Intent intent = new Intent(this, ProfileListActivity.class);
+            Bundle args = new Bundle();
+            args.putInt(ProfileListActivity.ARG_ACTION, ProfileListActivity.ACTION_EDIT_PROFILE);
+            args.putInt(ProfileListActivity.ARG_PROFILE_INDEX, 0);
+            intent.putExtras(args);
+            startActivity(intent, args);
+        }
+    }
     public void fab_new_transaction_clicked(View view) {
         Intent intent = new Intent(this, NewTransactionActivity.class);
         startActivity(intent);
@@ -332,7 +359,7 @@ public class MainActivity extends AppCompatActivity {
     }
     public void updateLastUpdateTextFromDB() {
         {
-            long last_update = MLDB.get_option_value(MLDB.OPT_TRANSACTION_LIST_STAMP, 0L);
+            long last_update = Data.profile.get().get_option_value(MLDB.OPT_LAST_SCRAPE, 0L);
 
             Log.d("transactions", String.format("Last update = %d", last_update));
             if (last_update == 0) {
@@ -382,6 +409,11 @@ public class MainActivity extends AppCompatActivity {
             progressBar.setIndeterminate(false);
         }
     }
+    public void nav_profiles_clicked(View view) {
+        drawer.closeDrawers();
+        Intent intent = new Intent(this, ProfileListActivity.class);
+        startActivity(intent);
+    }
     public class SectionsPagerAdapter extends FragmentPagerAdapter {
 
         public SectionsPagerAdapter(FragmentManager fm) {
index 43c10b2..bc904a0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2018 Damyan Ivanov.
+ * Copyright © 2019 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
@@ -19,7 +19,6 @@ package net.ktnx.mobileledger.ui.activity;
 
 import android.annotation.SuppressLint;
 import android.os.Bundle;
-import android.preference.PreferenceManager;
 import android.support.design.widget.BaseTransientBottomBar;
 import android.support.design.widget.Snackbar;
 import android.support.v4.app.DialogFragment;
@@ -43,13 +42,13 @@ import android.widget.TableLayout;
 import android.widget.TableRow;
 import android.widget.TextView;
 
-import net.ktnx.mobileledger.ui.OnSwipeTouchListener;
 import net.ktnx.mobileledger.R;
 import net.ktnx.mobileledger.async.SaveTransactionTask;
 import net.ktnx.mobileledger.async.TaskCallback;
 import net.ktnx.mobileledger.model.LedgerTransaction;
 import net.ktnx.mobileledger.model.LedgerTransactionAccount;
 import net.ktnx.mobileledger.ui.DatePickerFragment;
+import net.ktnx.mobileledger.ui.OnSwipeTouchListener;
 import net.ktnx.mobileledger.utils.MLDB;
 
 import java.util.Date;
@@ -91,7 +90,7 @@ public class NewTransactionActivity extends AppCompatActivity implements TaskCal
         });
         text_descr = findViewById(R.id.new_transaction_description);
         MLDB.hook_autocompletion_adapter(this, text_descr, MLDB.DESCRIPTION_HISTORY_TABLE,
-                "description");
+                "description", false);
         hook_text_change_listener(text_descr);
 
         progress = findViewById(R.id.save_transaction_progress);
@@ -103,7 +102,8 @@ public class NewTransactionActivity extends AppCompatActivity implements TaskCal
             AutoCompleteTextView acc_name_view = (AutoCompleteTextView) row.getChildAt(0);
             TextView amount_view = (TextView) row.getChildAt(1);
             hook_swipe_listener(row);
-            MLDB.hook_autocompletion_adapter(this, acc_name_view, MLDB.ACCOUNTS_TABLE, "name");
+            MLDB.hook_autocompletion_adapter(this, acc_name_view, MLDB.ACCOUNTS_TABLE, "name",
+                    true);
             hook_text_change_listener(acc_name_view);
             hook_text_change_listener(amount_view);
 //            Log.d("swipe", "hooked to row "+i);
@@ -139,7 +139,6 @@ public class NewTransactionActivity extends AppCompatActivity implements TaskCal
 
         saver = new SaveTransactionTask(this);
 
-        saver.setPref(PreferenceManager.getDefaultSharedPreferences(this));
         String date = text_date.getText().toString();
         if (date.isEmpty()) date = String.valueOf(new Date().getDate());
         LedgerTransaction tr = new LedgerTransaction(date, text_descr.getText().toString());
@@ -300,7 +299,7 @@ public class NewTransactionActivity extends AppCompatActivity implements TaskCal
         if (focus) acc.requestFocus();
 
         hook_swipe_listener(row);
-        MLDB.hook_autocompletion_adapter(this, acc, MLDB.ACCOUNTS_TABLE, "name");
+        MLDB.hook_autocompletion_adapter(this, acc, MLDB.ACCOUNTS_TABLE, "name", true);
         hook_text_change_listener(acc);
         hook_text_change_listener(amt);
     }
diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/ProfileListActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/ProfileListActivity.java
new file mode 100644 (file)
index 0000000..3cd6171
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ * Copyright © 2019 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.ui.activity;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.design.widget.FloatingActionButton;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.Toolbar;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CompoundButton;
+import android.widget.RadioButton;
+import android.widget.TextView;
+
+import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.model.Data;
+import net.ktnx.mobileledger.model.MobileLedgerProfile;
+import net.ktnx.mobileledger.ui.profiles.ProfileDetailActivity;
+import net.ktnx.mobileledger.ui.profiles.ProfileDetailFragment;
+import net.ktnx.mobileledger.utils.MLDB;
+
+import java.util.List;
+import java.util.Observable;
+import java.util.Observer;
+
+/**
+ * An activity representing a list of Profiles. This activity
+ * has different presentations for handset and tablet-size devices. On
+ * handsets, the activity presents a list of items, which when touched,
+ * lead to a {@link ProfileDetailActivity} representing
+ * item details. On tablets, the activity presents the list of items and
+ * item details side-by-side using two vertical panes.
+ */
+public class ProfileListActivity extends AppCompatActivity {
+
+    public static final String ARG_ACTION = "action";
+    public static final String ARG_PROFILE_INDEX = "profile_uuid";
+    public static final int ACTION_EDIT_PROFILE = 1;
+    public static final int ACTION_INVALID = -1;
+    /**
+     * Whether or not the activity is in two-pane mode, i.e. running on a tablet
+     * device.
+     */
+    private boolean mTwoPane;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_profile_list);
+
+        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+        toolbar.setTitle(getTitle());
+
+        RecyclerView recyclerView = findViewById(R.id.profile_list);
+        if (recyclerView == null) throw new AssertionError();
+        setupRecyclerView(recyclerView);
+
+        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
+        fab.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                ProfilesRecyclerViewAdapter adapter =
+                        (ProfilesRecyclerViewAdapter) recyclerView.getAdapter();
+                if (adapter != null) adapter.editProfile(recyclerView, null);
+            }
+        });
+
+        if (findViewById(R.id.profile_detail_container) != null) {
+            // The detail container view will be present only in the
+            // large-screen layouts (res/values-w900dp).
+            // If this view is present, then the
+            // activity should be in two-pane mode.
+            mTwoPane = true;
+        }
+
+        int action = getIntent().getIntExtra(ARG_ACTION, ACTION_INVALID);
+        if (action == ACTION_EDIT_PROFILE) {
+            Log.d("profiles", "got edit profile action");
+            int index = getIntent().getIntExtra(ARG_PROFILE_INDEX, -1);
+            if (index >= 0) {
+                List<MobileLedgerProfile> list = MobileLedgerProfile.loadAllFromDB();
+                if (index < list.size()) {
+                    ProfilesRecyclerViewAdapter adapter =
+                            (ProfilesRecyclerViewAdapter) recyclerView.getAdapter();
+                    if (adapter != null) adapter.editProfile(recyclerView, list.get(index));
+                }
+            }
+        }
+    }
+
+    private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
+        List<MobileLedgerProfile> list = MobileLedgerProfile.loadAllFromDB();
+        recyclerView.setAdapter(new ProfilesRecyclerViewAdapter(this, list, mTwoPane));
+    }
+
+    public static class ProfilesRecyclerViewAdapter
+            extends RecyclerView.Adapter<ProfilesRecyclerViewAdapter.ProfileListViewHolder> {
+
+        private final ProfileListActivity mParentActivity;
+        private final List<MobileLedgerProfile> mValues;
+        private final boolean mTwoPane;
+        private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                MobileLedgerProfile item = (MobileLedgerProfile) ((View) view.getParent()).getTag();
+                editProfile(view, item);
+            }
+        };
+        ProfilesRecyclerViewAdapter(ProfileListActivity parent, List<MobileLedgerProfile> items,
+                                    boolean twoPane) {
+            mValues = items;
+            mParentActivity = parent;
+            mTwoPane = twoPane;
+        }
+        private void editProfile(View view, MobileLedgerProfile item) {
+            if (mTwoPane) {
+                Bundle arguments = new Bundle();
+                arguments.putString(ProfileDetailFragment.ARG_ITEM_ID, item.getUuid());
+                ProfileDetailFragment fragment = new ProfileDetailFragment();
+                fragment.setArguments(arguments);
+                mParentActivity.getSupportFragmentManager().beginTransaction()
+                        .replace(R.id.profile_detail_container, fragment).commit();
+            }
+            else {
+                Context context = view.getContext();
+                Intent intent = new Intent(context, ProfileDetailActivity.class);
+                intent.putExtra(ProfileDetailFragment.ARG_ITEM_ID,
+                        (item == null) ? null : item.getUuid());
+
+                context.startActivity(intent);
+            }
+        }
+        @NonNull
+        @Override
+        public ProfileListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+            View view = LayoutInflater.from(parent.getContext())
+                    .inflate(R.layout.profile_list_content, parent, false);
+            ProfileListViewHolder holder = new ProfileListViewHolder(view);
+            Data.profile.addObserver(new Observer() {
+                @Override
+                public void update(Observable o, Object arg) {
+                    MobileLedgerProfile newProfile = Data.profile.get();
+                    MobileLedgerProfile profile = (MobileLedgerProfile) holder.itemView.getTag();
+                    holder.mRadioView.setChecked(
+                            newProfile != null && newProfile.getUuid().equals(profile.getUuid()));
+                }
+            });
+            return holder;
+        }
+        @Override
+        public void onBindViewHolder(@NonNull final ProfileListViewHolder holder, int position) {
+            final MobileLedgerProfile profile = mValues.get(position);
+            final MobileLedgerProfile currentProfile = Data.profile.get();
+            Log.d("profiles", String.format("pos %d: %s, current: %s", position, profile.getUuid(),
+                    currentProfile.getUuid()));
+            holder.mRadioView.setText(profile.getName());
+            holder.mRadioView.setChecked(profile.getUuid().equals(currentProfile.getUuid()));
+            holder.mRadioView
+                    .setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+                        @Override
+                        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                            if (!isChecked) return;
+                            MLDB.set_option_value(MLDB.OPT_PROFILE_UUID, profile.getUuid());
+                            Data.profile.set(profile);
+                        }
+                    });
+
+            holder.itemView.setTag(profile);
+            holder.mEditButton.setOnClickListener(mOnClickListener);
+
+        }
+        @Override
+        public int getItemCount() {
+            return mValues.size();
+        }
+        class ProfileListViewHolder extends RecyclerView.ViewHolder {
+            final RadioButton mRadioView;
+            final TextView mEditButton;
+
+            ProfileListViewHolder(View view) {
+                super(view);
+                mRadioView = view.findViewById(R.id.profile_list_radio);
+                mEditButton = view.findViewById(R.id.profile_list_edit_button);
+            }
+        }
+    }
+}
index 8f6ff2f..3645d82 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2018 Damyan Ivanov.
+ * Copyright © 2019 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
@@ -186,44 +186,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
      */
     protected boolean isValidFragment(String fragmentName) {
         return PreferenceFragment.class.getName().equals(fragmentName)
-                || BackendPreferenceFragment.class.getName().equals(fragmentName)
                 || DataSyncPreferenceFragment.class.getName().equals(fragmentName)
                 || NotificationPreferenceFragment.class.getName().equals(fragmentName)
                 || InterfacePreferenceFragment.class.getName().equals(fragmentName);
     }
 
-    /**
-     * This fragment shows general preferences only. It is used when the
-     * activity is showing a two-pane settings UI.
-     */
-    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
-    public static class BackendPreferenceFragment extends PreferenceFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            addPreferencesFromResource(R.xml.pref_backend);
-            setHasOptionsMenu(true);
-
-            // Bind the summaries of EditText/List/Dialog/Ringtone preferences
-            // to their values. When their values change, their summaries are
-            // updated to reflect the new value, per the Android Design
-            // guidelines.
-            bindPreferenceSummaryToValue(findPreference("backend_url"));
-            bindPreferenceSummaryToValue(findPreference("backend_auth_user"));
-
-        }
-
-        @Override
-        public boolean onOptionsItemSelected(MenuItem item) {
-            int id = item.getItemId();
-            if (id == android.R.id.home) {
-                startActivity(new Intent(getActivity(), SettingsActivity.class));
-                return true;
-            }
-            return super.onOptionsItemSelected(item);
-        }
-    }
-
     /**
      * This fragment shows general preferences only. It is used when the
      * activity is showing a two-pane settings UI.
diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailActivity.java
new file mode 100644 (file)
index 0000000..0909973
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Copyright © 2019 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.ui.profiles;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.Snackbar;
+import android.support.v7.widget.Toolbar;
+import android.view.View;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.app.ActionBar;
+import android.view.MenuItem;
+
+import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.ui.activity.ProfileListActivity;
+
+/**
+ * An activity representing a single Profile detail screen. This
+ * activity is only used on narrow width devices. On tablet-size devices,
+ * item details are presented side-by-side with a list of items
+ * in a {@link ProfileListActivity}.
+ */
+public class ProfileDetailActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_profile_detail);
+        Toolbar toolbar = (Toolbar) findViewById(R.id.detail_toolbar);
+        setSupportActionBar(toolbar);
+
+        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
+        fab.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                Snackbar.make(view, "Replace with your own detail action", Snackbar.LENGTH_LONG)
+                        .setAction("Action", null).show();
+            }
+        });
+
+        // Show the Up button in the action bar.
+        ActionBar actionBar = getSupportActionBar();
+        if (actionBar != null) {
+            actionBar.setDisplayHomeAsUpEnabled(true);
+        }
+
+        // savedInstanceState is non-null when there is fragment state
+        // saved from previous configurations of this activity
+        // (e.g. when rotating the screen from portrait to landscape).
+        // In this case, the fragment will automatically be re-added
+        // to its container so we don't need to manually add it.
+        // For more information, see the Fragments API guide at:
+        //
+        // http://developer.android.com/guide/components/fragments.html
+        //
+        if (savedInstanceState == null) {
+            // Create the detail fragment and add it to the activity
+            // using a fragment transaction.
+            Bundle arguments = new Bundle();
+            arguments.putString(ProfileDetailFragment.ARG_ITEM_ID,
+                    getIntent().getStringExtra(ProfileDetailFragment.ARG_ITEM_ID));
+            ProfileDetailFragment fragment = new ProfileDetailFragment();
+            fragment.setArguments(arguments);
+            getSupportFragmentManager().beginTransaction()
+                    .add(R.id.profile_detail_container, fragment).commit();
+        }
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        int id = item.getItemId();
+        if (id == android.R.id.home) {
+            // This ID represents the Home or Up button. In the case of this
+            // activity, the Up button is shown. For
+            // more details, see the Navigation pattern on Android Design:
+            //
+            // http://developer.android.com/design/patterns/navigation.html#up-vs-back
+            //
+            navigateUpTo(new Intent(this, ProfileListActivity.class));
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java
new file mode 100644 (file)
index 0000000..ff1b5e5
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * Copyright © 2019 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.ui.profiles;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.design.widget.CollapsingToolbarLayout;
+import android.support.design.widget.FloatingActionButton;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.model.Data;
+import net.ktnx.mobileledger.model.MobileLedgerProfile;
+import net.ktnx.mobileledger.ui.activity.ProfileListActivity;
+
+/**
+ * A fragment representing a single Profile detail screen.
+ * This fragment is either contained in a {@link ProfileListActivity}
+ * in two-pane mode (on tablets) or a {@link ProfileDetailActivity}
+ * on handsets.
+ */
+public class ProfileDetailFragment extends Fragment {
+    /**
+     * The fragment argument representing the item ID that this fragment
+     * represents.
+     */
+    public static final String ARG_ITEM_ID = "item_id";
+
+    /**
+     * The dummy content this fragment is presenting.
+     */
+    private MobileLedgerProfile mItem;
+    private TextView url;
+    private LinearLayout authParams;
+    private Switch useAuthentication;
+    private TextView userName;
+    private TextView password;
+    private FloatingActionButton fab;
+    private TextView profileName;
+
+    /**
+     * Mandatory empty constructor for the fragment manager to instantiate the
+     * fragment (e.g. upon screen orientation changes).
+     */
+    public ProfileDetailFragment() {
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if ((getArguments() != null) && getArguments().containsKey(ARG_ITEM_ID)) {
+            // Load the dummy content specified by the fragment
+            // arguments. In a real-world scenario, use a Loader
+            // to load content from a content provider.
+            String uuid = getArguments().getString(ARG_ITEM_ID);
+            if (uuid != null)
+                mItem = MobileLedgerProfile.loadUUIDFromDB(getArguments().getString(ARG_ITEM_ID));
+
+            Activity activity = this.getActivity();
+            if (activity == null) throw new AssertionError();
+            CollapsingToolbarLayout appBarLayout = activity.findViewById(R.id.toolbar_layout);
+            if (appBarLayout != null) {
+                if (mItem != null) appBarLayout.setTitle(mItem.getName());
+                else appBarLayout.setTitle(getResources().getString(R.string.new_profile_title));
+            }
+        }
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        fab = ((Activity) context).findViewById(R.id.fab);
+        fab.setOnClickListener(v -> {
+            if (mItem != null) {
+                mItem.setName(profileName.getText());
+                mItem.setUrl(url.getText());
+                mItem.setAuthEnabled(useAuthentication.isChecked());
+                mItem.setAuthUserName(userName.getText());
+                mItem.setAuthPassword(password.getText());
+                mItem.storeInDB();
+
+
+                if (mItem.getUuid().equals(Data.profile.get().getUuid())) {
+                    Data.profile.set(mItem);
+                }
+            }
+            else {
+                mItem = new MobileLedgerProfile(profileName.getText(), url.getText(),
+                        useAuthentication.isChecked(), userName.getText(), password.getText());
+                mItem.storeInDB();
+            }
+
+            Activity activity = getActivity();
+            if (activity != null) activity.finish();
+        });
+    }
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.profile_detail, container, false);
+
+        profileName = rootView.findViewById(R.id.profile_name);
+        url = rootView.findViewById(R.id.url);
+        authParams = rootView.findViewById(R.id.auth_params);
+        useAuthentication = rootView.findViewById(R.id.enable_http_auth);
+        userName = rootView.findViewById(R.id.auth_user_name);
+        password = rootView.findViewById(R.id.password);
+
+        useAuthentication.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            Log.d("profiles", isChecked ? "auth enabled " : "auth disabled");
+            authParams.setVisibility(isChecked ? View.VISIBLE : View.GONE);
+        });
+
+        if (mItem != null) {
+            profileName.setText(mItem.getName());
+            url.setText(mItem.getUrl());
+            useAuthentication.setChecked(mItem.isAuthEnabled());
+            authParams.setVisibility(mItem.isAuthEnabled() ? View.VISIBLE : View.GONE);
+            userName.setText(mItem.isAuthEnabled() ? mItem.getAuthUserName() : "");
+            password.setText(mItem.isAuthEnabled() ? mItem.getAuthPassword() : "");
+        }
+        else {
+            profileName.setText("");
+            url.setText("");
+            useAuthentication.setChecked(false);
+            authParams.setVisibility(View.GONE);
+            userName.setText("");
+            password.setText("");
+        }
+
+        return rootView;
+    }
+}
index bcdc198..b824f2c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2018 Damyan Ivanov.
+ * Copyright © 2019 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
@@ -50,7 +50,8 @@ public class TransactionListAdapter extends RecyclerView.Adapter<TransactionRowH
         // a bit longer
         if (tr == null) return;
 
-        Log.d("transactions", String.format("Filling position %d", position));
+        Log.d("transactions", String.format("Filling position %d with %d accounts", position,
+                tr.getAccounts().size()));
 
         TransactionLoader loader = new TransactionLoader();
         loader.execute(new TransactionLoaderParams(tr, holder, position, boldAccountName));
@@ -91,6 +92,8 @@ public class TransactionListAdapter extends RecyclerView.Adapter<TransactionRowH
 
             int rowIndex = 0;
             for (LedgerTransactionAccount acc : tr.getAccounts()) {
+                Log.d("tmp", String.format("publishing tr %d acc %s %1.2f", tr.getId(),
+                        acc.getAccountName(), acc.getAmount()));
                 publishProgress(new TransactionLoaderStep(p[0].holder, acc, rowIndex++,
                         p[0].boldAccountName));
             }
@@ -156,6 +159,9 @@ public class TransactionListAdapter extends RecyclerView.Adapter<TransactionRowH
                     accName.setText(acc.getAccountName());
                     accAmount.setText(acc.toString());
 
+                    Log.d("tmp", String.format("showing acc row %d: %s %1.2f", rowIndex,
+                            acc.getAccountName(), acc.getAmount()));
+
                     String boldAccountName = step.getBoldAccountName();
                     if ((boldAccountName != null) && boldAccountName.equals(acc.getAccountName())) {
                         accName.setTypeface(null, Typeface.BOLD);
index b9a3b83..dcad525 100644 (file)
@@ -158,7 +158,7 @@ public class TransactionListFragment extends MobileLedgerListFragment {
         accNameFilter = mActivity.findViewById(R.id.transaction_filter_account_name);
 
         TransactionListFragment me = this;
-        MLDB.hook_autocompletion_adapter(mActivity, accNameFilter, "accounts", "name");
+        MLDB.hook_autocompletion_adapter(mActivity, accNameFilter, "accounts", "name", true);
         accNameFilter.setOnItemClickListener(new AdapterView.OnItemClickListener() {
             @Override
             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
@@ -177,6 +177,16 @@ public class TransactionListFragment extends MobileLedgerListFragment {
             Log.d("flow", String.format("Account filter set to '%s'", mShowOnlyAccountName));
         }
 
+        Data.profile.addObserver(new Observer() {
+            @Override
+            public void update(Observable o, Object arg) {
+                mActivity.runOnUiThread(() -> {
+                    Log.d("transactions", "requesting list reload");
+                    TransactionListViewModel.scheduleTransactionListReload(mActivity);
+                });
+            }
+        });
+
         TransactionListViewModel.scheduleTransactionListReload(mActivity);
         TransactionListViewModel.updating.addObserver(new Observer() {
             @Override
index d6fa777..b863cf6 100644 (file)
@@ -33,6 +33,8 @@ import android.widget.AutoCompleteTextView;
 import android.widget.FilterQueryProvider;
 import android.widget.SimpleCursorAdapter;
 
+import net.ktnx.mobileledger.model.Data;
+
 import org.jetbrains.annotations.NonNls;
 
 import java.io.BufferedReader;
@@ -47,10 +49,10 @@ import static net.ktnx.mobileledger.utils.MLDB.DatabaseMode.WRITE;
 public final class MLDB {
     public static final String ACCOUNTS_TABLE = "accounts";
     public static final String DESCRIPTION_HISTORY_TABLE = "description_history";
-    public static final String OPT_TRANSACTION_LIST_STAMP = "transaction_list_last_update";
-    public static final String OPT_LAST_REFRESH = "last_refresh";
+    public static final String OPT_LAST_SCRAPE = "last_scrape";
     @NonNls
     public static final String OPT_PROFILE_UUID = "profile_uuid";
+    private static final String NO_PROFILE = "-";
     private static MobileLedgerDatabase helperForReading, helperForWriting;
     private static Application context;
     private static void checkState() {
@@ -98,8 +100,8 @@ public final class MLDB {
     static public String get_option_value(String name, String default_value) {
         Log.d("db", "about to fetch option " + name);
         SQLiteDatabase db = getReadableDatabase();
-        try (Cursor cursor = db
-                .rawQuery("select value from options where name=?", new String[]{name}))
+        try (Cursor cursor = db.rawQuery("select value from options where profile = ? and name=?",
+                new String[]{NO_PROFILE, name}))
         {
             if (cursor.moveToFirst()) {
                 String result = cursor.getString(0);
@@ -117,18 +119,19 @@ public final class MLDB {
         }
     }
     static public void set_option_value(String name, String value) {
-        Log.d("db", "setting option " + name + "=" + value);
-        SQLiteDatabase db = getWritableDatabase();
-        db.execSQL("insert or replace into options(name, value) values(?, ?);",
-                new String[]{name, value});
+        Log.d("option", String.format("%s := %s", name, value));
+        SQLiteDatabase db = MLDB.getWritableDatabase();
+        db.execSQL("insert or replace into options(profile, name, value) values(?, ?, ?);",
+                new String[]{NO_PROFILE, name, value});
     }
     static public void set_option_value(String name, long value) {
-        set_option_value(name, String.valueOf(value));
+        set_option_value(name, value);
     }
     @TargetApi(Build.VERSION_CODES.N)
     public static void hook_autocompletion_adapter(final Context context,
                                                    final AutoCompleteTextView view,
-                                                   final String table, final String field) {
+                                                   final String table, final String field,
+                                                   final boolean profileSpecific) {
         String[] from = {field};
         int[] to = {android.R.id.text1};
         SimpleCursorAdapter adapter =
@@ -146,15 +149,30 @@ public final class MLDB {
                 String[] col_names = {FontsContract.Columns._ID, field};
                 MatrixCursor c = new MatrixCursor(col_names);
 
+                String sql;
+                String[] params;
+                if (profileSpecific) {
+                    sql = String.format("SELECT %s as a, case when %s_upper LIKE ?||'%%' then 1 " +
+                                        "WHEN %s_upper LIKE '%%:'||?||'%%' then 2 " +
+                                        "WHEN %s_upper LIKE '%% '||?||'%%' then 3 " +
+                                        "else 9 end " + "FROM %s " +
+                                        "WHERE profile=? AND %s_upper LIKE '%%'||?||'%%' " +
+                                        "ORDER BY 2, 1;", field, field, field, field, table, field);
+                    params = new String[]{str, str, str, Data.profile.get().getUuid(), str};
+                }
+                else {
+                    sql = String.format("SELECT %s as a, case when %s_upper LIKE ?||'%%' then 1 " +
+                                        "WHEN %s_upper LIKE '%%:'||?||'%%' then 2 " +
+                                        "WHEN %s_upper LIKE '%% '||?||'%%' then 3 " +
+                                        "else 9 end " + "FROM %s " +
+                                        "WHERE %s_upper LIKE '%%'||?||'%%' " + "ORDER BY 2, 1;",
+                            field, field, field, field, table, field);
+                    params = new String[]{str, str, str, str};
+                }
+                Log.d("autocompletion", sql);
                 SQLiteDatabase db = MLDB.getReadableDatabase();
 
-                try (Cursor matches = db.rawQuery(String.format(
-                        "SELECT %s as a, case when %s_upper LIKE ?||'%%' then 1 " +
-                        "WHEN %s_upper LIKE '%%:'||?||'%%' then 2 " +
-                        "WHEN %s_upper LIKE '%% '||?||'%%' then 3 " + "else 9 end " + "FROM %s " +
-                        "WHERE %s_upper LIKE '%%'||?||'%%' " + "ORDER BY 2, 1;", field, field,
-                        field, field, table, field), new String[]{str, str, str, str}))
-                {
+                try (Cursor matches = db.rawQuery(sql, params)) {
                     int i = 0;
                     while (matches.moveToNext()) {
                         String match = matches.getString(0);
@@ -177,8 +195,7 @@ public final class MLDB {
         MLDB.context = context;
     }
     public static void done() {
-        if (helperForReading != null)
-            helperForReading.close();
+        if (helperForReading != null) helperForReading.close();
 
         if ((helperForWriting != helperForReading) && (helperForWriting != null))
             helperForWriting.close();
@@ -188,7 +205,7 @@ public final class MLDB {
 
 class MobileLedgerDatabase extends SQLiteOpenHelper implements AutoCloseable {
     public static final String DB_NAME = "mobile-ledger.db";
-    public static final int LATEST_REVISION = 11;
+    public static final int LATEST_REVISION = 15;
 
     private final Application mContext;
 
@@ -225,8 +242,25 @@ class MobileLedgerDatabase extends SQLiteOpenHelper implements AutoCloseable {
             BufferedReader reader = new BufferedReader(isr);
 
             String line;
+            int line_no = 1;
             while ((line = reader.readLine()) != null) {
-                db.execSQL(line);
+                if (line.startsWith("--")) {
+                    line_no++;
+                    continue;
+                }
+                if (line.isEmpty()) {
+                    line_no++;
+                    continue;
+                }
+                try {
+                    db.execSQL(line);
+                }
+                catch (Exception e) {
+                    throw new RuntimeException(
+                            String.format("Error applying revision %d, line %d", rev_no, line_no),
+                            e);
+                }
+                line_no++;
             }
 
             db.setTransactionSuccessful();
index 8641751..efb1ac9 100644 (file)
@@ -32,7 +32,7 @@ public final class NetworkUtil {
     public static HttpURLConnection prepare_connection(String path) throws IOException {
         MobileLedgerProfile profile = Data.profile.get();
         final String backend_url = profile.getUrl();
-        final boolean use_auth = profile.isUseAuthentication();
+        final boolean use_auth = profile.isAuthEnabled();
         Log.d("network", "Connecting to " + backend_url + "/" + path);
         HttpURLConnection http =
                 (HttpURLConnection) new URL(backend_url + "/" + path).openConnection();
diff --git a/app/src/main/res/drawable/ic_mode_edit_black_24dp.xml b/app/src/main/res/drawable/ic_mode_edit_black_24dp.xml
new file mode 100644 (file)
index 0000000..eee9560
--- /dev/null
@@ -0,0 +1,21 @@
+<!--
+  ~ Copyright Google Inc.
+  ~
+  ~ Licensed under the Apache License, version 2.0 ("the License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the license at:
+  ~
+  ~   https://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distribution under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector android:height="24dp" android:tint="#313131"
+    android:viewportHeight="24.0" android:viewportWidth="24.0"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FF000000" android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_view_list_black_24dp.xml b/app/src/main/res/drawable/ic_view_list_black_24dp.xml
new file mode 100644 (file)
index 0000000..8300027
--- /dev/null
@@ -0,0 +1,21 @@
+<!--
+  ~ Copyright Google Inc.
+  ~
+  ~ Licensed under the Apache License, version 2.0 ("the License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the license at:
+  ~
+  ~   https://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distribution under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector android:height="24dp" android:tint="#313131"
+    android:viewportHeight="24.0" android:viewportWidth="24.0"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FF000000" android:pathData="M4,14h4v-4L4,10v4zM4,19h4v-4L4,15v4zM4,9h4L8,5L4,5v4zM9,14h12v-4L9,10v4zM9,19h12v-4L9,15v4zM9,5v4h12L21,5L9,5z"/>
+</vector>
diff --git a/app/src/main/res/layout-w900dp/profile_list.xml b/app/src/main/res/layout-w900dp/profile_list.xml
new file mode 100644 (file)
index 0000000..5d21e30
--- /dev/null
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright © 2019 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:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginLeft="16dp"
+    android:layout_marginRight="16dp"
+    android:baselineAligned="false"
+    android:divider="?android:attr/dividerHorizontal"
+    android:orientation="horizontal"
+    android:showDividers="middle"
+    tools:context=".ui.activity.ProfileListActivity">
+
+    <!--
+    This layout is a two-pane layout for the Profiles
+    master/detail flow.
+    
+    -->
+
+    <android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools"
+        android:id="@+id/profile_list"
+        android:name="net.ktnx.mobileledger.ui.activity.ProfileListFragment"
+        android:layout_width="@dimen/item_width"
+        android:layout_height="match_parent"
+        android:layout_marginLeft="16dp"
+        android:layout_marginRight="16dp"
+        app:layoutManager="LinearLayoutManager"
+        tools:context="net.ktnx.mobileledger.ui.activity.ProfileListActivity"
+        tools:listitem="@layout/profile_list_content" />
+
+    <FrameLayout
+        android:id="@+id/profile_detail_container"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="3" />
+
+</LinearLayout>
\ No newline at end of file
index dacbcea..833b5fb 100644 (file)
                         android:showDividers="beginning"
                         app:layout_constraintBottom_toBottomOf="parent">
 
+                        <TextView
+                            android:id="@+id/nav_profiles"
+                            style="@style/nav_button"
+                            android:layout_width="match_parent"
+                            android:layout_weight="1"
+                            android:drawableStart="@drawable/ic_view_list_black_24dp"
+                            android:onClick="nav_profiles_clicked"
+                            android:text="@string/profiles" />
+
                         <TextView
                             android:id="@+id/textView2"
                             style="@style/nav_button"
diff --git a/app/src/main/res/layout/activity_profile_detail.xml b/app/src/main/res/layout/activity_profile_detail.xml
new file mode 100644 (file)
index 0000000..321c4d0
--- /dev/null
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright © 2019 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/>.
+  -->
+
+<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:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    tools:context=".ui.profiles.ProfileDetailActivity"
+    tools:ignore="MergeRootFrame">
+
+    <android.support.design.widget.AppBarLayout
+        android:id="@+id/app_bar"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/app_bar_height"
+        android:fitsSystemWindows="true"
+        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
+
+        <android.support.design.widget.CollapsingToolbarLayout
+            android:id="@+id/toolbar_layout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:fitsSystemWindows="true"
+            app:contentScrim="?attr/colorPrimary"
+            app:layout_scrollFlags="scroll|exitUntilCollapsed"
+            app:toolbarId="@+id/toolbar">
+
+            <android.support.v7.widget.Toolbar
+                android:id="@+id/detail_toolbar"
+                android:layout_width="match_parent"
+                android:layout_height="?attr/actionBarSize"
+                app:layout_collapseMode="pin"
+                app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
+
+        </android.support.design.widget.CollapsingToolbarLayout>
+
+    </android.support.design.widget.AppBarLayout>
+
+    <android.support.v4.widget.NestedScrollView
+        android:id="@+id/profile_detail_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical|start"
+        android:layout_margin="@dimen/fab_margin"
+        app:layout_anchor="@+id/profile_detail_container"
+        app:layout_anchorGravity="top|end"
+        app:srcCompat="@drawable/ic_save_white_24dp" />
+
+</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_profile_list.xml b/app/src/main/res/layout/activity_profile_list.xml
new file mode 100644 (file)
index 0000000..65a90c6
--- /dev/null
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright © 2019 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/>.
+  -->
+
+<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:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    tools:context=".ui.activity.ProfileListActivity">
+
+    <android.support.design.widget.AppBarLayout
+        android:id="@+id/app_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="@style/AppTheme.AppBarOverlay">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"
+            app:popupTheme="@style/AppTheme.PopupOverlay" />
+
+    </android.support.design.widget.AppBarLayout>
+
+    <FrameLayout
+        android:id="@+id/frameLayout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior">
+
+        <include layout="@layout/profile_list" />
+    </FrameLayout>
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|end"
+        android:layout_margin="@dimen/fab_margin"
+        app:srcCompat="@drawable/svg_thick_plus_white" />
+
+
+</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/profile_detail.xml b/app/src/main/res/layout/profile_detail.xml
new file mode 100644 (file)
index 0000000..39eeb50
--- /dev/null
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright © 2019 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/profile_detail"
+    style="?android:attr/textAppearanceLarge"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:padding="16dp"
+    android:textIsSelectable="true"
+    tools:context=".ui.profiles.ProfileDetailFragment">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="16dp"
+        android:orientation="vertical">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="Profile name" />
+
+        <EditText
+            android:id="@+id/profile_name"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ems="10"
+            android:inputType="textPersonName"
+            android:text="Name" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="16dp"
+        android:orientation="vertical">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="URL" />
+
+        <EditText
+            android:id="@+id/url"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ems="10"
+            android:inputType="textUri"
+            android:text="https://server/url" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <Switch
+            android:id="@+id/enable_http_auth"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="16dp"
+            android:text="@string/pref_title_use_http_auth" />
+
+        <LinearLayout
+            android:id="@+id/auth_params"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:paddingStart="8dp">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="16dp"
+                android:orientation="vertical">
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/pref_title_backend_auth_user" />
+
+                <EditText
+                    android:id="@+id/auth_user_name"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:ems="10"
+                    android:inputType="textPersonName"
+                    android:text="Name" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/pref_title_backend_auth_password" />
+
+                <EditText
+                    android:id="@+id/password"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:ems="10"
+                    android:inputType="textPassword" />
+
+            </LinearLayout>
+        </LinearLayout>
+
+
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/profile_list.xml b/app/src/main/res/layout/profile_list.xml
new file mode 100644 (file)
index 0000000..cb915aa
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright © 2019 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/>.
+  -->
+
+<android.support.v7.widget.RecyclerView 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:id="@+id/profile_list"
+    android:name="net.ktnx.mobileledger.ui.activity.ProfileListFragment"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginLeft="16dp"
+    android:layout_marginRight="16dp"
+    app:layoutManager="LinearLayoutManager"
+    tools:context=".ui.activity.ProfileListActivity"
+    tools:listitem="@layout/profile_list_content" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/profile_list_content.xml b/app/src/main/res/layout/profile_list_content.xml
new file mode 100644 (file)
index 0000000..68fa9a9
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright © 2019 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/>.
+  -->
+
+<android.support.constraint.ConstraintLayout 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:id="@+id/profile_list"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_horizontal"
+    android:foregroundGravity="center_vertical"
+    android:paddingVertical="@dimen/nav_header_vertical_spacing">
+
+    <TextView
+        android:id="@+id/profile_list_edit_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="9"
+        android:drawableStart="@drawable/ic_mode_edit_black_24dp"
+        android:padding="8dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <RadioButton
+        android:id="@+id/profile_list_radio"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="9"
+        android:text="Profile name"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/profile_list_edit_button"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:ignore="HardcodedText" />
+
+
+</android.support.constraint.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/raw/sql_13.sql b/app/src/main/res/raw/sql_13.sql
new file mode 100644 (file)
index 0000000..e8038e9
--- /dev/null
@@ -0,0 +1,22 @@
+delete from options where name='transaction_list_last_update';
+delete from options where name='last_refresh';
+alter table options add profile varchar;
+drop index idx_options_name;
+create unique index un_options on options(profile,name);
+--
+drop table account_values;
+create table account_values(profile varchar not null, account varchar not null, currency varchar not null default '', keep boolean, value decimal not null );
+create unique index un_account_values on account_values(profile,account,currency);
+--
+drop table accounts;
+create table accounts(profile varchar not null, name varchar not null, name_upper varchar not null, hidden boolean not null default 0, keep boolean not null default 0, level integer not null, parent_name varchar);
+create unique index un_accounts on accounts(profile, name);
+--
+drop table transaction_accounts;
+drop table transactions;
+--
+create table transactions(id integer not null, data_hash varchar not null, date varchar not null, description varchar not null, keep boolean not null default 0);
+create unique index un_transactions_id on transactions(id);
+create unique index un_transactions_data_hash on transactions(data_hash);
+--
+create table transaction_accounts(profile varchar not null, transaction_id integer not null, account_name varchar not null, currency varchar not null default '', amount decimal not null, constraint fk_transaction_accounts_acc foreign key(profile,account_name) references accounts(profile,account_name), constraint fk_transaction_accounts_trn foreign key(transaction_id) references transactions(id));
\ No newline at end of file
diff --git a/app/src/main/res/raw/sql_14.sql b/app/src/main/res/raw/sql_14.sql
new file mode 100644 (file)
index 0000000..ddc634e
--- /dev/null
@@ -0,0 +1,8 @@
+drop table transaction_accounts;
+drop table transactions;
+--
+create table transactions(profile varchar not null, id integer not null, data_hash varchar not null, date varchar not null, description varchar not null, keep boolean not null default 0);
+create unique index un_transactions_id on transactions(profile,id);
+create unique index un_transactions_data_hash on transactions(profile,data_hash);
+--
+create table transaction_accounts(profile varchar not null, transaction_id integer not null, account_name varchar not null, currency varchar not null default '', amount decimal not null, constraint fk_transaction_accounts_acc foreign key(profile,account_name) references accounts(profile,account_name), constraint fk_transaction_accounts_trn foreign key(profile, transaction_id) references transactions(profile,id));
\ No newline at end of file
diff --git a/app/src/main/res/raw/sql_15.sql b/app/src/main/res/raw/sql_15.sql
new file mode 100644 (file)
index 0000000..56eb75e
--- /dev/null
@@ -0,0 +1,10 @@
+delete from options where profile is null and name='last_scrape';
+create table new_options(profile varchar not null, name varchar not null, value varchar);
+
+insert into new_options(profile, name, value) select distinct '-', o.name, (select o2.value from options o2 where o2.name=o.name and o2.profile is null) from options o where o.profile is null;
+insert into new_options(profile, name, value) select distinct o.profile, o.name, (select o2.value from options o2 where o2.name=o.name and o2.profile=o.profile) from options o where o.profile is not null;
+drop table options;
+create table options(profile varchar not null, name varchar not null, value varchar);
+create unique index un_options on options(profile,name);
+insert into options(profile,name,value) select profile,name,value from new_options;
+drop table new_options;
\ No newline at end of file
index 7d3e1df..c81a8f0 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright © 2018 Damyan Ivanov.
+  ~ Copyright © 2019 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
     <string name="err_bad_auth">Грешно потребителско име или парола</string>
     <string name="new_transaction_amount_hint">0,00</string>
     <string name="transactions_last_update_label">Данни към:</string>
+    <string name="title_profile_list">Профили</string>
+    <string name="profiles">Профили</string>
+    <string name="title_profile_details">Данни за профила</string>
+    <string name="transaction_last_update_never">никога</string>
+    <string name="err_cancelled">Операцията е прекъсната</string>
+    <string name="title_activity_transaction_list">Трансакции</string>
+    <string name="err_http_error">Грешка в HTTP</string>
+    <string name="new_profile_title">Нов профил</string>
 </resources>
\ No newline at end of file
index d99c74e..d00fc16 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-  ~ Copyright © 2018 Damyan Ivanov.
+  ~ Copyright © 2019 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
@@ -21,4 +21,7 @@
     <dimen name="activity_vertical_margin">16dp</dimen>
     <dimen name="nav_header_vertical_spacing">8dp</dimen>
     <dimen name="fab_margin">16dp</dimen>
+    <dimen name="app_bar_height">200dp</dimen>
+    <dimen name="item_width">200dp</dimen>
+    <dimen name="text_margin">16dp</dimen>
 </resources>
\ No newline at end of file
index 9cb2cd3..8c1682a 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-  ~ Copyright © 2018 Damyan Ivanov.
+  ~ Copyright © 2019 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
     <string name="pref_show_only_starred_on_summary">Only starred accounts are shown</string>
     <string name="menu_acc_summary_hide_selected_title">Hide selected accounts</string>
     <string name="menu_acc_summary_cancel_selection_title">Cancel selection</string>
-    <string name="menu_acc_summary_confirm_selection_title">Confirm selectin</string>
+    <string name="menu_acc_summary_confirm_selection_title">Confirm selectiоn</string>
     <string name="title_activity_transaction_list">Transactions</string>
     <string name="transactions_last_update_label">Last update:</string>
     <string name="transaction_last_update_never">never</string>
     <string name="err_cancelled">Operation cancelled</string>
+    <string name="title_profile_list">Profiles</string>
+    <string name="title_profile_details">Profile Details</string>
+    <string name="profiles">Profiles</string>
+    <string name="new_profile_title" type="id">New profile</string>
 </resources>
diff --git a/app/src/main/res/xml/pref_backend.xml b/app/src/main/res/xml/pref_backend.xml
deleted file mode 100644 (file)
index b076364..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-<!--
-  ~ 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/>.
-  -->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <!-- NOTE: EditTextPreference accepts EditText attributes. -->
-    <!-- NOTE: EditTextPreference's summary should be set to its value by the activity code. -->
-    <EditTextPreference
-        android:id="@+id/pref_backend_url"
-        android:capitalize="words"
-        android:defaultValue="@string/pref_default_backend_url"
-        android:inputType="textUri"
-        android:key="backend_url"
-        android:maxLines="1"
-        android:selectAllOnFocus="true"
-        android:singleLine="true"
-        android:title="@string/pref_title_backend_url" />
-
-    <SwitchPreference
-        android:id="@+id/pref_backend_use_http_auth"
-        android:defaultValue="false"
-        android:disableDependentsState="false"
-        android:key="backend_use_http_auth"
-        android:summaryOff="@string/pref_description_use_http_auth_off"
-        android:summaryOn="@string/pref_description_use_http_auth_on"
-        android:title="@string/pref_title_use_http_auth" />
-
-    <EditTextPreference
-        android:id="@+id/pref_backend_auth_user"
-        android:dependency="backend_use_http_auth"
-        android:inputType="text"
-        android:key="backend_auth_user"
-        android:maxLines="1"
-        android:selectAllOnFocus="true"
-        android:singleLine="true"
-        android:title="@string/pref_title_backend_auth_user" />
-
-    <EditTextPreference
-        android:id="@+id/pref_backend_auth_password"
-        android:dependency="backend_use_http_auth"
-        android:inputType="textPassword"
-        android:key="backend_auth_password"
-        android:maxLines="1"
-        android:selectAllOnFocus="true"
-        android:singleLine="true"
-        android:title="@string/pref_title_backend_auth_password" />
-
-</PreferenceScreen>