]> git.ktnx.net Git - mobile-ledger.git/commitdiff
UI and machinery for detecting hledger-web version
authorDamyan Ivanov <dam+mobileledger@ktnx.net>
Sun, 4 Oct 2020 18:37:23 +0000 (21:37 +0300)
committerDamyan Ivanov <dam+mobileledger@ktnx.net>
Sun, 4 Oct 2020 18:37:23 +0000 (21:37 +0300)
todo: retry discovering version upon URL change, make JSON input/output
honour the version

12 files changed:
app/src/main/java/net/ktnx/mobileledger/model/HledgerVersion.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java
app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java
app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailModel.java
app/src/main/java/net/ktnx/mobileledger/utils/MobileLedgerDatabase.java
app/src/main/java/net/ktnx/mobileledger/utils/NetworkUtil.java
app/src/main/res/drawable-anydpi/ic_refresh_primary_24dp.xml [new file with mode: 0644]
app/src/main/res/layout/profile_detail.xml
app/src/main/res/raw/create_db.sql
app/src/main/res/raw/sql_41.sql [new file with mode: 0644]
app/src/main/res/values-bg/strings.xml
app/src/main/res/values/strings.xml

diff --git a/app/src/main/java/net/ktnx/mobileledger/model/HledgerVersion.java b/app/src/main/java/net/ktnx/mobileledger/model/HledgerVersion.java
new file mode 100644 (file)
index 0000000..f3cbba2
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright © 2020 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe 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.
+ *
+ * MoLe 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 MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.model;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Locale;
+
+public class HledgerVersion {
+    private int major;
+    private int minor;
+    private int patch;
+    private boolean isPre_1_20;
+    private boolean hasPatch;
+    public HledgerVersion(int major, int minor) {
+        this.major = major;
+        this.minor = minor;
+        this.isPre_1_20 = false;
+        this.hasPatch = false;
+    }
+    public HledgerVersion(int major, int minor, int patch) {
+        this.major = major;
+        this.minor = minor;
+        this.patch = patch;
+        this.isPre_1_20 = false;
+        this.hasPatch = true;
+    }
+    public HledgerVersion(boolean pre_1_20) {
+        if (!pre_1_20)
+            throw new IllegalArgumentException("pre_1_20 argument must be true");
+        this.major = this.minor = 0;
+        this.isPre_1_20 = true;
+        this.hasPatch = false;
+    }
+    public HledgerVersion(HledgerVersion origin) {
+        this.major = origin.major;
+        this.minor = origin.minor;
+        this.isPre_1_20 = origin.isPre_1_20;
+        this.patch = origin.patch;
+        this.hasPatch = origin.hasPatch;
+    }
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (obj == null)
+            return false;
+        if (!(obj instanceof HledgerVersion))
+            return false;
+        HledgerVersion that = (HledgerVersion) obj;
+
+        return (this.isPre_1_20 == that.isPre_1_20 && this.major == that.major &&
+                this.minor == that.minor && this.patch == that.patch &&
+                this.hasPatch == that.hasPatch);
+    }
+    public boolean isPre_1_20() {
+        return isPre_1_20;
+    }
+    public int getMajor() {
+        return major;
+    }
+    public int getMinor() {
+        return minor;
+    }
+    public int getPatch() {
+        return patch;
+    }
+    @NonNull
+    @Override
+    public String toString() {
+        if (isPre_1_20)
+            return "(before 1.20)";
+        return hasPatch ? String.format(Locale.ROOT, "%d.%d.%d", major, minor, patch)
+                        : String.format(Locale.ROOT, "%d.%d", major, minor);
+    }
+}
index c9e358e292451c83468ddefc923c2393a9df3fa4..55746dddd1279f803798357fd4441190ea5f9452 100644 (file)
@@ -63,6 +63,7 @@ public final class MobileLedgerProfile {
     private FutureDates futureDates = FutureDates.None;
     private boolean accountsLoaded;
     private boolean transactionsLoaded;
+    private HledgerVersion detectedVersion;
     // N.B. when adding new fields, update the copy-constructor below
     transient private AccountAndTransactionListSaver accountAndTransactionListSaver;
     public MobileLedgerProfile(String uuid) {
@@ -86,6 +87,8 @@ public final class MobileLedgerProfile {
         defaultCommodity = origin.defaultCommodity;
         accountsLoaded = origin.accountsLoaded;
         transactionsLoaded = origin.transactionsLoaded;
+        if (origin.detectedVersion != null)
+            detectedVersion = new HledgerVersion(origin.detectedVersion);
     }
     // loads all profiles into Data.profiles
     // returns the profile with the given UUID
@@ -97,7 +100,8 @@ public final class MobileLedgerProfile {
                                          "auth_password, permit_posting, theme, order_no, " +
                                          "preferred_accounts_filter, future_dates, api_version, " +
                                          "show_commodity_by_default, default_commodity, " +
-                                         "show_comments_by_default FROM " +
+                                         "show_comments_by_default, detected_version_pre_1_19, " +
+                                         "detected_version_major, detected_version_minor FROM " +
                                          "profiles order by order_no", null))
         {
             while (cursor.moveToNext()) {
@@ -116,6 +120,21 @@ public final class MobileLedgerProfile {
                 item.setShowCommodityByDefault(cursor.getInt(12) == 1);
                 item.setDefaultCommodity(cursor.getString(13));
                 item.setShowCommentsByDefault(cursor.getInt(14) == 1);
+                {
+                    boolean pre_1_20 = cursor.getInt(15) == 1;
+                    int major = cursor.getInt(16);
+                    int minor = cursor.getInt(17);
+
+                    if (!pre_1_20 && major == 0 && minor == 0) {
+                        item.detectedVersion = null;
+                    }
+                    else if (pre_1_20) {
+                        item.detectedVersion = new HledgerVersion(true);
+                    }
+                    else {
+                        item.detectedVersion = new HledgerVersion(major, minor);
+                    }
+                }
                 list.add(item);
                 if (item.getUuid()
                         .equals(currentProfileUUID))
@@ -142,6 +161,12 @@ public final class MobileLedgerProfile {
             db.endTransaction();
         }
     }
+    public HledgerVersion getDetectedVersion() {
+        return detectedVersion;
+    }
+    public void setDetectedVersion(HledgerVersion detectedVersion) {
+        this.detectedVersion = detectedVersion;
+    }
     @Contract(value = "null -> false", pure = true)
     @Override
     public boolean equals(@Nullable Object obj) {
@@ -179,6 +204,8 @@ public final class MobileLedgerProfile {
             return false;
         if (apiVersion != p.apiVersion)
             return false;
+        if (!Objects.equals(detectedVersion, p.detectedVersion))
+            return false;
         return futureDates == p.futureDates;
     }
     public boolean getShowCommentsByDefault() {
@@ -294,13 +321,18 @@ public final class MobileLedgerProfile {
             db.execSQL("REPLACE INTO profiles(uuid, name, permit_posting, url, " +
                        "use_authentication, auth_user, auth_password, theme, order_no, " +
                        "preferred_accounts_filter, future_dates, api_version, " +
-                       "show_commodity_by_default, default_commodity, show_comments_by_default) " +
-                       "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+                       "show_commodity_by_default, default_commodity, show_comments_by_default," +
+                       "detected_version_pre_1_19, detected_version_major, " +
+                       "detected_version_minor) " +
+                       "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
                     new Object[]{uuid, name, permitPosting, url, authEnabled,
                                  authEnabled ? authUserName : null,
                                  authEnabled ? authPassword : null, themeHue, orderNo,
                                  preferredAccountsFilter, futureDates.toInt(), apiVersion.toInt(),
-                                 showCommodityByDefault, defaultCommodity, showCommentsByDefault
+                                 showCommodityByDefault, defaultCommodity, showCommentsByDefault,
+                                 (detectedVersion != null) && detectedVersion.isPre_1_20(),
+                                 (detectedVersion == null) ? 0 : detectedVersion.getMajor(),
+                                 (detectedVersion == null) ? 0 : detectedVersion.getMinor()
                     });
             db.setTransactionSuccessful();
         }
index 72c4ed4cdab47fb56448a70ec3f2dc1a1262895f..9b0b60b70b00a874277a47418058c6d40068a627 100644 (file)
@@ -282,33 +282,26 @@ public class ProfileDetailFragment extends Fragment {
         apiVersionText = context.findViewById(R.id.api_version_text);
         model.observeApiVersion(viewLifecycleOwner,
                 apiVer -> apiVersionText.setText(apiVer.getDescription(getResources())));
-        context.findViewById(R.id.api_version_layout)
-               .setOnClickListener(v -> {
-                   MenuInflater mi = new MenuInflater(context);
-                   PopupMenu menu = new PopupMenu(context, v);
-                   menu.inflate(R.menu.api_version);
-                   menu.setOnMenuItemClickListener(item -> {
-                       SendTransactionTask.API apiVer;
-                       switch (item.getItemId()) {
-                           case R.id.api_version_menu_html:
-                               apiVer = SendTransactionTask.API.html;
-                               break;
-                           case R.id.api_version_menu_post_1_14:
-                               apiVer = SendTransactionTask.API.post_1_14;
-                               break;
-                           case R.id.api_version_menu_pre_1_15:
-                               apiVer = SendTransactionTask.API.pre_1_15;
-                               break;
-                           case R.id.api_version_menu_auto:
-                           default:
-                               apiVer = SendTransactionTask.API.auto;
-                       }
-                       model.setApiVersion(apiVer);
-                       apiVersionText.setText(apiVer.getDescription(getResources()));
-                       return true;
-                   });
-                   menu.show();
-               });
+        context.findViewById(R.id.api_version_label)
+               .setOnClickListener(this::chooseAPIVersion);
+        context.findViewById(R.id.api_version_text)
+               .setOnClickListener(this::chooseAPIVersion);
+
+        TextView detectedApiVersion = context.findViewById(R.id.detected_version_text);
+        model.observeDetectedVersion(viewLifecycleOwner, ver -> {
+            if (ver == null)
+                detectedApiVersion.setText(context.getResources()
+                                                  .getString(R.string.api_version_unknown_label));
+            else if (ver.isPre_1_20())
+                detectedApiVersion.setText(context.getResources()
+                                                  .getString(R.string.api_pre_1_19));
+            else
+                detectedApiVersion.setText(ver.toString());
+        });
+        detectedApiVersion.setOnClickListener(v -> model.triggerVersionDetection());
+        context.findViewById(R.id.api_version_detect_button)
+               .setOnClickListener(v -> model.triggerVersionDetection());
+
         authParams = context.findViewById(R.id.auth_params);
 
         useAuthentication = context.findViewById(R.id.enable_http_auth);
@@ -386,6 +379,34 @@ public class ProfileDetailFragment extends Fragment {
 
         profileName.requestFocus();
     }
+    private void chooseAPIVersion(View v) {
+        Activity context = getActivity();
+        ProfileDetailModel model = getModel();
+        MenuInflater mi = new MenuInflater(context);
+        PopupMenu menu = new PopupMenu(context, v);
+        menu.inflate(R.menu.api_version);
+        menu.setOnMenuItemClickListener(item -> {
+            SendTransactionTask.API apiVer;
+            switch (item.getItemId()) {
+                case R.id.api_version_menu_html:
+                    apiVer = SendTransactionTask.API.html;
+                    break;
+                case R.id.api_version_menu_post_1_14:
+                    apiVer = SendTransactionTask.API.post_1_14;
+                    break;
+                case R.id.api_version_menu_pre_1_15:
+                    apiVer = SendTransactionTask.API.pre_1_15;
+                    break;
+                case R.id.api_version_menu_auto:
+                default:
+                    apiVer = SendTransactionTask.API.auto;
+            }
+            model.setApiVersion(apiVer);
+            apiVersionText.setText(apiVer.getDescription(getResources()));
+            return true;
+        });
+        menu.show();
+    }
     private MobileLedgerProfile.FutureDates futureDatesSettingFromMenuItemId(int itemId) {
         switch (itemId) {
             case R.id.menu_future_dates_7:
index 915ab032b58fc4a26e9e8be7b2f58899781685b5..92d1505fb0baa159bd38cf2e24b9f003d2ddfa4a 100644 (file)
@@ -26,9 +26,22 @@ import androidx.lifecycle.ViewModel;
 
 import net.ktnx.mobileledger.async.SendTransactionTask;
 import net.ktnx.mobileledger.model.Currency;
+import net.ktnx.mobileledger.model.HledgerVersion;
 import net.ktnx.mobileledger.model.MobileLedgerProfile;
 import net.ktnx.mobileledger.utils.Colors;
+import net.ktnx.mobileledger.utils.Logger;
 import net.ktnx.mobileledger.utils.Misc;
+import net.ktnx.mobileledger.utils.NetworkUtil;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public class ProfileDetailModel extends ViewModel {
     private static final String HTTPS_URL_START = "https://";
@@ -47,7 +60,9 @@ public class ProfileDetailModel extends ViewModel {
     private final MutableLiveData<String> authPassword = new MutableLiveData<>(null);
     private final MutableLiveData<String> preferredAccountsFilter = new MutableLiveData<>(null);
     private final MutableLiveData<Integer> themeId = new MutableLiveData<>(-1);
+    private final MutableLiveData<HledgerVersion> detectedVersion = new MutableLiveData<>(null);
     public int initialThemeHue = Colors.DEFAULT_HUE_DEG;
+    private VersionDetectionThread versionDetectionThread;
     public ProfileDetailModel() {
     }
     String getProfileName() {
@@ -131,6 +146,14 @@ public class ProfileDetailModel extends ViewModel {
     void observeApiVersion(LifecycleOwner lfo, Observer<SendTransactionTask.API> o) {
         apiVersion.observe(lfo, o);
     }
+    HledgerVersion getDetectedVersion() { return detectedVersion.getValue(); }
+    void setDetectedVersion(HledgerVersion newValue) {
+        if (!Objects.equals(detectedVersion.getValue(), newValue))
+            detectedVersion.setValue(newValue);
+    }
+    void observeDetectedVersion(LifecycleOwner lfo, Observer<HledgerVersion> o) {
+        detectedVersion.observe(lfo, o);
+    }
     String getUrl() {
         return url.getValue();
     }
@@ -218,6 +241,7 @@ public class ProfileDetailModel extends ViewModel {
             authPassword.setValue(mProfile.isAuthEnabled() ? mProfile.getAuthPassword() : "");
             preferredAccountsFilter.setValue(mProfile.getPreferredAccountsFilter());
             themeId.setValue(mProfile.getThemeHue());
+            detectedVersion.setValue(mProfile.getDetectedVersion());
         }
         else {
             profileName.setValue(null);
@@ -232,9 +256,8 @@ public class ProfileDetailModel extends ViewModel {
             authPassword.setValue("");
             preferredAccountsFilter.setValue(null);
             themeId.setValue(newProfileHue);
+            detectedVersion.setValue(null);
         }
-
-
     }
     void updateProfile(MobileLedgerProfile mProfile) {
         mProfile.setName(profileName.getValue());
@@ -251,5 +274,62 @@ public class ProfileDetailModel extends ViewModel {
         mProfile.setThemeHue(themeId.getValue());
         mProfile.setFutureDates(futureDates.getValue());
         mProfile.setApiVersion(apiVersion.getValue());
+        mProfile.setDetectedVersion(detectedVersion.getValue());
+    }
+    synchronized public void triggerVersionDetection() {
+        if (versionDetectionThread != null)
+            versionDetectionThread.interrupt();
+
+        versionDetectionThread = new VersionDetectionThread(this);
+        versionDetectionThread.start();
+    }
+    static class VersionDetectionThread extends Thread {
+        private final Pattern versionPattern =
+                Pattern.compile("^\"(\\d+)\\.(\\d+)(?:\\.(\\d+))?\"$");
+        private final ProfileDetailModel model;
+        public VersionDetectionThread(ProfileDetailModel model) {
+            this.model = model;
+        }
+        @Override
+        public void run() {
+            try {
+                HttpURLConnection http = NetworkUtil.prepareConnection(model.getUrl(), "version",
+                        model.getUseAuthentication());
+                switch (http.getResponseCode()) {
+                    case 200:
+                        break;
+                    case 404:
+                        model.detectedVersion.postValue(new HledgerVersion(true));
+                        return;
+                    default:
+                        Logger.debug("profile", String.format(Locale.US,
+                                "HTTP error detecting hledger-web version: [%d] %s",
+                                http.getResponseCode(), http.getResponseMessage()));
+                        model.detectedVersion.postValue(null);
+                        return;
+                }
+                InputStream stream = http.getInputStream();
+                BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+                String version = reader.readLine();
+                Matcher m = versionPattern.matcher(version);
+                if (m.matches()) {
+                    int major = Integer.parseInt(Objects.requireNonNull(m.group(1)));
+                    int minor = Integer.parseInt(Objects.requireNonNull(m.group(2)));
+                    final boolean hasPatch = m.groupCount() >= 3;
+                    int patch = hasPatch ? Integer.parseInt(Objects.requireNonNull(m.group(3))) : 0;
+
+                    model.detectedVersion.postValue(
+                            hasPatch ? new HledgerVersion(major, minor, patch)
+                                     : new HledgerVersion(major, minor));
+                }
+                else {
+                    Logger.debug("profile",
+                            String.format("Unrecognised version string '%s'", version));
+                }
+            }
+            catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
     }
 }
index 29f4142571e4974eb5d35577ef579c995296014d..8f8be513c8e60a837bcedca09fe02fc288c527c6 100644 (file)
@@ -39,7 +39,7 @@ import static net.ktnx.mobileledger.utils.Logger.debug;
 public class MobileLedgerDatabase extends SQLiteOpenHelper {
     public static final MutableLiveData<Boolean> initComplete = new MutableLiveData<>(false);
     private static final String DB_NAME = "MoLe.db";
-    private static final int LATEST_REVISION = 40;
+    private static final int LATEST_REVISION = 41;
     private static final String CREATE_DB_SQL = "create_db";
     private final Application mContext;
 
index c0802a4547b1f0668faf3810f4852f668155649f..c9f0151ba4b84b12175bbf828eb425e7607c07f8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2019 Damyan Ivanov.
+ * Copyright © 2020 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
 
 package net.ktnx.mobileledger.utils;
 
+import androidx.annotation.NonNull;
+
 import net.ktnx.mobileledger.model.MobileLedgerProfile;
 
+import org.jetbrains.annotations.NotNull;
+
 import java.io.IOException;
 import java.net.HttpURLConnection;
 import java.net.URL;
@@ -27,14 +31,19 @@ import static net.ktnx.mobileledger.utils.Logger.debug;
 
 public final class NetworkUtil {
     private static final int thirtySeconds = 30000;
-    public static HttpURLConnection prepareConnection(MobileLedgerProfile profile, String path)
-            throws IOException {
-        String url = profile.getUrl();
-        final boolean use_auth = profile.isAuthEnabled();
-        if (!url.endsWith("/")) url += "/";
-        url += path;
-        debug("network", "Connecting to " + url);
-        HttpURLConnection http = (HttpURLConnection) new URL(url).openConnection();
+    @NotNull
+    public static HttpURLConnection prepareConnection(@NonNull MobileLedgerProfile profile,
+                                                      @NonNull String path) throws IOException {
+        return prepareConnection(profile.getUrl(), path, profile.isAuthEnabled());
+    }
+    public static HttpURLConnection prepareConnection(@NonNull String url, @NonNull String path,
+                                                      boolean authEnabled) throws IOException {
+        String connectURL = url;
+        if (!connectURL.endsWith("/"))
+            connectURL += "/";
+        connectURL += path;
+        debug("network", "Connecting to " + connectURL);
+        HttpURLConnection http = (HttpURLConnection) new URL(connectURL).openConnection();
         http.setAllowUserInteraction(true);
         http.setRequestProperty("Accept-Charset", "UTF-8");
         http.setInstanceFollowRedirects(false);
diff --git a/app/src/main/res/drawable-anydpi/ic_refresh_primary_24dp.xml b/app/src/main/res/drawable-anydpi/ic_refresh_primary_24dp.xml
new file mode 100644 (file)
index 0000000..4ddd835
--- /dev/null
@@ -0,0 +1,31 @@
+<!--
+  ~ 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.
+  ~
+  ~ Modified/adapted by Damyan Ivanov for MoLe
+  -->
+
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorPrimary"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    >
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"
+        />
+</vector>
index 9442604e8a2d62436c196dab2ff5f5d7cd64e9cf..d2ca5601a89705eb5322bd25b5f0d4da281bc195 100644 (file)
 
         </LinearLayout>
 
-        <LinearLayout
+        <androidx.constraintlayout.widget.ConstraintLayout
             android:id="@+id/api_version_layout"
-            android:orientation="vertical"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginBottom="16dp">
 
             <TextView
+                android:id="@+id/api_version_label"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@string/profile_api_version_title"
-                android:textAppearance="?android:textAppearanceListItem" />
+                android:textAppearance="?android:textAppearanceListItem"
+                app:layout_constraintTop_toTopOf="parent"
+                />
 
             <TextView
                 android:id="@+id/api_version_text"
-                android:layout_width="match_parent"
+                android:layout_width="0dp"
                 android:layout_height="wrap_content"
                 android:textAppearance="?android:textAppearanceListItemSecondary"
-                android:textColor="?attr/textColor" />
-        </LinearLayout>
+                android:textColor="?attr/textColor"
+                app:layout_constraintEnd_toStartOf="@id/detected_version_text"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/api_version_label"
+                />
+            <TextView
+                android:id="@+id/detected_version_label"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="8dp"
+                android:gravity="end"
+                android:text="@string/detected_version_label"
+                android:textAppearance="?android:textAppearanceListItemSecondary"
+                app:layout_constraintEnd_toStartOf="@id/detected_version_text"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/api_version_text"
+                />
+            <TextView
+                android:id="@+id/detected_version_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="8dp"
+                android:text="@string/api_version_unknown_label"
+                android:textAppearance="?android:textAppearanceListItemSecondary"
+                android:textColor="?attr/textColor"
+                app:layout_constraintEnd_toStartOf="@id/api_version_detect_button"
+                app:layout_constraintTop_toBottomOf="@id/api_version_text"
+                />
+            <TextView
+                android:id="@+id/api_version_detect_button"
+                android:layout_width="24dp"
+                android:layout_height="0dp"
+                android:drawableBottom="@drawable/ic_refresh_primary_24dp"
+                android:foregroundGravity="bottom"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/api_version_label"
+                />
+        </androidx.constraintlayout.widget.ConstraintLayout>
 
         <Switch
             android:id="@+id/profile_permit_posting"
index e640baef78ead9e2d66b029fb9b80bbdc08ae60e..2126aedf826303ac943d01f800079640e34679f6 100644 (file)
@@ -12,6 +12,7 @@
 --
 -- You should have received a copy of the GNU General Public License
 -- along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+create table profiles(uuid varchar not null primary key, name not null, url not null, use_authentication boolean not null, auth_user varchar, auth_password varchar, order_no integer, permit_posting boolean default 0, theme integer default -1, preferred_accounts_filter varchar, future_dates integer, api_version integer, show_commodity_by_default boolean default 0, default_commodity varchar, show_comments_by_default boolean default 1, detected_version_pre_1_19 boolean, detected_version_major integer, detected_version_minor integer);
 create table accounts(profile varchar not null, name varchar not null, name_upper varchar not null, level integer not null, parent_name varchar, expanded default 1, amounts_expanded boolean default 0, generation integer default 0);
 create unique index un_accounts on accounts(profile, name);
 create table options(profile varchar not null, name varchar not null, value varchar);
@@ -20,7 +21,6 @@ create table account_values(profile varchar not null, account varchar not null,
 create unique index un_account_values on account_values(profile,account,currency);
 create table description_history(description varchar not null primary key, description_upper varchar, generation integer default 0);
 create unique index un_description_history on description_history(description_upper);
-create table profiles(uuid varchar not null primary key, name not null, url not null, use_authentication boolean not null, auth_user varchar, auth_password varchar, order_no integer, permit_posting boolean default 0, theme integer default -1, preferred_accounts_filter varchar, future_dates integer, api_version integer, show_commodity_by_default boolean default 0, default_commodity varchar, show_comments_by_default boolean default 1);
 create table transactions(profile varchar not null, id integer not null, data_hash varchar not null, year integer not null, month integer not null, day integer not null, description varchar not null, comment varchar, generation integer default 0);
 create unique index un_transactions_id on transactions(profile,id);
 create unique index un_transactions_data_hash on transactions(profile,data_hash);
diff --git a/app/src/main/res/raw/sql_41.sql b/app/src/main/res/raw/sql_41.sql
new file mode 100644 (file)
index 0000000..5026276
--- /dev/null
@@ -0,0 +1,17 @@
+-- Copyright © 2020 Damyan Ivanov.
+-- This file is part of MoLe.
+-- MoLe 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.
+--
+-- MoLe 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 MoLe. If not, see <https://www.gnu.org/licenses/>.
+alter table profiles add detected_version_pre_1_19 boolean;
+alter table profiles add detected_version_major integer;
+alter table profiles add detected_version_minor integer;
\ No newline at end of file
index 01ca5802946cc0ad07b65e61e29d5bafa06d4a95..71308242f75237f3dd557310e0f7c766ee4de5b0 100644 (file)
     <string name="nav_header_desc">Заглавна част на страничния панел</string>
     <string name="transaction_count_summary">%,d движения към %s</string>
     <string name="account_count_summary">%,d сметки към %s</string>
+    <string name="api_version_unknown_label">Неизвестна</string>
+    <string name="api_pre_1_19">Преди 1.20.?</string>
+    <string name="detected_version_label">Открита версия</string>
 </resources>
index 2778846e44fa8f9bda559b6b9bf737fd07e7f4bb..bd3cb031d9b2304e49182847711081118f58e18a 100644 (file)
     <string name="sub_accounts_expand_collapse_trigger_description">Sub-accounts expand/collapse trigger</string>
     <string name="transaction_count_summary">%,d transactions as of %s</string>
     <string name="account_count_summary">%,d accounts as of %s</string>
+    <string name="api_version_unknown_label">Unknown</string>
+    <string name="api_pre_1_19">Before 1.20.?</string>
+    <string name="detected_version_label">Detected version</string>
 </resources>