2 * Copyright © 2021 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.LiveData;
24 import androidx.lifecycle.MutableLiveData;
25 import androidx.lifecycle.Observer;
26 import androidx.lifecycle.ViewModel;
28 import net.ktnx.mobileledger.App;
29 import net.ktnx.mobileledger.db.Profile;
30 import net.ktnx.mobileledger.json.API;
31 import net.ktnx.mobileledger.model.FutureDates;
32 import net.ktnx.mobileledger.model.HledgerVersion;
33 import net.ktnx.mobileledger.utils.Colors;
34 import net.ktnx.mobileledger.utils.Logger;
35 import net.ktnx.mobileledger.utils.Misc;
36 import net.ktnx.mobileledger.utils.NetworkUtil;
38 import java.io.BufferedReader;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.io.InputStreamReader;
42 import java.net.HttpURLConnection;
43 import java.util.Locale;
44 import java.util.Objects;
45 import java.util.regex.Matcher;
46 import java.util.regex.Pattern;
48 import static net.ktnx.mobileledger.db.Profile.NO_PROFILE_ID;
50 public class ProfileDetailModel extends ViewModel {
51 private static final String HTTPS_URL_START = "https://";
52 private final MutableLiveData<String> profileName = new MutableLiveData<>();
53 private final MutableLiveData<Integer> orderNo = new MutableLiveData<>();
54 private final MutableLiveData<Boolean> postingPermitted = new MutableLiveData<>(true);
55 private final MutableLiveData<String> defaultCommodity = new MutableLiveData<>(null);
56 private final MutableLiveData<FutureDates> futureDates =
57 new MutableLiveData<>(FutureDates.None);
58 private final MutableLiveData<Boolean> showCommodityByDefault = new MutableLiveData<>(false);
59 private final MutableLiveData<Boolean> showCommentsByDefault = new MutableLiveData<>(true);
60 private final MutableLiveData<Boolean> useAuthentication = new MutableLiveData<>(false);
61 private final MutableLiveData<API> apiVersion = new MutableLiveData<>(API.auto);
62 private final MutableLiveData<String> url = new MutableLiveData<>(null);
63 private final MutableLiveData<String> authUserName = new MutableLiveData<>(null);
64 private final MutableLiveData<String> authPassword = new MutableLiveData<>(null);
65 private final MutableLiveData<String> preferredAccountsFilter = new MutableLiveData<>(null);
66 private final MutableLiveData<Integer> themeId = new MutableLiveData<>(-1);
67 private final MutableLiveData<HledgerVersion> detectedVersion = new MutableLiveData<>(null);
68 private final MutableLiveData<Boolean> detectingHledgerVersion = new MutableLiveData<>(false);
69 private final MutableLiveData<Long> profileId = new MutableLiveData<>(NO_PROFILE_ID);
70 public int initialThemeHue = Colors.DEFAULT_HUE_DEG;
71 private VersionDetectionThread versionDetectionThread;
72 public ProfileDetailModel() {
74 String getProfileName() {
75 return profileName.getValue();
77 void setProfileName(String newValue) {
78 if (!Misc.nullIsEmpty(newValue)
79 .equals(Misc.nullIsEmpty(profileName.getValue())))
80 profileName.setValue(newValue);
82 void setProfileName(CharSequence newValue) {
83 setProfileName(String.valueOf(newValue));
85 void observeProfileName(LifecycleOwner lfo, Observer<String> o) {
86 profileName.observe(lfo, o);
88 Boolean getPostingPermitted() {
89 return postingPermitted.getValue();
91 void setPostingPermitted(boolean newValue) {
92 if (newValue != postingPermitted.getValue())
93 postingPermitted.setValue(newValue);
95 void observePostingPermitted(LifecycleOwner lfo, Observer<Boolean> o) {
96 postingPermitted.observe(lfo, o);
98 public void setShowCommentsByDefault(boolean newValue) {
99 if (newValue != showCommentsByDefault.getValue())
100 showCommentsByDefault.setValue(newValue);
102 void observeShowCommentsByDefault(LifecycleOwner lfo, Observer<Boolean> o) {
103 showCommentsByDefault.observe(lfo, o);
105 FutureDates getFutureDates() {
106 return futureDates.getValue();
108 void setFutureDates(FutureDates newValue) {
109 if (newValue != futureDates.getValue())
110 futureDates.setValue(newValue);
112 void observeFutureDates(LifecycleOwner lfo, Observer<FutureDates> o) {
113 futureDates.observe(lfo, o);
115 String getDefaultCommodity() {
116 return defaultCommodity.getValue();
118 void setDefaultCommodity(String newValue) {
119 if (!Misc.equalStrings(newValue, defaultCommodity.getValue()))
120 defaultCommodity.setValue(newValue);
122 void observeDefaultCommodity(LifecycleOwner lfo, Observer<String> o) {
123 defaultCommodity.observe(lfo, o);
125 Boolean getShowCommodityByDefault() {
126 return showCommodityByDefault.getValue();
128 void setShowCommodityByDefault(boolean newValue) {
129 if (newValue != showCommodityByDefault.getValue())
130 showCommodityByDefault.setValue(newValue);
132 void observeShowCommodityByDefault(LifecycleOwner lfo, Observer<Boolean> o) {
133 showCommodityByDefault.observe(lfo, o);
135 public Boolean getUseAuthentication() {
136 return useAuthentication.getValue();
138 void setUseAuthentication(boolean newValue) {
139 if (newValue != useAuthentication.getValue())
140 useAuthentication.setValue(newValue);
142 void observeUseAuthentication(LifecycleOwner lfo, Observer<Boolean> o) {
143 useAuthentication.observe(lfo, o);
145 API getApiVersion() {
146 return apiVersion.getValue();
148 void setApiVersion(API newValue) {
149 if (newValue != apiVersion.getValue())
150 apiVersion.setValue(newValue);
152 void observeApiVersion(LifecycleOwner lfo, Observer<API> o) {
153 apiVersion.observe(lfo, o);
155 HledgerVersion getDetectedVersion() { return detectedVersion.getValue(); }
156 void setDetectedVersion(HledgerVersion newValue) {
157 if (!Objects.equals(detectedVersion.getValue(), newValue))
158 detectedVersion.setValue(newValue);
160 void observeDetectedVersion(LifecycleOwner lfo, Observer<HledgerVersion> o) {
161 detectedVersion.observe(lfo, o);
163 public String getUrl() {
164 return url.getValue();
166 void setUrl(String newValue) {
167 if (!Misc.nullIsEmpty(newValue)
168 .equals(Misc.nullIsEmpty(url.getValue())))
169 url.setValue(newValue);
171 void setUrl(CharSequence newValue) {
172 setUrl(String.valueOf(newValue));
174 void observeUrl(LifecycleOwner lfo, Observer<String> o) {
177 public String getAuthUserName() {
178 return authUserName.getValue();
180 void setAuthUserName(String newValue) {
181 if (!Misc.nullIsEmpty(newValue)
182 .equals(Misc.nullIsEmpty(authUserName.getValue())))
183 authUserName.setValue(newValue);
185 void setAuthUserName(CharSequence newValue) {
186 setAuthUserName(String.valueOf(newValue));
188 void observeUserName(LifecycleOwner lfo, Observer<String> o) {
189 authUserName.observe(lfo, o);
191 public String getAuthPassword() {
192 return authPassword.getValue();
194 void setAuthPassword(String newValue) {
195 if (!Misc.nullIsEmpty(newValue)
196 .equals(Misc.nullIsEmpty(authPassword.getValue())))
197 authPassword.setValue(newValue);
199 void setAuthPassword(CharSequence newValue) {
200 setAuthPassword(String.valueOf(newValue));
202 void observePassword(LifecycleOwner lfo, Observer<String> o) {
203 authPassword.observe(lfo, o);
205 String getPreferredAccountsFilter() {
206 return preferredAccountsFilter.getValue();
208 void setPreferredAccountsFilter(String newValue) {
209 if (!Misc.nullIsEmpty(newValue)
210 .equals(Misc.nullIsEmpty(preferredAccountsFilter.getValue())))
211 preferredAccountsFilter.setValue(newValue);
213 void setPreferredAccountsFilter(CharSequence newValue) {
214 setPreferredAccountsFilter(String.valueOf(newValue));
216 void observePreferredAccountsFilter(LifecycleOwner lfo, Observer<String> o) {
217 preferredAccountsFilter.observe(lfo, o);
220 return themeId.getValue();
222 void setThemeId(int newValue) {
223 themeId.setValue(newValue);
225 void observeThemeId(LifecycleOwner lfo, Observer<Integer> o) {
226 themeId.observe(lfo, o);
228 void observeDetectingHledgerVersion(LifecycleOwner lfo, Observer<Boolean> o) {
229 detectingHledgerVersion.observe(lfo, o);
231 void setValuesFromProfile(Profile mProfile) {
232 if (mProfile != null) {
233 profileId.setValue(mProfile.getId());
234 profileName.setValue(mProfile.getName());
235 orderNo.setValue(mProfile.getOrderNo());
236 postingPermitted.setValue(mProfile.permitPosting());
237 showCommentsByDefault.setValue(mProfile.getShowCommentsByDefault());
238 showCommodityByDefault.setValue(mProfile.getShowCommodityByDefault());
240 String comm = mProfile.getDefaultCommodity();
241 if (TextUtils.isEmpty(comm))
242 setDefaultCommodity(null);
244 setDefaultCommodity(comm);
246 futureDates.setValue(FutureDates.valueOf(mProfile.getFutureDates()));
247 apiVersion.setValue(API.valueOf(mProfile.getApiVersion()));
248 url.setValue(mProfile.getUrl());
249 useAuthentication.setValue(mProfile.useAuthentication());
250 authUserName.setValue(mProfile.useAuthentication() ? mProfile.getAuthUser() : "");
251 authPassword.setValue(mProfile.useAuthentication() ? mProfile.getAuthPassword() : "");
252 preferredAccountsFilter.setValue(mProfile.getPreferredAccountsFilter());
253 themeId.setValue(mProfile.getTheme());
254 detectedVersion.setValue(mProfile.detectedVersionPre_1_19() ? new HledgerVersion(true)
255 : new HledgerVersion(
256 mProfile.getDetectedVersionMajor(),
257 mProfile.getDetectedVersionMinor()));
260 profileId.setValue(NO_PROFILE_ID);
261 orderNo.setValue(-1);
262 profileName.setValue(null);
263 url.setValue(HTTPS_URL_START);
264 postingPermitted.setValue(true);
265 showCommentsByDefault.setValue(true);
266 showCommodityByDefault.setValue(false);
267 setFutureDates(FutureDates.None);
268 setApiVersion(API.auto);
269 useAuthentication.setValue(false);
270 authUserName.setValue("");
271 authPassword.setValue("");
272 preferredAccountsFilter.setValue(null);
273 detectedVersion.setValue(null);
276 void updateProfile(Profile mProfile) {
277 mProfile.setId(profileId.getValue());
278 mProfile.setName(profileName.getValue());
279 mProfile.setOrderNo(orderNo.getValue());
280 mProfile.setUrl(url.getValue());
281 mProfile.setPermitPosting(postingPermitted.getValue());
282 mProfile.setShowCommentsByDefault(showCommentsByDefault.getValue());
283 mProfile.setDefaultCommodity(defaultCommodity.getValue());
284 mProfile.setShowCommodityByDefault(showCommodityByDefault.getValue());
285 mProfile.setPreferredAccountsFilter(preferredAccountsFilter.getValue());
286 mProfile.setUseAuthentication(useAuthentication.getValue());
287 mProfile.setAuthUser(authUserName.getValue());
288 mProfile.setAuthPassword(authPassword.getValue());
289 mProfile.setTheme(themeId.getValue());
290 mProfile.setFutureDates(futureDates.getValue()
292 mProfile.setApiVersion(apiVersion.getValue()
294 HledgerVersion version = detectedVersion.getValue();
295 mProfile.setDetectedVersionPre_1_19(version != null && version.isPre_1_20_1());
296 mProfile.setDetectedVersionMajor(version != null ? version.getMajor() : -1);
297 mProfile.setDetectedVersionMinor(version != null ? version.getMinor() : -1);
299 synchronized public void triggerVersionDetection() {
300 if (versionDetectionThread != null)
301 versionDetectionThread.interrupt();
303 versionDetectionThread = new VersionDetectionThread(this);
304 versionDetectionThread.start();
306 public LiveData<Long> getProfileId() {
309 static class VersionDetectionThread extends Thread {
310 static final int TARGET_PROCESS_DURATION = 1000;
311 private final Pattern versionPattern =
312 Pattern.compile("^\"(\\d+)\\.(\\d+)(?:\\.(\\d+))?\"$");
313 private final ProfileDetailModel model;
314 public VersionDetectionThread(ProfileDetailModel model) {
317 private HledgerVersion detectVersion() {
318 App.setAuthenticationDataFromProfileModel(model);
319 HttpURLConnection http;
321 http = NetworkUtil.prepareConnection(model.getUrl(), "version",
322 model.getUseAuthentication());
323 switch (http.getResponseCode()) {
327 return new HledgerVersion(true);
329 Logger.debug("profile", String.format(Locale.US,
330 "HTTP error detecting hledger-web version: [%d] %s",
331 http.getResponseCode(), http.getResponseMessage()));
334 InputStream stream = http.getInputStream();
335 BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
336 String version = reader.readLine();
337 Matcher m = versionPattern.matcher(version);
339 int major = Integer.parseInt(Objects.requireNonNull(m.group(1)));
340 int minor = Integer.parseInt(Objects.requireNonNull(m.group(2)));
341 final String patchText = m.group(3);
342 final boolean hasPatch = patchText != null;
343 int patch = hasPatch ? Integer.parseInt(patchText) : 0;
345 return hasPatch ? new HledgerVersion(major, minor, patch)
346 : new HledgerVersion(major, minor);
349 Logger.debug("profile",
350 String.format("Unrecognised version string '%s'", version));
354 catch (IOException e) {
359 App.resetAuthenticationData();
364 model.detectingHledgerVersion.postValue(true);
366 long startTime = System.currentTimeMillis();
368 final HledgerVersion version = detectVersion();
370 long elapsed = System.currentTimeMillis() - startTime;
371 Logger.debug("profile", "Detection duration " + elapsed);
372 if (elapsed < TARGET_PROCESS_DURATION) {
374 Thread.sleep(TARGET_PROCESS_DURATION - elapsed);
376 catch (InterruptedException e) {
380 model.detectedVersion.postValue(version);
383 model.detectingHledgerVersion.postValue(false);