]> git.ktnx.net Git - mobile-ledger-staging.git/blob - app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailModel.java
5de553481684c8a78c12a3edd2a8e182b4e0acad
[mobile-ledger-staging.git] / app / src / main / java / net / ktnx / mobileledger / ui / profiles / ProfileDetailModel.java
1 /*
2  * Copyright © 2020 Damyan Ivanov.
3  * This file is part of MoLe.
4  * MoLe is free software: you can distribute it and/or modify it
5  * under the term of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your opinion), any later version.
8  *
9  * MoLe is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License terms for details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
16  */
17
18 package net.ktnx.mobileledger.ui.profiles;
19
20 import android.text.TextUtils;
21
22 import androidx.lifecycle.LifecycleOwner;
23 import androidx.lifecycle.MutableLiveData;
24 import androidx.lifecycle.Observer;
25 import androidx.lifecycle.ViewModel;
26
27 import net.ktnx.mobileledger.json.API;
28 import net.ktnx.mobileledger.model.Currency;
29 import net.ktnx.mobileledger.model.HledgerVersion;
30 import net.ktnx.mobileledger.model.MobileLedgerProfile;
31 import net.ktnx.mobileledger.utils.Colors;
32 import net.ktnx.mobileledger.utils.Logger;
33 import net.ktnx.mobileledger.utils.Misc;
34 import net.ktnx.mobileledger.utils.NetworkUtil;
35
36 import java.io.BufferedReader;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.io.InputStreamReader;
40 import java.net.HttpURLConnection;
41 import java.util.Locale;
42 import java.util.Objects;
43 import java.util.regex.Matcher;
44 import java.util.regex.Pattern;
45
46 public class ProfileDetailModel extends ViewModel {
47     private static final String HTTPS_URL_START = "https://";
48     private final MutableLiveData<String> profileName = new MutableLiveData<>();
49     private final MutableLiveData<Boolean> postingPermitted = new MutableLiveData<>(true);
50     private final MutableLiveData<Currency> defaultCommodity = new MutableLiveData<>(null);
51     private final MutableLiveData<MobileLedgerProfile.FutureDates> futureDates =
52             new MutableLiveData<>(MobileLedgerProfile.FutureDates.None);
53     private final MutableLiveData<Boolean> showCommodityByDefault = new MutableLiveData<>(false);
54     private final MutableLiveData<Boolean> showCommentsByDefault = new MutableLiveData<>(true);
55     private final MutableLiveData<Boolean> useAuthentication = new MutableLiveData<>(false);
56     private final MutableLiveData<API> apiVersion = new MutableLiveData<>(API.auto);
57     private final MutableLiveData<String> url = new MutableLiveData<>(null);
58     private final MutableLiveData<String> authUserName = new MutableLiveData<>(null);
59     private final MutableLiveData<String> authPassword = new MutableLiveData<>(null);
60     private final MutableLiveData<String> preferredAccountsFilter = new MutableLiveData<>(null);
61     private final MutableLiveData<Integer> themeId = new MutableLiveData<>(-1);
62     private final MutableLiveData<HledgerVersion> detectedVersion = new MutableLiveData<>(null);
63     private final MutableLiveData<Boolean> detectingHledgerVersion = new MutableLiveData<>(false);
64     public int initialThemeHue = Colors.DEFAULT_HUE_DEG;
65     private VersionDetectionThread versionDetectionThread;
66     public ProfileDetailModel() {
67     }
68     String getProfileName() {
69         return profileName.getValue();
70     }
71     void setProfileName(String newValue) {
72         if (!Misc.nullIsEmpty(newValue)
73                  .equals(Misc.nullIsEmpty(profileName.getValue())))
74             profileName.setValue(newValue);
75     }
76     void setProfileName(CharSequence newValue) {
77         setProfileName(String.valueOf(newValue));
78     }
79     void observeProfileName(LifecycleOwner lfo, Observer<String> o) {
80         profileName.observe(lfo, o);
81     }
82     Boolean getPostingPermitted() {
83         return postingPermitted.getValue();
84     }
85     void setPostingPermitted(boolean newValue) {
86         if (newValue != postingPermitted.getValue())
87             postingPermitted.setValue(newValue);
88     }
89     void observePostingPermitted(LifecycleOwner lfo, Observer<Boolean> o) {
90         postingPermitted.observe(lfo, o);
91     }
92     public void setShowCommentsByDefault(boolean newValue) {
93         if (newValue != showCommentsByDefault.getValue())
94             showCommentsByDefault.setValue(newValue);
95     }
96     void observeShowCommentsByDefault(LifecycleOwner lfo, Observer<Boolean> o) {
97         showCommentsByDefault.observe(lfo, o);
98     }
99     MobileLedgerProfile.FutureDates getFutureDates() {
100         return futureDates.getValue();
101     }
102     void setFutureDates(MobileLedgerProfile.FutureDates newValue) {
103         if (newValue != futureDates.getValue())
104             futureDates.setValue(newValue);
105     }
106     void observeFutureDates(LifecycleOwner lfo, Observer<MobileLedgerProfile.FutureDates> o) {
107         futureDates.observe(lfo, o);
108     }
109     Currency getDefaultCommodity() {
110         return defaultCommodity.getValue();
111     }
112     void setDefaultCommodity(Currency newValue) {
113         if (newValue != defaultCommodity.getValue())
114             defaultCommodity.setValue(newValue);
115     }
116     void observeDefaultCommodity(LifecycleOwner lfo, Observer<Currency> o) {
117         defaultCommodity.observe(lfo, o);
118     }
119     Boolean getShowCommodityByDefault() {
120         return showCommodityByDefault.getValue();
121     }
122     void setShowCommodityByDefault(boolean newValue) {
123         if (newValue != showCommodityByDefault.getValue())
124             showCommodityByDefault.setValue(newValue);
125     }
126     void observeShowCommodityByDefault(LifecycleOwner lfo, Observer<Boolean> o) {
127         showCommodityByDefault.observe(lfo, o);
128     }
129     Boolean getUseAuthentication() {
130         return useAuthentication.getValue();
131     }
132     void setUseAuthentication(boolean newValue) {
133         if (newValue != useAuthentication.getValue())
134             useAuthentication.setValue(newValue);
135     }
136     void observeUseAuthentication(LifecycleOwner lfo, Observer<Boolean> o) {
137         useAuthentication.observe(lfo, o);
138     }
139     API getApiVersion() {
140         return apiVersion.getValue();
141     }
142     void setApiVersion(API newValue) {
143         if (newValue != apiVersion.getValue())
144             apiVersion.setValue(newValue);
145     }
146     void observeApiVersion(LifecycleOwner lfo, Observer<API> o) {
147         apiVersion.observe(lfo, o);
148     }
149     HledgerVersion getDetectedVersion() { return detectedVersion.getValue(); }
150     void setDetectedVersion(HledgerVersion newValue) {
151         if (!Objects.equals(detectedVersion.getValue(), newValue))
152             detectedVersion.setValue(newValue);
153     }
154     void observeDetectedVersion(LifecycleOwner lfo, Observer<HledgerVersion> o) {
155         detectedVersion.observe(lfo, o);
156     }
157     String getUrl() {
158         return url.getValue();
159     }
160     void setUrl(String newValue) {
161         if (!Misc.nullIsEmpty(newValue)
162                  .equals(Misc.nullIsEmpty(url.getValue())))
163             url.setValue(newValue);
164     }
165     void setUrl(CharSequence newValue) {
166         setUrl(String.valueOf(newValue));
167     }
168     void observeUrl(LifecycleOwner lfo, Observer<String> o) {
169         url.observe(lfo, o);
170     }
171     String getAuthUserName() {
172         return authUserName.getValue();
173     }
174     void setAuthUserName(String newValue) {
175         if (!Misc.nullIsEmpty(newValue)
176                  .equals(Misc.nullIsEmpty(authUserName.getValue())))
177             authUserName.setValue(newValue);
178     }
179     void setAuthUserName(CharSequence newValue) {
180         setAuthUserName(String.valueOf(newValue));
181     }
182     void observeUserName(LifecycleOwner lfo, Observer<String> o) {
183         authUserName.observe(lfo, o);
184     }
185     String getAuthPassword() {
186         return authPassword.getValue();
187     }
188     void setAuthPassword(String newValue) {
189         if (!Misc.nullIsEmpty(newValue)
190                  .equals(Misc.nullIsEmpty(authPassword.getValue())))
191             authPassword.setValue(newValue);
192     }
193     void setAuthPassword(CharSequence newValue) {
194         setAuthPassword(String.valueOf(newValue));
195     }
196     void observePassword(LifecycleOwner lfo, Observer<String> o) {
197         authPassword.observe(lfo, o);
198     }
199     String getPreferredAccountsFilter() {
200         return preferredAccountsFilter.getValue();
201     }
202     void setPreferredAccountsFilter(String newValue) {
203         if (!Misc.nullIsEmpty(newValue)
204                  .equals(Misc.nullIsEmpty(preferredAccountsFilter.getValue())))
205             preferredAccountsFilter.setValue(newValue);
206     }
207     void setPreferredAccountsFilter(CharSequence newValue) {
208         setPreferredAccountsFilter(String.valueOf(newValue));
209     }
210     void observePreferredAccountsFilter(LifecycleOwner lfo, Observer<String> o) {
211         preferredAccountsFilter.observe(lfo, o);
212     }
213     int getThemeId() {
214         return themeId.getValue();
215     }
216     void setThemeId(int newValue) {
217         themeId.setValue(newValue);
218     }
219     void observeThemeId(LifecycleOwner lfo, Observer<Integer> o) {
220         themeId.observe(lfo, o);
221     }
222     void observeDetectingHledgerVersion(LifecycleOwner lfo, Observer<Boolean> o) {
223         detectingHledgerVersion.observe(lfo, o);
224     }
225     void setValuesFromProfile(MobileLedgerProfile mProfile, int newProfileHue) {
226         final int profileThemeId;
227         if (mProfile != null) {
228             profileName.setValue(mProfile.getName());
229             postingPermitted.setValue(mProfile.isPostingPermitted());
230             showCommentsByDefault.setValue(mProfile.getShowCommentsByDefault());
231             showCommodityByDefault.setValue(mProfile.getShowCommodityByDefault());
232             {
233                 String comm = mProfile.getDefaultCommodity();
234                 if (TextUtils.isEmpty(comm))
235                     setDefaultCommodity(null);
236                 else
237                     setDefaultCommodity(new Currency(-1, comm));
238             }
239             futureDates.setValue(mProfile.getFutureDates());
240             apiVersion.setValue(mProfile.getApiVersion());
241             url.setValue(mProfile.getUrl());
242             useAuthentication.setValue(mProfile.isAuthEnabled());
243             authUserName.setValue(mProfile.isAuthEnabled() ? mProfile.getAuthUserName() : "");
244             authPassword.setValue(mProfile.isAuthEnabled() ? mProfile.getAuthPassword() : "");
245             preferredAccountsFilter.setValue(mProfile.getPreferredAccountsFilter());
246             themeId.setValue(mProfile.getThemeHue());
247             detectedVersion.setValue(mProfile.getDetectedVersion());
248         }
249         else {
250             profileName.setValue(null);
251             url.setValue(HTTPS_URL_START);
252             postingPermitted.setValue(true);
253             showCommentsByDefault.setValue(true);
254             showCommodityByDefault.setValue(false);
255             setFutureDates(MobileLedgerProfile.FutureDates.None);
256             setApiVersion(API.auto);
257             useAuthentication.setValue(false);
258             authUserName.setValue("");
259             authPassword.setValue("");
260             preferredAccountsFilter.setValue(null);
261             themeId.setValue(newProfileHue);
262             detectedVersion.setValue(null);
263         }
264     }
265     void updateProfile(MobileLedgerProfile mProfile) {
266         mProfile.setName(profileName.getValue());
267         mProfile.setUrl(url.getValue());
268         mProfile.setPostingPermitted(postingPermitted.getValue());
269         mProfile.setShowCommentsByDefault(showCommentsByDefault.getValue());
270         Currency c = defaultCommodity.getValue();
271         mProfile.setDefaultCommodity((c == null) ? null : c.getName());
272         mProfile.setShowCommodityByDefault(showCommodityByDefault.getValue());
273         mProfile.setPreferredAccountsFilter(preferredAccountsFilter.getValue());
274         mProfile.setAuthEnabled(useAuthentication.getValue());
275         mProfile.setAuthUserName(authUserName.getValue());
276         mProfile.setAuthPassword(authPassword.getValue());
277         mProfile.setThemeHue(themeId.getValue());
278         mProfile.setFutureDates(futureDates.getValue());
279         mProfile.setApiVersion(apiVersion.getValue());
280         mProfile.setDetectedVersion(detectedVersion.getValue());
281     }
282     synchronized public void triggerVersionDetection() {
283         if (versionDetectionThread != null)
284             versionDetectionThread.interrupt();
285
286         versionDetectionThread = new VersionDetectionThread(this);
287         versionDetectionThread.start();
288     }
289     static class VersionDetectionThread extends Thread {
290         static final int TARGET_PROCESS_DURATION = 1000;
291         private final Pattern versionPattern =
292                 Pattern.compile("^\"(\\d+)\\.(\\d+)(?:\\.(\\d+))?\"$");
293         private final ProfileDetailModel model;
294         public VersionDetectionThread(ProfileDetailModel model) {
295             this.model = model;
296         }
297         private HledgerVersion detectVersion() {
298             HttpURLConnection http = null;
299             try {
300                 http = NetworkUtil.prepareConnection(model.getUrl(), "version",
301                         model.getUseAuthentication());
302                 switch (http.getResponseCode()) {
303                     case 200:
304                         break;
305                     case 404:
306                         return new HledgerVersion(true);
307                     default:
308                         Logger.debug("profile", String.format(Locale.US,
309                                 "HTTP error detecting hledger-web version: [%d] %s",
310                                 http.getResponseCode(), http.getResponseMessage()));
311                         return null;
312                 }
313                 InputStream stream = http.getInputStream();
314                 BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
315                 String version = reader.readLine();
316                 Matcher m = versionPattern.matcher(version);
317                 if (m.matches()) {
318                     int major = Integer.parseInt(Objects.requireNonNull(m.group(1)));
319                     int minor = Integer.parseInt(Objects.requireNonNull(m.group(2)));
320                     final boolean hasPatch = m.groupCount() >= 3;
321                     int patch = hasPatch ? Integer.parseInt(Objects.requireNonNull(m.group(3))) : 0;
322
323                     return hasPatch ? new HledgerVersion(major, minor, patch)
324                                     : new HledgerVersion(major, minor);
325                 }
326                 else {
327                     Logger.debug("profile",
328                             String.format("Unrecognised version string '%s'", version));
329                     return null;
330                 }
331             }
332             catch (IOException e) {
333                 e.printStackTrace();
334                 return null;
335             }
336         }
337         @Override
338         public void run() {
339             model.detectingHledgerVersion.postValue(true);
340             try {
341                 long startTime = System.currentTimeMillis();
342
343                 final HledgerVersion version = detectVersion();
344
345                 long elapsed = System.currentTimeMillis() - startTime;
346                 Logger.debug("profile", "Detection duration " + elapsed);
347                 if (elapsed < TARGET_PROCESS_DURATION) {
348                     try {
349                         Thread.sleep(TARGET_PROCESS_DURATION - elapsed);
350                     }
351                     catch (InterruptedException e) {
352                         e.printStackTrace();
353                     }
354                 }
355                 model.detectedVersion.postValue(version);
356             }
357             finally {
358                 model.detectingHledgerVersion.postValue(false);
359             }
360         }
361     }
362 }