diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/CustomSyncServerSettingsFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/CustomSyncServerSettingsFragment.kt index 3db91e5e2b1d..7d78842a9101 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/CustomSyncServerSettingsFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/CustomSyncServerSettingsFragment.kt @@ -15,49 +15,50 @@ */ package com.ichi2.anki.preferences -import android.app.AlertDialog -import android.webkit.URLUtil -import androidx.preference.Preference -import androidx.preference.SwitchPreference +import android.content.SharedPreferences +import android.os.Bundle +import com.ichi2.anki.AnkiDroidApp import com.ichi2.anki.R import com.ichi2.anki.web.CustomSyncServer +import okhttp3.HttpUrl.Companion.toHttpUrl class CustomSyncServerSettingsFragment : SettingsFragment() { - override val preferenceResource: Int - get() = R.xml.preferences_custom_sync_server - override val analyticsScreenNameConstant: String - get() = "prefs.custom_sync_server" + override val preferenceResource = R.xml.preferences_custom_sync_server + override val analyticsScreenNameConstant = "prefs.custom_sync_server" override fun initSubscreen() { - // Use custom sync server - requirePreference(R.string.custom_sync_server_enable_key).setOnPreferenceChangeListener { _ -> - CustomSyncServer.handleSyncServerPreferenceChange(requireContext()) + listOf( + R.string.custom_sync_server_collection_url_key, + R.string.custom_sync_server_media_url_key + ).forEach { + requirePreference(it).continuousValidator = + VersatileTextPreference.Validator { value -> + if (value.isNotEmpty()) value.toHttpUrl() + } } - // Sync url - requirePreference(R.string.custom_sync_server_collection_url_key).setOnPreferenceChangeListener { _, newValue: Any -> - val newUrl = newValue.toString() - if (newUrl.isNotEmpty() && !URLUtil.isValidUrl(newUrl)) { - AlertDialog.Builder(requireContext()) - .setTitle(R.string.custom_sync_server_base_url_invalid) - .setPositiveButton(R.string.dialog_ok, null) - .show() - return@setOnPreferenceChangeListener false - } - CustomSyncServer.handleSyncServerPreferenceChange(requireContext()) - true - } - // Media url - requirePreference(R.string.custom_sync_server_media_url_key).setOnPreferenceChangeListener { _, newValue: Any -> - val newUrl = newValue.toString() - if (newUrl.isNotEmpty() && !URLUtil.isValidUrl(newUrl)) { - AlertDialog.Builder(requireContext()) - .setTitle(R.string.custom_sync_server_media_url_invalid) - .setPositiveButton(R.string.dialog_ok, null) - .show() - return@setOnPreferenceChangeListener false - } - CustomSyncServer.handleSyncServerPreferenceChange(requireContext()) - true + } + + // See discussion at https://github.com/ankidroid/Anki-Android/pull/12367#discussion_r967681337 + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + preferenceManager.sharedPreferences + ?.registerOnSharedPreferenceChangeListener(preferenceChangeListener) + } + + override fun onDestroy() { + super.onDestroy() + preferenceManager.sharedPreferences + ?.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener) + } + + private val preferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> + if ( + key == CustomSyncServer.PREFERENCE_CUSTOM_COLLECTION_SYNC_URL || + key == CustomSyncServer.PREFERENCE_CUSTOM_MEDIA_SYNC_URL || + key == CustomSyncServer.PREFERENCE_CUSTOM_COLLECTION_SYNC_SERVER_ENABLED || + key == CustomSyncServer.PREFERENCE_CUSTOM_MEDIA_SYNC_SERVER_ENABLED + ) { + CustomSyncServer.handleSyncServerPreferenceChange(AnkiDroidApp.instance) } } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/SyncSettingsFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/SyncSettingsFragment.kt index 3f6b82f574c7..1e5bf9a444a1 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/SyncSettingsFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/SyncSettingsFragment.kt @@ -55,10 +55,17 @@ class SyncSettingsFragment : SettingsFragment() { // Custom sync server requirePreference(R.string.custom_sync_server_key).setSummaryProvider { val preferences = AnkiDroidApp.getSharedPrefs(requireContext()) - if (!CustomSyncServer.isEnabled(preferences)) { - getString(R.string.disabled) + val collectionSyncUrl = CustomSyncServer.getCollectionSyncUrlIfSetAndEnabledOrNull(preferences) + val mediaSyncUrl = CustomSyncServer.getMediaSyncUrlIfSetAndEnabledOrNull(preferences) + + if (collectionSyncUrl == null && mediaSyncUrl == null) { + getString(R.string.custom_sync_server_summary_none_of_the_two_servers_used) } else { - CustomSyncServer.getCollectionSyncUrl(preferences) ?: "" + getString( + R.string.custom_sync_server_summary_both_or_either_of_the_two_servers_used, + collectionSyncUrl ?: getString(R.string.custom_sync_server_summary_placeholder_default), + mediaSyncUrl ?: getString(R.string.custom_sync_server_summary_placeholder_default) + ) } } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/PreferenceUpgradeService.kt b/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/PreferenceUpgradeService.kt index ade9206b6ecc..8896134d5b96 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/PreferenceUpgradeService.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/PreferenceUpgradeService.kt @@ -192,8 +192,8 @@ object PreferenceUpgradeService { */ internal class RemoveLegacyMediaSyncUrl : PreferenceUpgrade(3) { override fun upgrade(preferences: SharedPreferences) { - val mediaSyncUrl = CustomSyncServer.getMediaSyncUrl(preferences) ?: return - if (mediaSyncUrl.startsWith("https://msync.ankiweb.net")) { + val mediaSyncUrl = preferences.getString(CustomSyncServer.PREFERENCE_CUSTOM_MEDIA_SYNC_URL, null) + if (mediaSyncUrl?.startsWith("https://msync.ankiweb.net") == true) { preferences.edit { remove(CustomSyncServer.PREFERENCE_CUSTOM_MEDIA_SYNC_URL) } } } @@ -394,4 +394,5 @@ object PreferenceUpgradeService { object RemovedPreferences { const val PREFERENCE_CUSTOM_SYNC_BASE = "syncBaseUrl" + const val PREFERENCE_ENABLE_CUSTOM_SYNC_SERVER = "useCustomSyncServer" } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/web/CustomSyncServer.kt b/AnkiDroid/src/main/java/com/ichi2/anki/web/CustomSyncServer.kt index 9e7febfae278..a8d3d2fb0ed8 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/web/CustomSyncServer.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/web/CustomSyncServer.kt @@ -20,38 +20,33 @@ import android.content.Context import android.content.SharedPreferences import android.system.Os import com.ichi2.anki.AnkiDroidApp +import com.ichi2.anki.preferences.VersatileTextWithASwitchPreference import net.ankiweb.rsdroid.BackendFactory import timber.log.Timber object CustomSyncServer { const val PREFERENCE_CUSTOM_COLLECTION_SYNC_URL = "customCollectionSyncUrl" const val PREFERENCE_CUSTOM_MEDIA_SYNC_URL = "syncMediaUrl" - const val PREFERENCE_ENABLE_CUSTOM_SYNC_SERVER = "useCustomSyncServer" - fun getCollectionSyncUrl(preferences: SharedPreferences): String? { - return preferences.getString(PREFERENCE_CUSTOM_COLLECTION_SYNC_URL, null) - } - - fun getMediaSyncUrl(preferences: SharedPreferences): String? { - return preferences.getString(PREFERENCE_CUSTOM_MEDIA_SYNC_URL, null) - } + const val PREFERENCE_CUSTOM_COLLECTION_SYNC_SERVER_ENABLED = + PREFERENCE_CUSTOM_COLLECTION_SYNC_URL + VersatileTextWithASwitchPreference.SWITCH_SUFFIX + const val PREFERENCE_CUSTOM_MEDIA_SYNC_SERVER_ENABLED = + PREFERENCE_CUSTOM_MEDIA_SYNC_URL + VersatileTextWithASwitchPreference.SWITCH_SUFFIX fun getCollectionSyncUrlIfSetAndEnabledOrNull(preferences: SharedPreferences): String? { - if (!isEnabled(preferences)) return null - val collectionSyncUrl = getCollectionSyncUrl(preferences) + val enabled = preferences.getBoolean(PREFERENCE_CUSTOM_COLLECTION_SYNC_SERVER_ENABLED, false) + if (!enabled) return null + val collectionSyncUrl = preferences.getString(PREFERENCE_CUSTOM_COLLECTION_SYNC_URL, null) return if (collectionSyncUrl.isNullOrEmpty()) null else collectionSyncUrl } fun getMediaSyncUrlIfSetAndEnabledOrNull(preferences: SharedPreferences): String? { - if (!isEnabled(preferences)) return null - val mediaSyncUrl = getMediaSyncUrl(preferences) + val enabled = preferences.getBoolean(PREFERENCE_CUSTOM_MEDIA_SYNC_SERVER_ENABLED, false) + if (!enabled) return null + val mediaSyncUrl = preferences.getString(PREFERENCE_CUSTOM_MEDIA_SYNC_URL, null) return if (mediaSyncUrl.isNullOrEmpty()) null else mediaSyncUrl } - fun isEnabled(userPreferences: SharedPreferences): Boolean { - return userPreferences.getBoolean(PREFERENCE_ENABLE_CUSTOM_SYNC_SERVER, false) - } - fun handleSyncServerPreferenceChange(context: Context) { Timber.i("Sync Server Preferences updated.") // #4921 - if any of the preferences change, we should reset the HostNum. diff --git a/AnkiDroid/src/main/res/values/10-preferences.xml b/AnkiDroid/src/main/res/values/10-preferences.xml index 5bcd6cf4de17..ada8bc4b60ab 100644 --- a/AnkiDroid/src/main/res/values/10-preferences.xml +++ b/AnkiDroid/src/main/res/values/10-preferences.xml @@ -206,6 +206,16 @@ Number of iterations of the simulation Custom sync server + Not used + Collection: %1$s\nMedia: %2$s + default Note that unlike in older AnkiDroid versions, now you have to specify the full custom sync URL, e.g. https://example.com:8080/sync/.]]> - Use custom sync server - If you don\'t want to use the proprietary AnkiWeb service you can specify an alternative server here Sync url - Sync url invalid Media sync url - Media sync url invalid Keyboard Bluetooth diff --git a/AnkiDroid/src/main/res/values/preferences.xml b/AnkiDroid/src/main/res/values/preferences.xml index d1aae6b576df..8674de7e60dd 100644 --- a/AnkiDroid/src/main/res/values/preferences.xml +++ b/AnkiDroid/src/main/res/values/preferences.xml @@ -85,7 +85,6 @@ showLargeAnswerButtons customSyncServerScreen - useCustomSyncServer customCollectionSyncUrl syncMediaUrl diff --git a/AnkiDroid/src/main/res/xml/preferences_custom_sync_server.xml b/AnkiDroid/src/main/res/xml/preferences_custom_sync_server.xml index 0f75f60afe2a..fb8a7dd60ee0 100644 --- a/AnkiDroid/src/main/res/xml/preferences_custom_sync_server.xml +++ b/AnkiDroid/src/main/res/xml/preferences_custom_sync_server.xml @@ -8,19 +8,14 @@ android:key="@string/pref_custom_sync_server_screen_key"> - - - diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/servicemodel/PreferenceUpgradeServiceTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/servicemodel/PreferenceUpgradeServiceTest.kt index 97b928a163ef..fde1f07c8b37 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/servicemodel/PreferenceUpgradeServiceTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/servicemodel/PreferenceUpgradeServiceTest.kt @@ -111,12 +111,12 @@ class PreferenceUpgradeServiceTest : RobolectricTest() { } @Test - fun check_custom_media_sync_url() { - var syncURL = "https://msync.ankiweb.net" + fun `Legacy custom media sync URL is removed during upgrade`() { + val syncURL = "https://msync.ankiweb.net" mPrefs.edit { putString(CustomSyncServer.PREFERENCE_CUSTOM_MEDIA_SYNC_URL, syncURL) } - assertThat("Preference of custom media sync url is set to ($syncURL).", CustomSyncServer.getMediaSyncUrl(mPrefs).equals(syncURL)) + assertThat(mPrefs.getString(CustomSyncServer.PREFERENCE_CUSTOM_MEDIA_SYNC_URL, null), equalTo(syncURL)) PreferenceUpgrade.RemoveLegacyMediaSyncUrl().performUpgrade(mPrefs) - assertThat("Preference of custom media sync url is removed.", CustomSyncServer.getMediaSyncUrl(mPrefs).equals(null)) + assertThat(mPrefs.getString(CustomSyncServer.PREFERENCE_CUSTOM_MEDIA_SYNC_URL, null), equalTo(null)) } @Test diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/sync/HttpSyncerTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/sync/HttpSyncerTest.kt index f45873880adb..83f32accc9d1 100644 --- a/AnkiDroid/src/test/java/com/ichi2/libanki/sync/HttpSyncerTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/libanki/sync/HttpSyncerTest.kt @@ -97,14 +97,14 @@ class HttpSyncerTest { private fun setCustomServerWithNoUrl() { val userPreferences = AnkiDroidApp.getSharedPrefs(AnkiDroidApp.instance) userPreferences.edit { - putBoolean(CustomSyncServer.PREFERENCE_ENABLE_CUSTOM_SYNC_SERVER, true) + putBoolean(CustomSyncServer.PREFERENCE_CUSTOM_COLLECTION_SYNC_SERVER_ENABLED, true) } } private fun setCustomServer(s: String) { val userPreferences = AnkiDroidApp.getSharedPrefs(AnkiDroidApp.instance) userPreferences.edit { - putBoolean(CustomSyncServer.PREFERENCE_ENABLE_CUSTOM_SYNC_SERVER, true) + putBoolean(CustomSyncServer.PREFERENCE_CUSTOM_COLLECTION_SYNC_SERVER_ENABLED, true) putString(CustomSyncServer.PREFERENCE_CUSTOM_COLLECTION_SYNC_URL, s) } } diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/sync/RemoteMediaServerTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/sync/RemoteMediaServerTest.kt index 16a2a3e4ea70..e7d02f6d2ea0 100644 --- a/AnkiDroid/src/test/java/com/ichi2/libanki/sync/RemoteMediaServerTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/libanki/sync/RemoteMediaServerTest.kt @@ -16,8 +16,10 @@ package com.ichi2.libanki.sync +import androidx.core.content.edit import androidx.test.ext.junit.runners.AndroidJUnit4 import com.ichi2.anki.AnkiDroidApp +import com.ichi2.anki.web.CustomSyncServer import com.ichi2.utils.KotlinCleanup import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.equalTo @@ -91,19 +93,19 @@ class RemoteMediaServerTest { assertThat(syncUrl, equalTo(sDefaultUrlWithHostNum)) } - @KotlinCleanup("use edit{} extension function") private fun setCustomServerWithNoUrl() { val userPreferences = AnkiDroidApp.getSharedPrefs(AnkiDroidApp.instance) - userPreferences.edit().putBoolean("useCustomSyncServer", true).apply() + userPreferences.edit { + putBoolean(CustomSyncServer.PREFERENCE_CUSTOM_MEDIA_SYNC_SERVER_ENABLED, true) + } } - @KotlinCleanup("use edit{} extension function") private fun setCustomMediaServer(s: String) { val userPreferences = AnkiDroidApp.getSharedPrefs(AnkiDroidApp.instance) - val e = userPreferences.edit() - e.putBoolean("useCustomSyncServer", true) - e.putString("syncMediaUrl", s) - e.apply() + userPreferences.edit { + putBoolean(CustomSyncServer.PREFERENCE_CUSTOM_MEDIA_SYNC_SERVER_ENABLED, true) + putString(CustomSyncServer.PREFERENCE_CUSTOM_MEDIA_SYNC_URL, s) + } } private fun getServerWithHostNum(hostNum: Int?): RemoteMediaServer {