From 2348c82d94fff9a52fc70f6bc1ccd4fae56291d6 Mon Sep 17 00:00:00 2001 From: Joshua Kuestersteffen Date: Tue, 20 Jun 2023 16:04:44 -0500 Subject: [PATCH 1/7] chore: add hilt to gradle build and start integrating it with SettingsDialogActivity --- build.gradle | 4 ++++ src/main/AndroidManifest.xml | 3 ++- .../webapp/mobile/ChtAndroidApplication.java | 9 +++++++++ .../webapp/mobile/SettingsDialogActivity.java | 10 ++++++++-- .../webapp/mobile/SettingsStore.java | 2 +- .../mobile/modules/SettingsStoreModule.java | 20 +++++++++++++++++++ 6 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/medicmobile/webapp/mobile/ChtAndroidApplication.java create mode 100644 src/main/java/org/medicmobile/webapp/mobile/modules/SettingsStoreModule.java diff --git a/build.gradle b/build.gradle index c7c0881a..7ec96733 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.1.3' classpath 'com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.5' + classpath 'com.google.dagger:hilt-android-gradle-plugin:2.44.2' } } @@ -15,6 +16,7 @@ apply plugin: 'com.android.application' apply plugin: 'checkstyle' apply plugin: 'pmd' apply plugin: 'com.github.spotbugs' +apply plugin: 'com.google.dagger.hilt.android' apply from: 'coverage.gradle' // enable verbose lint warnings @@ -412,6 +414,8 @@ dependencies { implementation 'androidx.core:core:1.7.0' implementation 'androidx.activity:activity:1.4.0' implementation 'androidx.fragment:fragment:1.4.1' + implementation 'com.google.dagger:hilt-android:2.44.2' + annotationProcessor "com.google.dagger:hilt-compiler:2.44.2" compileOnly 'com.github.spotbugs:spotbugs-annotations:4.5.3' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' testImplementation 'junit:junit:4.13.2' diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index e9d68e7d..f195053a 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -26,7 +26,8 @@ --> - Date: Tue, 20 Jun 2023 16:35:44 -0500 Subject: [PATCH 2/7] chore: refactor SettingsStore internals to handle breaking out the SettingsDialogActivity --- .../mobile/SettingsDialogActivityTest.java | 1 + src/main/AndroidManifest.xml | 2 +- .../mobile/EmbeddedBrowserActivity.java | 2 + .../mobile/OpenSettingsDialogFragment.java | 2 + .../webapp/mobile/SettingsStore.java | 160 +++++++++--------- .../org/medicmobile/webapp/mobile/Utils.java | 1 + .../SettingsDialogActivity.java | 12 +- .../OpenSettingsDialogFragmentTest.java | 1 + 8 files changed, 98 insertions(+), 83 deletions(-) rename src/main/java/org/medicmobile/webapp/mobile/{ => components/settings_dialog}/SettingsDialogActivity.java (94%) diff --git a/src/androidTestUnbrandedDebug/java/org/medicmobile/webapp/mobile/SettingsDialogActivityTest.java b/src/androidTestUnbrandedDebug/java/org/medicmobile/webapp/mobile/SettingsDialogActivityTest.java index d7011b80..43de12f1 100644 --- a/src/androidTestUnbrandedDebug/java/org/medicmobile/webapp/mobile/SettingsDialogActivityTest.java +++ b/src/androidTestUnbrandedDebug/java/org/medicmobile/webapp/mobile/SettingsDialogActivityTest.java @@ -39,6 +39,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.MethodSorters; +import org.medicmobile.webapp.mobile.components.settings_dialog.SettingsDialogActivity; import java.util.Locale; diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index f195053a..6b611219 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -49,7 +49,7 @@ android:launchMode="singleTop"/> - diff --git a/src/main/java/org/medicmobile/webapp/mobile/EmbeddedBrowserActivity.java b/src/main/java/org/medicmobile/webapp/mobile/EmbeddedBrowserActivity.java index dafa0975..91d58887 100644 --- a/src/main/java/org/medicmobile/webapp/mobile/EmbeddedBrowserActivity.java +++ b/src/main/java/org/medicmobile/webapp/mobile/EmbeddedBrowserActivity.java @@ -34,6 +34,8 @@ import androidx.core.content.ContextCompat; +import org.medicmobile.webapp.mobile.SettingsStore.SettingsException; + import java.util.Arrays; import java.util.Optional; diff --git a/src/main/java/org/medicmobile/webapp/mobile/OpenSettingsDialogFragment.java b/src/main/java/org/medicmobile/webapp/mobile/OpenSettingsDialogFragment.java index bc688d76..8419bec5 100644 --- a/src/main/java/org/medicmobile/webapp/mobile/OpenSettingsDialogFragment.java +++ b/src/main/java/org/medicmobile/webapp/mobile/OpenSettingsDialogFragment.java @@ -11,6 +11,8 @@ import androidx.annotation.Nullable; +import org.medicmobile.webapp.mobile.components.settings_dialog.SettingsDialogActivity; + import java.time.Clock; @SuppressLint("ValidFragment") diff --git a/src/main/java/org/medicmobile/webapp/mobile/SettingsStore.java b/src/main/java/org/medicmobile/webapp/mobile/SettingsStore.java index 83710555..db287183 100644 --- a/src/main/java/org/medicmobile/webapp/mobile/SettingsStore.java +++ b/src/main/java/org/medicmobile/webapp/mobile/SettingsStore.java @@ -42,7 +42,7 @@ public boolean isRootUrl(String url) { public abstract void update(SharedPreferences.Editor ed, WebappSettings s); - void updateWith(WebappSettings s) throws SettingsException { + public void updateWith(WebappSettings s) throws SettingsException { s.validate(); SharedPreferences.Editor ed = prefs.edit(); @@ -101,6 +101,85 @@ public static SettingsStore in(Context ctx) { Boolean allowCustomHosts = ctx.getResources().getBoolean(R.bool.allowCustomHosts); return new UnbrandedSettingsStore(prefs, allowCustomHosts); } + + public static class WebappSettings { + + public static final Pattern URL_PATTERN = Pattern.compile("http[s]?://([^/:]*)(:\\d*)?(.*)"); + + public final String appUrl; + + public WebappSettings(String appUrl) { + trace(this, "WebappSettings() :: appUrl: %s", redactUrl(appUrl)); + this.appUrl = appUrl; + } + + public void validate() throws IllegalSettingsException { + List errors = new LinkedList<>(); + + if (!isSet(appUrl)) { + errors.add(new IllegalSetting(R.id.txtAppUrl, R.string.errRequired)); + } else if (!URL_PATTERN.matcher(appUrl).matches()) { + errors.add(new IllegalSetting(R.id.txtAppUrl, R.string.errInvalidUrl)); + } + + if (!errors.isEmpty()) { + throw new IllegalSettingsException(errors); + } + } + + public void update(SharedPreferences.Editor ed, WebappSettings s) { + ed.putString("app-url", s.appUrl); + } + + private boolean isSet(String val) { + return val != null && val.length() > 0; + } + } + + public static class IllegalSetting { + + public final int componentId; + public final int errorStringId; + + public IllegalSetting(int componentId, int errorStringId) { + this.componentId = componentId; + this.errorStringId = errorStringId; + } + } + + public static class SettingsException extends Exception { + // See: https://pmd.github.io/pmd-6.36.0/pmd_rules_java_errorprone.html#missingserialversionuid + public static final long serialVersionUID = -1008287132276329302L; + + public SettingsException(String message) { + super(message); + } + } + + public static class IllegalSettingsException extends SettingsException { + + public final List errors; + + public IllegalSettingsException(List errors) { + super(createMessage(errors)); + this.errors = errors; + } + + private static String createMessage(List errors) { + if (DEBUG) { + StringBuilder bob = new StringBuilder(); + for (IllegalSetting e : errors) { + if (bob.length() > 0) { + bob.append("; "); + } + + bob.append(String.format("component[%s]: error[%s]", e.componentId, e.errorStringId)); + } + return bob.toString(); + } + return null; + } + } } @SuppressWarnings("PMD.CallSuperInConstructor") @@ -153,82 +232,3 @@ public void update(SharedPreferences.Editor ed, WebappSettings s) { ed.putString("app-url", s.appUrl); } } - -class WebappSettings { - - public static final Pattern URL_PATTERN = Pattern.compile("http[s]?://([^/:]*)(:\\d*)?(.*)"); - - public final String appUrl; - - public WebappSettings(String appUrl) { - trace(this, "WebappSettings() :: appUrl: %s", redactUrl(appUrl)); - this.appUrl = appUrl; - } - - public void validate() throws IllegalSettingsException { - List errors = new LinkedList<>(); - - if (!isSet(appUrl)) { - errors.add(new IllegalSetting(R.id.txtAppUrl, R.string.errRequired)); - } else if (!URL_PATTERN.matcher(appUrl).matches()) { - errors.add(new IllegalSetting(R.id.txtAppUrl, R.string.errInvalidUrl)); - } - - if (!errors.isEmpty()) { - throw new IllegalSettingsException(errors); - } - } - - public void update(SharedPreferences.Editor ed, WebappSettings s) { - ed.putString("app-url", s.appUrl); - } - - private boolean isSet(String val) { - return val != null && val.length() > 0; - } -} - -class IllegalSetting { - - public final int componentId; - public final int errorStringId; - - public IllegalSetting(int componentId, int errorStringId) { - this.componentId = componentId; - this.errorStringId = errorStringId; - } -} - -class SettingsException extends Exception { - // See: https://pmd.github.io/pmd-6.36.0/pmd_rules_java_errorprone.html#missingserialversionuid - public static final long serialVersionUID = -1008287132276329302L; - - public SettingsException(String message) { - super(message); - } -} - -class IllegalSettingsException extends SettingsException { - - public final List errors; - - public IllegalSettingsException(List errors) { - super(createMessage(errors)); - this.errors = errors; - } - - private static String createMessage(List errors) { - if (DEBUG) { - StringBuilder bob = new StringBuilder(); - for (IllegalSetting e : errors) { - if (bob.length() > 0) { - bob.append("; "); - } - - bob.append(String.format("component[%s]: error[%s]", e.componentId, e.errorStringId)); - } - return bob.toString(); - } - return null; - } -} diff --git a/src/main/java/org/medicmobile/webapp/mobile/Utils.java b/src/main/java/org/medicmobile/webapp/mobile/Utils.java index e4188663..dce6e246 100644 --- a/src/main/java/org/medicmobile/webapp/mobile/Utils.java +++ b/src/main/java/org/medicmobile/webapp/mobile/Utils.java @@ -11,6 +11,7 @@ import org.json.JSONException; import org.json.JSONObject; +import org.medicmobile.webapp.mobile.components.settings_dialog.SettingsDialogActivity; import java.io.File; import java.util.Optional; diff --git a/src/main/java/org/medicmobile/webapp/mobile/SettingsDialogActivity.java b/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/SettingsDialogActivity.java similarity index 94% rename from src/main/java/org/medicmobile/webapp/mobile/SettingsDialogActivity.java rename to src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/SettingsDialogActivity.java index 89cf4ea5..b6199142 100644 --- a/src/main/java/org/medicmobile/webapp/mobile/SettingsDialogActivity.java +++ b/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/SettingsDialogActivity.java @@ -1,7 +1,7 @@ -package org.medicmobile.webapp.mobile; +package org.medicmobile.webapp.mobile.components.settings_dialog; -import static org.medicmobile.webapp.mobile.MedicLog.trace; import static org.medicmobile.webapp.mobile.MedicLog.error; +import static org.medicmobile.webapp.mobile.MedicLog.trace; import static org.medicmobile.webapp.mobile.SimpleJsonClient2.redactUrl; import android.annotation.SuppressLint; @@ -21,6 +21,14 @@ import androidx.fragment.app.FragmentActivity; +import org.medicmobile.webapp.mobile.AppUrlVerifier; +import org.medicmobile.webapp.mobile.EmbeddedBrowserActivity; +import org.medicmobile.webapp.mobile.R; +import org.medicmobile.webapp.mobile.SettingsStore; +import org.medicmobile.webapp.mobile.SettingsStore.IllegalSettingsException; +import org.medicmobile.webapp.mobile.SettingsStore.IllegalSetting; +import org.medicmobile.webapp.mobile.SettingsStore.SettingsException; +import org.medicmobile.webapp.mobile.SettingsStore.WebappSettings; import org.medicmobile.webapp.mobile.adapters.FilterableListAdapter; import org.medicmobile.webapp.mobile.dialogs.ConfirmServerSelectionDialog; import org.medicmobile.webapp.mobile.listeners.TextChangedListener; diff --git a/src/test/java/org/medicmobile/webapp/mobile/OpenSettingsDialogFragmentTest.java b/src/test/java/org/medicmobile/webapp/mobile/OpenSettingsDialogFragmentTest.java index b0a0a1e2..bbffee77 100644 --- a/src/test/java/org/medicmobile/webapp/mobile/OpenSettingsDialogFragmentTest.java +++ b/src/test/java/org/medicmobile/webapp/mobile/OpenSettingsDialogFragmentTest.java @@ -21,6 +21,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.medicmobile.webapp.mobile.components.settings_dialog.SettingsDialogActivity; import org.mockito.ArgumentCaptor; import org.mockito.MockSettings; import org.mockito.MockedStatic; From 1c75595eda11242b3fe35c06dbdee3455ca7c5b3 Mon Sep 17 00:00:00 2001 From: Joshua Kuestersteffen Date: Wed, 21 Jun 2023 11:03:38 -0500 Subject: [PATCH 3/7] chore: break out inner classes from SettingsDialogActivity --- .../adapters/ServerMetadataAdapter.java | 40 ++++++ .../settings_dialog/ServerMetadata.java | 19 +++ .../settings_dialog/ServerRepo.java | 136 ++++++++++++++++++ .../SettingsDialogActivity.java | 30 +--- .../mobile/modules/SettingsStoreModule.java | 4 +- 5 files changed, 205 insertions(+), 24 deletions(-) create mode 100644 src/main/java/org/medicmobile/webapp/mobile/adapters/ServerMetadataAdapter.java create mode 100644 src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/ServerMetadata.java create mode 100644 src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/ServerRepo.java diff --git a/src/main/java/org/medicmobile/webapp/mobile/adapters/ServerMetadataAdapter.java b/src/main/java/org/medicmobile/webapp/mobile/adapters/ServerMetadataAdapter.java new file mode 100644 index 00000000..8b50b286 --- /dev/null +++ b/src/main/java/org/medicmobile/webapp/mobile/adapters/ServerMetadataAdapter.java @@ -0,0 +1,40 @@ +package org.medicmobile.webapp.mobile.adapters; + +import android.content.Context; + +import org.medicmobile.webapp.mobile.R; +import org.medicmobile.webapp.mobile.components.settings_dialog.ServerMetadata; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class ServerMetadataAdapter extends FilterableListAdapter { + private ServerMetadataAdapter(Context context, List> data) { + super( + context, + data, + R.layout.server_list_item, + new String[]{"name", "url"}, + new int[]{R.id.txtName, R.id.txtUrl}, + "name", "url" + ); + } + + public static ServerMetadataAdapter createInstance(Context context, List servers) { + return new ServerMetadataAdapter(context, adapt(servers)); + } + + private static List> adapt(List servers) { + return servers + .stream() + .map(server -> { + Map serverProperties = new HashMap<>(); + serverProperties.put("name", server.name); + serverProperties.put("url", server.url); + return serverProperties; + }) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/ServerMetadata.java b/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/ServerMetadata.java new file mode 100644 index 00000000..7c89abda --- /dev/null +++ b/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/ServerMetadata.java @@ -0,0 +1,19 @@ +package org.medicmobile.webapp.mobile.components.settings_dialog; + +import static org.medicmobile.webapp.mobile.MedicLog.trace; +import static org.medicmobile.webapp.mobile.SimpleJsonClient2.redactUrl; + +public class ServerMetadata { + public final String name; + public final String url; + + ServerMetadata(String name) { + this(name, null); + } + + ServerMetadata(String name, String url) { + trace(this, "ServerMetadata() :: name: %s, url: %s", name, redactUrl(url)); + this.name = name; + this.url = url; + } +} diff --git a/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/ServerRepo.java b/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/ServerRepo.java new file mode 100644 index 00000000..28ae8e53 --- /dev/null +++ b/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/ServerRepo.java @@ -0,0 +1,136 @@ +package org.medicmobile.webapp.mobile.components.settings_dialog; + +import static org.medicmobile.webapp.mobile.MedicLog.error; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; + +import org.medicmobile.webapp.mobile.R; +import org.medicmobile.webapp.mobile.SettingsStore; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; + +import dagger.hilt.android.qualifiers.ActivityContext; +import dagger.hilt.android.scopes.ActivityScoped; + +@ActivityScoped +class ServerRepo { + private final SharedPreferences prefs; + private final SettingsStore settingsStore; + + @Inject + ServerRepo(@ActivityContext Context ctx, SettingsStore settingsStore) { + prefs = ctx.getSharedPreferences( + "ServerRepo", + Context.MODE_PRIVATE); + + this.settingsStore = settingsStore; + + Map instances = parseInstanceXML(ctx); + for (Map.Entry entry : instances.entrySet()) { + String instanceName = entry.getValue(); + String instanceUrl = entry.getKey(); + + save(instanceName, instanceUrl); + } + } + + List getServers() { + List servers = new LinkedList(); + + for (Map.Entry e : prefs.getAll().entrySet()) { + servers.add(new ServerMetadata( + e.getValue().toString(), + e.getKey())); + } + + Collections.sort(servers, Comparator.comparing(server -> server.name)); + + if (this.settingsStore.allowCustomHosts()) { + servers.add(0, new ServerMetadata("Custom")); + } + + return servers; + } + + void save(String url) { + save(friendly(url), url); + } + + void save(String name, String url) { + SharedPreferences.Editor ed = prefs.edit(); + ed.putString(url, name); + ed.apply(); + } + + private static Map parseInstanceXML(Context context) { + try { + HashMap result = new HashMap<>(); + + Resources resources = context.getResources(); + XmlResourceParser xmlParser = resources.getXml(R.xml.instances); + + while (xmlParser.next() != XmlPullParser.END_TAG) { + if (xmlParser.getEventType() != XmlPullParser.START_TAG + || !"instance".equals(xmlParser.getName())) { + continue; + } + String name = xmlParser.getAttributeValue(null, "name"); + String url = xmlParser.nextText(); + if (name == null) { + name = friendly(url); + } + result.put(url, name); + } + + return result; + } catch (XmlPullParserException | IOException e) { + error(e, "Failed to load instances data from xml."); + return Collections.emptyMap(); + } + } + + + @SuppressLint("DefaultLocale") + private static String friendly(String url) { + int slashes = url.indexOf("//"); + + if (slashes != -1) { + url = url.substring(slashes + 2); + } + + if (url.endsWith(".medicmobile.org")) { + url = url.substring(0, url.length() - ".medicmobile.org".length()); + } + + if (url.endsWith(".medicmobile.org/")) { + url = url.substring(0, url.length() - ".medicmobile.org/".length()); + } + + if (url.startsWith("192.168.")) { + return url.substring("192.168.".length()); + } else { + String[] parts = url.split("\\."); + StringBuilder stringBuilder = new StringBuilder(); + for (String p : parts) { + stringBuilder.append(" "); + stringBuilder.append(p.substring(0, 1).toUpperCase()); + stringBuilder.append(p.substring(1)); + } + return stringBuilder.toString().substring(1); + } + } +} diff --git a/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/SettingsDialogActivity.java b/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/SettingsDialogActivity.java index b6199142..4753b073 100644 --- a/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/SettingsDialogActivity.java +++ b/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/SettingsDialogActivity.java @@ -1,15 +1,8 @@ package org.medicmobile.webapp.mobile.components.settings_dialog; -import static org.medicmobile.webapp.mobile.MedicLog.error; import static org.medicmobile.webapp.mobile.MedicLog.trace; -import static org.medicmobile.webapp.mobile.SimpleJsonClient2.redactUrl; -import android.annotation.SuppressLint; -import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.content.res.XmlResourceParser; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; @@ -25,25 +18,16 @@ import org.medicmobile.webapp.mobile.EmbeddedBrowserActivity; import org.medicmobile.webapp.mobile.R; import org.medicmobile.webapp.mobile.SettingsStore; -import org.medicmobile.webapp.mobile.SettingsStore.IllegalSettingsException; import org.medicmobile.webapp.mobile.SettingsStore.IllegalSetting; +import org.medicmobile.webapp.mobile.SettingsStore.IllegalSettingsException; import org.medicmobile.webapp.mobile.SettingsStore.SettingsException; import org.medicmobile.webapp.mobile.SettingsStore.WebappSettings; -import org.medicmobile.webapp.mobile.adapters.FilterableListAdapter; +import org.medicmobile.webapp.mobile.adapters.ServerMetadataAdapter; import org.medicmobile.webapp.mobile.dialogs.ConfirmServerSelectionDialog; import org.medicmobile.webapp.mobile.listeners.TextChangedListener; import org.medicmobile.webapp.mobile.util.AsyncExecutor; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.LinkedList; + import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; import javax.inject.Inject; @@ -56,15 +40,15 @@ public class SettingsDialogActivity extends FragmentActivity { @Inject SettingsStore settings; - private ServerRepo serverRepo; + + @Inject + ServerRepo serverRepo; + private int state; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); trace(this, "onCreate()"); - - this.serverRepo = new ServerRepo(this, this.settings); - displayServerSelectList(); } diff --git a/src/main/java/org/medicmobile/webapp/mobile/modules/SettingsStoreModule.java b/src/main/java/org/medicmobile/webapp/mobile/modules/SettingsStoreModule.java index b20a4a89..0e3722ad 100644 --- a/src/main/java/org/medicmobile/webapp/mobile/modules/SettingsStoreModule.java +++ b/src/main/java/org/medicmobile/webapp/mobile/modules/SettingsStoreModule.java @@ -9,12 +9,14 @@ import dagger.hilt.InstallIn; import dagger.hilt.android.components.ActivityComponent; import dagger.hilt.android.qualifiers.ActivityContext; +import dagger.hilt.android.scopes.ActivityScoped; @Module @InstallIn(ActivityComponent.class) public class SettingsStoreModule { @Provides - public static SettingsStore provideAnalyticsService(@ActivityContext Context ctx) { + @ActivityScoped + public static SettingsStore provideSettingsStore(@ActivityContext Context ctx) { return SettingsStore.in(ctx); } } From 8c76f4c7ed7f0972df75f7cfa32064cf95a59517 Mon Sep 17 00:00:00 2001 From: Joshua Kuestersteffen Date: Wed, 21 Jun 2023 11:05:49 -0500 Subject: [PATCH 4/7] chore: move SettingStoreModule into the SettingsStore --- .../webapp/mobile/SettingsStore.java | 33 +++++++++++++++---- .../mobile/modules/SettingsStoreModule.java | 22 ------------- 2 files changed, 26 insertions(+), 29 deletions(-) delete mode 100644 src/main/java/org/medicmobile/webapp/mobile/modules/SettingsStoreModule.java diff --git a/src/main/java/org/medicmobile/webapp/mobile/SettingsStore.java b/src/main/java/org/medicmobile/webapp/mobile/SettingsStore.java index db287183..d55da26c 100644 --- a/src/main/java/org/medicmobile/webapp/mobile/SettingsStore.java +++ b/src/main/java/org/medicmobile/webapp/mobile/SettingsStore.java @@ -1,15 +1,24 @@ package org.medicmobile.webapp.mobile; -import android.content.*; -import android.net.Uri; - -import java.util.*; -import java.util.regex.*; - import static org.medicmobile.webapp.mobile.BuildConfig.DEBUG; import static org.medicmobile.webapp.mobile.BuildConfig.TTL_LAST_URL; -import static org.medicmobile.webapp.mobile.SimpleJsonClient2.redactUrl; import static org.medicmobile.webapp.mobile.MedicLog.trace; +import static org.medicmobile.webapp.mobile.SimpleJsonClient2.redactUrl; + +import android.content.Context; +import android.content.SharedPreferences; +import android.net.Uri; + +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Pattern; + +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.android.components.ActivityComponent; +import dagger.hilt.android.qualifiers.ActivityContext; +import dagger.hilt.android.scopes.ActivityScoped; @SuppressWarnings("PMD.ShortMethodName") public abstract class SettingsStore { @@ -102,6 +111,16 @@ public static SettingsStore in(Context ctx) { return new UnbrandedSettingsStore(prefs, allowCustomHosts); } + @Module + @InstallIn(ActivityComponent.class) + public static class SettingsStoreModule { + @Provides + @ActivityScoped + public static SettingsStore provideSettingsStore(@ActivityContext Context ctx) { + return SettingsStore.in(ctx); + } + } + public static class WebappSettings { public static final Pattern URL_PATTERN = Pattern.compile("http[s]?://([^/:]*)(:\\d*)?(.*)"); diff --git a/src/main/java/org/medicmobile/webapp/mobile/modules/SettingsStoreModule.java b/src/main/java/org/medicmobile/webapp/mobile/modules/SettingsStoreModule.java deleted file mode 100644 index 0e3722ad..00000000 --- a/src/main/java/org/medicmobile/webapp/mobile/modules/SettingsStoreModule.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.medicmobile.webapp.mobile.modules; - -import android.content.Context; - -import org.medicmobile.webapp.mobile.SettingsStore; - -import dagger.Module; -import dagger.Provides; -import dagger.hilt.InstallIn; -import dagger.hilt.android.components.ActivityComponent; -import dagger.hilt.android.qualifiers.ActivityContext; -import dagger.hilt.android.scopes.ActivityScoped; - -@Module -@InstallIn(ActivityComponent.class) -public class SettingsStoreModule { - @Provides - @ActivityScoped - public static SettingsStore provideSettingsStore(@ActivityContext Context ctx) { - return SettingsStore.in(ctx); - } -} From 50cb5138a9a460256628a61220e47b7ab577a44b Mon Sep 17 00:00:00 2001 From: Joshua Kuestersteffen Date: Wed, 21 Jun 2023 11:55:35 -0500 Subject: [PATCH 5/7] chore: merge in latest changes from master --- .../adapters/ServerMetadataAdapter.java | 10 +- .../settings_dialog/ServerMetadata.java | 2 +- .../SettingsDialogActivity.java | 157 ------------------ 3 files changed, 9 insertions(+), 160 deletions(-) diff --git a/src/main/java/org/medicmobile/webapp/mobile/adapters/ServerMetadataAdapter.java b/src/main/java/org/medicmobile/webapp/mobile/adapters/ServerMetadataAdapter.java index 8b50b286..9347c911 100644 --- a/src/main/java/org/medicmobile/webapp/mobile/adapters/ServerMetadataAdapter.java +++ b/src/main/java/org/medicmobile/webapp/mobile/adapters/ServerMetadataAdapter.java @@ -16,8 +16,8 @@ private ServerMetadataAdapter(Context context, List> data) { context, data, R.layout.server_list_item, - new String[]{"name", "url"}, - new int[]{R.id.txtName, R.id.txtUrl}, + new String[]{ "name", "url" }, + new int[]{ R.id.txtName, R.id.txtUrl }, "name", "url" ); } @@ -26,6 +26,12 @@ public static ServerMetadataAdapter createInstance(Context context, List dataMap = (Map) this.getItem(position); + return new ServerMetadata(dataMap.get("name"), dataMap.get("url")); + } + private static List> adapt(List servers) { return servers .stream() diff --git a/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/ServerMetadata.java b/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/ServerMetadata.java index 7c89abda..24267bf6 100644 --- a/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/ServerMetadata.java +++ b/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/ServerMetadata.java @@ -11,7 +11,7 @@ public class ServerMetadata { this(name, null); } - ServerMetadata(String name, String url) { + public ServerMetadata(String name, String url) { trace(this, "ServerMetadata() :: name: %s, url: %s", name, redactUrl(url)); this.name = name; this.url = url; diff --git a/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/SettingsDialogActivity.java b/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/SettingsDialogActivity.java index 4753b073..3dda9a7a 100644 --- a/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/SettingsDialogActivity.java +++ b/src/main/java/org/medicmobile/webapp/mobile/components/settings_dialog/SettingsDialogActivity.java @@ -202,161 +202,4 @@ public void onItemClick(AdapterView parent, final View view, int position, lo } } } - - static class ServerMetadataAdapter extends FilterableListAdapter { - private ServerMetadataAdapter(Context context, List> data) { - super( - context, - data, - R.layout.server_list_item, - new String[]{ "name", "url" }, - new int[]{ R.id.txtName, R.id.txtUrl }, - "name", "url" - ); - } - - static ServerMetadataAdapter createInstance(Context context, List servers) { - return new ServerMetadataAdapter(context, adapt(servers)); - } - - @SuppressWarnings("unchecked") - ServerMetadata getServerMetadata(int position) { - Map dataMap = (Map) this.getItem(position); - return new ServerMetadata(dataMap.get("name"), dataMap.get("url")); - } - - private static List> adapt(List servers) { - return servers - .stream() - .map(server -> { - Map serverProperties = new HashMap<>(); - serverProperties.put("name", server.name); - serverProperties.put("url", server.url); - return serverProperties; - }) - .collect(Collectors.toList()); - } - } -} - -class ServerMetadata { - public final String name; - public final String url; - - ServerMetadata(String name) { - this(name, null); - } - - ServerMetadata(String name, String url) { - trace(this, "ServerMetadata() :: name: %s, url: %s", name, redactUrl(url)); - this.name = name; - this.url = url; - } -} - -class ServerRepo { - private final SharedPreferences prefs; - private final SettingsStore settingsStore; - - ServerRepo(Context ctx, SettingsStore settingsStore) { - prefs = ctx.getSharedPreferences( - "ServerRepo", - Context.MODE_PRIVATE); - - this.settingsStore = settingsStore; - - Map instances = parseInstanceXML(ctx); - for (Map.Entry entry : instances.entrySet()) { - String instanceName = entry.getValue(); - String instanceUrl = entry.getKey(); - - save(instanceName, instanceUrl); - } - } - - List getServers() { - List servers = new LinkedList(); - - for(Map.Entry e : prefs.getAll().entrySet()) { - servers.add(new ServerMetadata( - e.getValue().toString(), - e.getKey())); - } - - Collections.sort(servers, Comparator.comparing(server -> server.name)); - - if (this.settingsStore.allowCustomHosts()) { - servers.add(0, new ServerMetadata("Custom")); - } - - return servers; - } - - void save(String url) { - save(friendly(url), url); - } - - void save(String name, String url) { - SharedPreferences.Editor ed = prefs.edit(); - ed.putString(url, name); - ed.apply(); - } - - private static Map parseInstanceXML(Context context) { - try { - HashMap result = new HashMap<>(); - - Resources resources = context.getResources(); - XmlResourceParser xmlParser = resources.getXml(R.xml.instances); - - while (xmlParser.next() != XmlPullParser.END_TAG) { - if (xmlParser.getEventType() != XmlPullParser.START_TAG - || !"instance".equals(xmlParser.getName())) { - continue; - } - String name = xmlParser.getAttributeValue(null, "name"); - String url = xmlParser.nextText(); - if (name == null) { - name = friendly(url); - } - result.put(url, name); - } - - return result; - } catch (XmlPullParserException | IOException e) { - error(e, "Failed to load instances data from xml."); - return Collections.emptyMap(); - } - } - - - @SuppressLint("DefaultLocale") - private static String friendly(String url) { - int slashes = url.indexOf("//"); - - if(slashes != -1) { - url = url.substring(slashes + 2); - } - - if(url.endsWith(".medicmobile.org")) { - url = url.substring(0, url.length() - ".medicmobile.org".length()); - } - - if(url.endsWith(".medicmobile.org/")) { - url = url.substring(0, url.length() - ".medicmobile.org/".length()); - } - - if(url.startsWith("192.168.")) { - return url.substring("192.168.".length()); - } else { - String[] parts = url.split("\\."); - StringBuilder stringBuilder = new StringBuilder(); - for(String p : parts) { - stringBuilder.append(" "); - stringBuilder.append(p.substring(0, 1).toUpperCase()); - stringBuilder.append(p.substring(1)); - } - return stringBuilder.toString().substring(1); - } - } } From 8c0e42ef9010752ab2862d8287c47d3ad3e025a7 Mon Sep 17 00:00:00 2001 From: Joshua Kuestersteffen Date: Wed, 21 Jun 2023 12:16:49 -0500 Subject: [PATCH 6/7] chore: fix LoginTests import --- .../java/org/medicmobile/webapp/mobile/LoginTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/androidTestMedicmobilegammaDebug/java/org/medicmobile/webapp/mobile/LoginTests.java b/src/androidTestMedicmobilegammaDebug/java/org/medicmobile/webapp/mobile/LoginTests.java index 92ed8e95..f7a18f43 100644 --- a/src/androidTestMedicmobilegammaDebug/java/org/medicmobile/webapp/mobile/LoginTests.java +++ b/src/androidTestMedicmobilegammaDebug/java/org/medicmobile/webapp/mobile/LoginTests.java @@ -40,6 +40,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.MethodSorters; +import org.medicmobile.webapp.mobile.components.settings_dialog.SettingsDialogActivity; import java.util.Locale; @LargeTest From 2bb3679b5ff928aa19676828b1aa950bed0cee61 Mon Sep 17 00:00:00 2001 From: Joshua Kuestersteffen Date: Fri, 21 Jul 2023 17:15:47 -0500 Subject: [PATCH 7/7] chore: add ServerMetadataTest unit tests --- .../settings_dialog/ServerMetadataTest.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/test/java/org/medicmobile/webapp/mobile/components/settings_dialog/ServerMetadataTest.java diff --git a/src/test/java/org/medicmobile/webapp/mobile/components/settings_dialog/ServerMetadataTest.java b/src/test/java/org/medicmobile/webapp/mobile/components/settings_dialog/ServerMetadataTest.java new file mode 100644 index 00000000..8a561593 --- /dev/null +++ b/src/test/java/org/medicmobile/webapp/mobile/components/settings_dialog/ServerMetadataTest.java @@ -0,0 +1,60 @@ +package org.medicmobile.webapp.mobile.components.settings_dialog; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mockStatic; + +import org.junit.Test; +import org.medicmobile.webapp.mobile.MedicLog; +import org.medicmobile.webapp.mobile.SimpleJsonClient2; +import org.mockito.MockedStatic; + +public class ServerMetadataTest { + private final String NAME = "test_name"; + private final String URL = "https://test.url"; + private final String REDACTED_URL = "test.url"; + + @Test + public void construct_withUrl() { + try ( + MockedStatic medicLogMock = mockStatic(MedicLog.class); + MockedStatic jsonClientMock = mockStatic(SimpleJsonClient2.class) + ) { + jsonClientMock.when(() -> SimpleJsonClient2.redactUrl(URL)).thenReturn(REDACTED_URL); + + ServerMetadata metadata = new ServerMetadata(NAME, URL); + + assertEquals(NAME, metadata.name); + assertEquals(URL, metadata.url); + jsonClientMock.verify(() -> SimpleJsonClient2.redactUrl(URL)); + medicLogMock.verify(() -> MedicLog.trace( + any(ServerMetadata.class), + eq("ServerMetadata() :: name: %s, url: %s"), + eq(NAME), + eq(REDACTED_URL) + )); + } + } + + @Test + public void construct_withoutUrl() { + try ( + MockedStatic medicLogMock = mockStatic(MedicLog.class); + MockedStatic jsonClientMock = mockStatic(SimpleJsonClient2.class) + ) { + ServerMetadata metadata = new ServerMetadata(NAME); + + assertEquals(NAME, metadata.name); + assertNull(metadata.url); + jsonClientMock.verify(() -> SimpleJsonClient2.redactUrl((String) null)); + medicLogMock.verify(() -> MedicLog.trace( + any(ServerMetadata.class), + eq("ServerMetadata() :: name: %s, url: %s"), + eq(NAME), + eq(null) + )); + } + } +}