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.
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.
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/>.
18 package net.ktnx.mobileledger.ui.profiles;
20 import android.text.TextUtils;
22 import androidx.lifecycle.LifecycleOwner;
23 import androidx.lifecycle.MutableLiveData;
24 import androidx.lifecycle.Observer;
25 import androidx.lifecycle.ViewModel;
27 import net.ktnx.mobileledger.App;
28 import net.ktnx.mobileledger.json.API;
29 import net.ktnx.mobileledger.model.Currency;
30 import net.ktnx.mobileledger.model.HledgerVersion;
31 import net.ktnx.mobileledger.model.MobileLedgerProfile;
32 import net.ktnx.mobileledger.utils.Colors;
33 import net.ktnx.mobileledger.utils.Logger;
34 import net.ktnx.mobileledger.utils.Misc;
35 import net.ktnx.mobileledger.utils.NetworkUtil;
37 import java.io.BufferedReader;
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.io.InputStreamReader;
41 import java.net.HttpURLConnection;
42 import java.util.Locale;
43 import java.util.Objects;
44 import java.util.regex.Matcher;
45 import java.util.regex.Pattern;
47 public class ProfileDetailModel extends ViewModel {
48 private static final String HTTPS_URL_START = "https://";
49 private final MutableLiveData<String> profileName = new MutableLiveData<>();
50 private final MutableLiveData<Boolean> postingPermitted = new MutableLiveData<>(true);
51 private final MutableLiveData<Currency> defaultCommodity = new MutableLiveData<>(null);
52 private final MutableLiveData<MobileLedgerProfile.FutureDates> futureDates =
53 new MutableLiveData<>(MobileLedgerProfile.FutureDates.None);
54 private final MutableLiveData<Boolean> showCommodityByDefault = new MutableLiveData<>(false);
55 private final MutableLiveData<Boolean> showCommentsByDefault = new MutableLiveData<>(true);
56 private final MutableLiveData<Boolean> useAuthentication = new MutableLiveData<>(false);
57 private final MutableLiveData<API> apiVersion = new MutableLiveData<>(API.auto);
58 private final MutableLiveData<String> url = new MutableLiveData<>(null);
59 private final MutableLiveData<String> authUserName = new MutableLiveData<>(null);
60 private final MutableLiveData<String> authPassword = new MutableLiveData<>(null);
61 private final MutableLiveData<String> preferredAccountsFilter = new MutableLiveData<>(null);
62 private final MutableLiveData<Integer> themeId = new MutableLiveData<>(-1);
63 private final MutableLiveData<HledgerVersion> detectedVersion = new MutableLiveData<>(null);
64 private final MutableLiveData<Boolean> detectingHledgerVersion = new MutableLiveData<>(false);
65 public int initialThemeHue = Colors.DEFAULT_HUE_DEG;
66 private VersionDetectionThread versionDetectionThread;
67 public ProfileDetailModel() {
69 String getProfileName() {
70 return profileName.getValue();
72 void setProfileName(String newValue) {
73 if (!Misc.nullIsEmpty(newValue)
74 .equals(Misc.nullIsEmpty(profileName.getValue())))
75 profileName.setValue(newValue);
77 void setProfileName(CharSequence newValue) {
78 setProfileName(String.valueOf(newValue));
80 void observeProfileName(LifecycleOwner lfo, Observer<String> o) {
81 profileName.observe(lfo, o);
83 Boolean getPostingPermitted() {
84 return postingPermitted.getValue();
86 void setPostingPermitted(boolean newValue) {
87 if (newValue != postingPermitted.getValue())
88 postingPermitted.setValue(newValue);
90 void observePostingPermitted(LifecycleOwner lfo, Observer<Boolean> o) {
91 postingPermitted.observe(lfo, o);
93 public void setShowCommentsByDefault(boolean newValue) {
94 if (newValue != showCommentsByDefault.getValue())
95 showCommentsByDefault.setValue(newValue);
97 void observeShowCommentsByDefault(LifecycleOwner lfo, Observer<Boolean> o) {
98 showCommentsByDefault.observe(lfo, o);
100 MobileLedgerProfile.FutureDates getFutureDates() {
101 return futureDates.getValue();
103 void setFutureDates(MobileLedgerProfile.FutureDates newValue) {
104 if (newValue != futureDates.getValue())
105 futureDates.setValue(newValue);
107 void observeFutureDates(LifecycleOwner lfo, Observer<MobileLedgerProfile.FutureDates> o) {
108 futureDates.observe(lfo, o);
110 Currency getDefaultCommodity() {
111 return defaultCommodity.getValue();
113 void setDefaultCommodity(Currency newValue) {
114 if (newValue != defaultCommodity.getValue())
115 defaultCommodity.setValue(newValue);
117 void observeDefaultCommodity(LifecycleOwner lfo, Observer<Currency> o) {
118 defaultCommodity.observe(lfo, o);
120 Boolean getShowCommodityByDefault() {
121 return showCommodityByDefault.getValue();
123 void setShowCommodityByDefault(boolean newValue) {
124 if (newValue != showCommodityByDefault.getValue())
125 showCommodityByDefault.setValue(newValue);
127 void observeShowCommodityByDefault(LifecycleOwner lfo, Observer<Boolean> o) {
128 showCommodityByDefault.observe(lfo, o);
130 public Boolean getUseAuthentication() {
131 return useAuthentication.getValue();
133 void setUseAuthentication(boolean newValue) {
134 if (newValue != useAuthentication.getValue())
135 useAuthentication.setValue(newValue);
137 void observeUseAuthentication(LifecycleOwner lfo, Observer<Boolean> o) {
138 useAuthentication.observe(lfo, o);
140 API getApiVersion() {
141 return apiVersion.getValue();
143 void setApiVersion(API newValue) {
144 if (newValue != apiVersion.getValue())
145 apiVersion.setValue(newValue);
147 void observeApiVersion(LifecycleOwner lfo, Observer<API> o) {
148 apiVersion.observe(lfo, o);
150 HledgerVersion getDetectedVersion() { return detectedVersion.getValue(); }
151 void setDetectedVersion(HledgerVersion newValue) {
152 if (!Objects.equals(detectedVersion.getValue(), newValue))
153 detectedVersion.setValue(newValue);
155 void observeDetectedVersion(LifecycleOwner lfo, Observer<HledgerVersion> o) {
156 detectedVersion.observe(lfo, o);
158 public String getUrl() {
159 return url.getValue();
161 void setUrl(String newValue) {
162 if (!Misc.nullIsEmpty(newValue)
163 .equals(Misc.nullIsEmpty(url.getValue())))
164 url.setValue(newValue);
166 void setUrl(CharSequence newValue) {
167 setUrl(String.valueOf(newValue));
169 void observeUrl(LifecycleOwner lfo, Observer<String> o) {
172 public String getAuthUserName() {
173 return authUserName.getValue();
175 void setAuthUserName(String newValue) {
176 if (!Misc.nullIsEmpty(newValue)
177 .equals(Misc.nullIsEmpty(authUserName.getValue())))
178 authUserName.setValue(newValue);
180 void setAuthUserName(CharSequence newValue) {
181 setAuthUserName(String.valueOf(newValue));
183 void observeUserName(LifecycleOwner lfo, Observer<String> o) {
184 authUserName.observe(lfo, o);
186 public String getAuthPassword() {
187 return authPassword.getValue();
189 void setAuthPassword(String newValue) {
190 if (!Misc.nullIsEmpty(newValue)
191 .equals(Misc.nullIsEmpty(authPassword.getValue())))
192 authPassword.setValue(newValue);
194 void setAuthPassword(CharSequence newValue) {
195 setAuthPassword(String.valueOf(newValue));
197 void observePassword(LifecycleOwner lfo, Observer<String> o) {
198 authPassword.observe(lfo, o);
200 String getPreferredAccountsFilter() {
201 return preferredAccountsFilter.getValue();
203 void setPreferredAccountsFilter(String newValue) {
204 if (!Misc.nullIsEmpty(newValue)
205 .equals(Misc.nullIsEmpty(preferredAccountsFilter.getValue())))
206 preferredAccountsFilter.setValue(newValue);
208 void setPreferredAccountsFilter(CharSequence newValue) {
209 setPreferredAccountsFilter(String.valueOf(newValue));
211 void observePreferredAccountsFilter(LifecycleOwner lfo, Observer<String> o) {
212 preferredAccountsFilter.observe(lfo, o);
215 return themeId.getValue();
217 void setThemeId(int newValue) {
218 themeId.setValue(newValue);
220 void observeThemeId(LifecycleOwner lfo, Observer<Integer> o) {
221 themeId.observe(lfo, o);
223 void observeDetectingHledgerVersion(LifecycleOwner lfo, Observer<Boolean> o) {
224 detectingHledgerVersion.observe(lfo, o);
226 void setValuesFromProfile(MobileLedgerProfile mProfile, int newProfileHue) {
227 final int profileThemeId;
228 if (mProfile != null) {
229 profileName.setValue(mProfile.getName());
230 postingPermitted.setValue(mProfile.isPostingPermitted());
231 showCommentsByDefault.setValue(mProfile.getShowCommentsByDefault());
232 showCommodityByDefault.setValue(mProfile.getShowCommodityByDefault());
234 String comm = mProfile.getDefaultCommodity();
235 if (TextUtils.isEmpty(comm))
236 setDefaultCommodity(null);
238 setDefaultCommodity(new Currency(-1, comm));
240 futureDates.setValue(mProfile.getFutureDates());
241 apiVersion.setValue(mProfile.getApiVersion());
242 url.setValue(mProfile.getUrl());
243 useAuthentication.setValue(mProfile.isAuthEnabled());
244 authUserName.setValue(mProfile.isAuthEnabled() ? mProfile.getAuthUserName() : "");
245 authPassword.setValue(mProfile.isAuthEnabled() ? mProfile.getAuthPassword() : "");
246 preferredAccountsFilter.setValue(mProfile.getPreferredAccountsFilter());
247 themeId.setValue(mProfile.getThemeHue());
248 detectedVersion.setValue(mProfile.getDetectedVersion());
251 profileName.setValue(null);
252 url.setValue(HTTPS_URL_START);
253 postingPermitted.setValue(true);
254 showCommentsByDefault.setValue(true);
255 showCommodityByDefault.setValue(false);
256 setFutureDates(MobileLedgerProfile.FutureDates.None);
257 setApiVersion(API.auto);
258 useAuthentication.setValue(false);
259 authUserName.setValue("");
260 authPassword.setValue("");
261 preferredAccountsFilter.setValue(null);
262 themeId.setValue(newProfileHue);
263 detectedVersion.setValue(null);
266 void updateProfile(MobileLedgerProfile mProfile) {
267 mProfile.setName(profileName.getValue());
268 mProfile.setUrl(url.getValue());
269 mProfile.setPostingPermitted(postingPermitted.getValue());
270 mProfile.setShowCommentsByDefault(showCommentsByDefault.getValue());
271 Currency c = defaultCommodity.getValue();
272 mProfile.setDefaultCommodity((c == null) ? null : c.getName());
273 mProfile.setShowCommodityByDefault(showCommodityByDefault.getValue());
274 mProfile.setPreferredAccountsFilter(preferredAccountsFilter.getValue());
275 mProfile.setAuthEnabled(useAuthentication.getValue());
276 mProfile.setAuthUserName(authUserName.getValue());
277 mProfile.setAuthPassword(authPassword.getValue());
278 mProfile.setThemeHue(themeId.getValue());
279 mProfile.setFutureDates(futureDates.getValue());
280 mProfile.setApiVersion(apiVersion.getValue());
281 mProfile.setDetectedVersion(detectedVersion.getValue());
283 synchronized public void triggerVersionDetection() {
284 if (versionDetectionThread != null)
285 versionDetectionThread.interrupt();
287 versionDetectionThread = new VersionDetectionThread(this);
288 versionDetectionThread.start();
290 static class VersionDetectionThread extends Thread {
291 static final int TARGET_PROCESS_DURATION = 1000;
292 private final Pattern versionPattern =
293 Pattern.compile("^\"(\\d+)\\.(\\d+)(?:\\.(\\d+))?\"$");
294 private final ProfileDetailModel model;
295 public VersionDetectionThread(ProfileDetailModel model) {
298 private HledgerVersion detectVersion() {
299 App.setAuthenticationDataFromProfileModel(model);
300 HttpURLConnection http = null;
302 http = NetworkUtil.prepareConnection(model.getUrl(), "version",
303 model.getUseAuthentication());
304 switch (http.getResponseCode()) {
308 return new HledgerVersion(true);
310 Logger.debug("profile", String.format(Locale.US,
311 "HTTP error detecting hledger-web version: [%d] %s",
312 http.getResponseCode(), http.getResponseMessage()));
315 InputStream stream = http.getInputStream();
316 BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
317 String version = reader.readLine();
318 Matcher m = versionPattern.matcher(version);
320 int major = Integer.parseInt(Objects.requireNonNull(m.group(1)));
321 int minor = Integer.parseInt(Objects.requireNonNull(m.group(2)));
322 final boolean hasPatch = m.groupCount() >= 3;
323 int patch = hasPatch ? Integer.parseInt(Objects.requireNonNull(m.group(3))) : 0;
325 return hasPatch ? new HledgerVersion(major, minor, patch)
326 : new HledgerVersion(major, minor);
329 Logger.debug("profile",
330 String.format("Unrecognised version string '%s'", version));
334 catch (IOException e) {
339 App.resetAuthenticationData();
344 model.detectingHledgerVersion.postValue(true);
346 long startTime = System.currentTimeMillis();
348 final HledgerVersion version = detectVersion();
350 long elapsed = System.currentTimeMillis() - startTime;
351 Logger.debug("profile", "Detection duration " + elapsed);
352 if (elapsed < TARGET_PROCESS_DURATION) {
354 Thread.sleep(TARGET_PROCESS_DURATION - elapsed);
356 catch (InterruptedException e) {
360 model.detectedVersion.postValue(version);
363 model.detectingHledgerVersion.postValue(false);