diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AnkiquestSettingsFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AnkiquestSettingsFragment.kt index 98b7051d7f83..9130031dc82f 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AnkiquestSettingsFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AnkiquestSettingsFragment.kt @@ -38,7 +38,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrl import org.json.JSONObject import java.util.Date -class AnkiquestSettingsFragment : SettingsFragment() { +open class AnkiquestSettingsFragment : SettingsFragment() { override val preferenceResource = R.xml.preferences_ankiquest override val analyticsScreenNameConstant = "prefs.ankiquest" @@ -75,37 +75,14 @@ class AnkiquestSettingsFragment : SettingsFragment() { override fun initSubscreen() { refreshServerSettings.clear() + bindDashboard(R.string.ankiquest_dashboard_key) + } + + protected fun bindAccount() { requirePreference(R.string.ankiquest_url_key).continuousValidator = VersatileTextPreference.Validator { value -> if (value.isNotEmpty()) value.toHttpUrl() } - requirePreference(R.string.ankiquest_notify_rank_key).setOnPreferenceChangeListener { _, enabled -> - if (enabled == true) askForNotifications() - true - } - requirePreference(R.string.ankiquest_streak_hours_key).setOnPreferenceChangeListener { _, hours -> - if (hours != "0") askForNotifications() - true - } - bindServerToggle( - R.string.ankiquest_nudges_key, - read = { Ankiquest.nudgesEnabled() }, - save = { Ankiquest.setNudges(it).getBoolean("nudges") }, - notify = true, - ) - bindServerToggle( - R.string.ankiquest_celebrations_key, - read = { Ankiquest.celebrationsEnabled() }, - save = { Ankiquest.setCelebrations(it).optBoolean("celebrations", it) }, - notify = true, - ) - bindServerToggle( - R.string.ankiquest_streak_protection_key, - read = { Ankiquest.streakProtectionEnabled() }, - save = { Ankiquest.setStreakProtection(it) }, - ) - bindDashboard(R.string.ankiquest_dashboard_key) - bindDashboard(R.string.ankiquest_community_reminders_key, community = true) requirePreference(R.string.ankiquest_avatar_key).setOnPreferenceClickListener { pictureAction { val account = checkNotNull(AnkiquestAvatars.account()) { getString(R.string.ankiquest_nudges_unavailable) } @@ -142,14 +119,48 @@ class AnkiquestSettingsFragment : SettingsFragment() { } true } + bindAction(R.string.ankiquest_test_key) { Ankiquest.runFromSettings(requireContext(), uploadAll = false) } + } + + protected fun bindStudy() { + bindServerToggle( + R.string.ankiquest_streak_protection_key, + read = { Ankiquest.streakProtectionEnabled() }, + save = { Ankiquest.setStreakProtection(it) }, + ) + } + + protected fun bindNotifications() { + requirePreference(R.string.ankiquest_notify_rank_key).setOnPreferenceChangeListener { _, enabled -> + if (enabled == true) askForNotifications() + true + } + requirePreference(R.string.ankiquest_streak_hours_key).setOnPreferenceChangeListener { _, hours -> + if (hours != "0") askForNotifications() + true + } + bindServerToggle( + R.string.ankiquest_nudges_key, + read = { Ankiquest.nudgesEnabled() }, + save = { Ankiquest.setNudges(it).getBoolean("nudges") }, + notify = true, + ) + bindServerToggle( + R.string.ankiquest_celebrations_key, + read = { Ankiquest.celebrationsEnabled() }, + save = { Ankiquest.setCelebrations(it).optBoolean("celebrations", it) }, + notify = true, + ) + bindDashboard(R.string.ankiquest_community_reminders_key, community = true) bindAlertSettings(R.string.ankiquest_message_alerts_key, nudge = false) bindAlertSettings(R.string.ankiquest_nudge_alerts_key, nudge = true) requirePreference(R.string.ankiquest_health_key).setOnPreferenceClickListener { showNotificationHealth() true } - bindAction(R.string.ankiquest_test_key) { Ankiquest.runFromSettings(requireContext(), uploadAll = false) } - bindAction(R.string.ankiquest_upload_all_key) { Ankiquest.runFromSettings(requireContext(), uploadAll = true) } + } + + protected fun bindSharing() { bindAction(R.string.ankiquest_deck_notifications_key) { try { showDeckNotifications(Ankiquest.deckNotificationSettings()) @@ -158,6 +169,10 @@ class AnkiquestSettingsFragment : SettingsFragment() { getString(R.string.ankiquest_check_failed, e.message ?: e.javaClass.simpleName) } } + } + + protected fun bindMaintenance() { + bindAction(R.string.ankiquest_upload_all_key) { Ankiquest.runFromSettings(requireContext(), uploadAll = true) } requirePreference(R.string.ankiquest_check_updates_key).summary = getString( R.string.ankiquest_check_updates_summary, diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AnkiquestSettingsSections.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AnkiquestSettingsSections.kt new file mode 100644 index 000000000000..1e3fe63eac2d --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AnkiquestSettingsSections.kt @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +package com.ichi2.anki.preferences + +import com.ichi2.anki.R + +class AnkiquestAccountSettingsFragment : AnkiquestSettingsFragment() { + override val preferenceResource = R.xml.preferences_ankiquest_account + override val analyticsScreenNameConstant = "prefs.ankiquest.account" + + override fun initSubscreen() = bindAccount() +} + +class AnkiquestStudySettingsFragment : AnkiquestSettingsFragment() { + override val preferenceResource = R.xml.preferences_ankiquest_study + override val analyticsScreenNameConstant = "prefs.ankiquest.study" + + override fun initSubscreen() = bindStudy() +} + +class AnkiquestNotificationsSettingsFragment : AnkiquestSettingsFragment() { + override val preferenceResource = R.xml.preferences_ankiquest_notifications + override val analyticsScreenNameConstant = "prefs.ankiquest.notifications" + + override fun initSubscreen() = bindNotifications() +} + +class AnkiquestSharingSettingsFragment : AnkiquestSettingsFragment() { + override val preferenceResource = R.xml.preferences_ankiquest_sharing + override val analyticsScreenNameConstant = "prefs.ankiquest.sharing" + + override fun initSubscreen() = bindSharing() +} + +class AnkiquestMaintenanceSettingsFragment : AnkiquestSettingsFragment() { + override val preferenceResource = R.xml.preferences_ankiquest_maintenance + override val analyticsScreenNameConstant = "prefs.ankiquest.maintenance" + + override fun initSubscreen() = bindMaintenance() +} diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/Preferences.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/Preferences.kt index 39a6c38f4738..da3c6ca49f36 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/Preferences.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/Preferences.kt @@ -311,6 +311,11 @@ fun getFragmentFromXmlRes( R.xml.preferences_backup_limits -> BackupLimitsSettingsFragment() R.xml.preferences_custom_sync_server -> CustomSyncServerSettingsFragment() R.xml.preferences_ankiquest -> AnkiquestSettingsFragment() + R.xml.preferences_ankiquest_account -> AnkiquestAccountSettingsFragment() + R.xml.preferences_ankiquest_study -> AnkiquestStudySettingsFragment() + R.xml.preferences_ankiquest_notifications -> AnkiquestNotificationsSettingsFragment() + R.xml.preferences_ankiquest_sharing -> AnkiquestSharingSettingsFragment() + R.xml.preferences_ankiquest_maintenance -> AnkiquestMaintenanceSettingsFragment() R.xml.preferences_ankiquest_subscriptions -> AnkiquestSubscriptionsFragment() R.xml.preferences_notifications -> NotificationsSettingsFragment() R.xml.preferences_appearance -> AppearanceSettingsFragment() diff --git a/AnkiDroid/src/main/res/values-de/ankiquest.xml b/AnkiDroid/src/main/res/values-de/ankiquest.xml index 9d6ff39ef845..b00a8db5ab3b 100644 --- a/AnkiDroid/src/main/res/values-de/ankiquest.xml +++ b/AnkiDroid/src/main/res/values-de/ankiquest.xml @@ -1,6 +1,18 @@ + Konto und Verbindung + Server, Spieler, Token und Profilbild + Lernen und Serien + Tägliches Lernen und Serienschutz + Erinnerungen und Hinweise + Erinnerungen, Hinweise und Alarme + Freunde und Teilen + Deckmeldungen und Abonnements + App und Daten + Uploads und App-Updates + Wann benachrichtigen + Benachrichtigungseinstellungen Rangliste Gemeinschaftserinnerungen Wähle Erinnerungen für Community-Events diff --git a/AnkiDroid/src/main/res/values-es/ankiquest.xml b/AnkiDroid/src/main/res/values-es/ankiquest.xml index e841f9d8ea51..0e5230cc384f 100644 --- a/AnkiDroid/src/main/res/values-es/ankiquest.xml +++ b/AnkiDroid/src/main/res/values-es/ankiquest.xml @@ -1,6 +1,18 @@ + Cuenta y conexión + Servidor, jugador, token y foto de perfil + Estudio y rachas + Opciones de estudio diario y protección de racha + Recordatorios y alertas + Recordatorios, avisos y alertas + Amigos y contenido compartido + Avisos de mazos y suscripciones + Aplicación y datos + Subida de repasos y actualizaciones + Cuándo avisar + Ajustes de alertas Clasificación Recordatorios de la comunidad Elige recordatorios de eventos de la comunidad diff --git a/AnkiDroid/src/main/res/values-fr/ankiquest.xml b/AnkiDroid/src/main/res/values-fr/ankiquest.xml index ecae0564f09b..8db42da5384b 100644 --- a/AnkiDroid/src/main/res/values-fr/ankiquest.xml +++ b/AnkiDroid/src/main/res/values-fr/ankiquest.xml @@ -1,6 +1,18 @@ + Compte et connexion + Serveur, joueur, jeton et photo de profil + Étude et séries + Étude quotidienne et protection des séries + Rappels et alertes + Rappels, encouragements et alertes + Amis et partage + Actualités des paquets et abonnements + Application et données + Envois de révisions et mises à jour + Quand notifier + Paramètres des alertes Classement Rappels communautaires Choisir des rappels pour les événements communautaires diff --git a/AnkiDroid/src/main/res/values-pt/ankiquest.xml b/AnkiDroid/src/main/res/values-pt/ankiquest.xml index aa1265a1d3cb..82caf94555b9 100644 --- a/AnkiDroid/src/main/res/values-pt/ankiquest.xml +++ b/AnkiDroid/src/main/res/values-pt/ankiquest.xml @@ -1,6 +1,18 @@ + Conta e conexão + Servidor, jogador, token e foto de perfil + Estudo e sequências + Estudo diário e proteção de sequências + Lembretes e alertas + Lembretes, incentivos e alertas + Amigos e partilha + Avisos de baralhos e subscrições + Aplicação e dados + Envio de revisões e atualizações + Quando notificar + Definições dos alertas Classificação Avisos comunitários Escolha lembretes para eventos comunitários diff --git a/AnkiDroid/src/main/res/values/ankiquest.xml b/AnkiDroid/src/main/res/values/ankiquest.xml index 3a29171d42c5..4b12f153a197 100644 --- a/AnkiDroid/src/main/res/values/ankiquest.xml +++ b/AnkiDroid/src/main/res/values/ankiquest.xml @@ -1,6 +1,23 @@ + ankiquestAccountScreen + Account & connection + Server, player, token and profile picture + ankiquestStudyScreen + Study & streaks + Daily study options and streak protection + ankiquestNotificationsScreen + Reminders & alerts + Reminders, nudges and alert delivery + ankiquestSharingScreen + Friends & sharing + Deck updates and subscriptions + ankiquestMaintenanceScreen + App & data + Review uploads and app updates + When to notify + Alert settings ankiquestDashboard Leaderboard ankiquestCommunityReminders diff --git a/AnkiDroid/src/main/res/xml/preferences_ankiquest.xml b/AnkiDroid/src/main/res/xml/preferences_ankiquest.xml index 456bdb861e95..fc14bce3f319 100644 --- a/AnkiDroid/src/main/res/xml/preferences_ankiquest.xml +++ b/AnkiDroid/src/main/res/xml/preferences_ankiquest.xml @@ -10,107 +10,34 @@ android:title="@string/ankiquest_dashboard_title" android:icon="@drawable/aki_face" android:summary="@string/aki_settings_summary" /> - - - - - - - - - - - + android:title="@string/ankiquest_account_title" + android:summary="@string/ankiquest_account_summary" /> + android:title="@string/ankiquest_study_title" + android:summary="@string/ankiquest_study_summary" /> + android:title="@string/ankiquest_notifications_title" + android:summary="@string/ankiquest_notifications_summary" /> - + android:title="@string/ankiquest_sharing_title" + android:summary="@string/ankiquest_sharing_summary" /> - - - - + android:title="@string/ankiquest_maintenance_title" + android:summary="@string/ankiquest_maintenance_summary" /> diff --git a/AnkiDroid/src/main/res/xml/preferences_ankiquest_account.xml b/AnkiDroid/src/main/res/xml/preferences_ankiquest_account.xml new file mode 100644 index 000000000000..b477c45a25a3 --- /dev/null +++ b/AnkiDroid/src/main/res/xml/preferences_ankiquest_account.xml @@ -0,0 +1,31 @@ + + + + + + + + + diff --git a/AnkiDroid/src/main/res/xml/preferences_ankiquest_maintenance.xml b/AnkiDroid/src/main/res/xml/preferences_ankiquest_maintenance.xml new file mode 100644 index 000000000000..13ddd84cb432 --- /dev/null +++ b/AnkiDroid/src/main/res/xml/preferences_ankiquest_maintenance.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/AnkiDroid/src/main/res/xml/preferences_ankiquest_notifications.xml b/AnkiDroid/src/main/res/xml/preferences_ankiquest_notifications.xml new file mode 100644 index 000000000000..e75421f4c0f6 --- /dev/null +++ b/AnkiDroid/src/main/res/xml/preferences_ankiquest_notifications.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + diff --git a/AnkiDroid/src/main/res/xml/preferences_ankiquest_sharing.xml b/AnkiDroid/src/main/res/xml/preferences_ankiquest_sharing.xml new file mode 100644 index 000000000000..c837b444a856 --- /dev/null +++ b/AnkiDroid/src/main/res/xml/preferences_ankiquest_sharing.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/AnkiDroid/src/main/res/xml/preferences_ankiquest_study.xml b/AnkiDroid/src/main/res/xml/preferences_ankiquest_study.xml new file mode 100644 index 000000000000..7df7c8f5df1f --- /dev/null +++ b/AnkiDroid/src/main/res/xml/preferences_ankiquest_study.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/ankiquest/AnkiquestNotificationHealthDialogTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/ankiquest/AnkiquestNotificationHealthDialogTest.kt index f417fafcf923..38c594850d62 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/ankiquest/AnkiquestNotificationHealthDialogTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/ankiquest/AnkiquestNotificationHealthDialogTest.kt @@ -9,6 +9,7 @@ import androidx.preference.Preference import androidx.test.ext.junit.runners.AndroidJUnit4 import com.ichi2.anki.R import com.ichi2.anki.RobolectricTest +import com.ichi2.anki.preferences.AnkiquestNotificationsSettingsFragment import com.ichi2.anki.preferences.AnkiquestSettingsFragment import com.ichi2.anki.preferences.PreferencesActivity import com.ichi2.anki.preferences.PreferencesFragment @@ -28,10 +29,18 @@ class AnkiquestNotificationHealthDialogTest : RobolectricTest() { val intent = PreferencesActivity.getIntent(targetContext, AnkiquestSettingsFragment::class) val controller = Robolectric.buildActivity(PreferencesActivity::class.java, intent).setup() saveControllerForCleanup(controller) - val fragment = + val menu = (controller.get().fragment as PreferencesFragment) .childFragmentManager .findFragmentById(R.id.settings_container) as AnkiquestSettingsFragment + assertNotNull( + menu.findPreference(targetContext.getString(R.string.ankiquest_notifications_screen_key)), + ).performClick() + shadowOf(Looper.getMainLooper()).idle() + val fragment = + (controller.get().fragment as PreferencesFragment) + .childFragmentManager + .findFragmentById(R.id.settings_container) as AnkiquestNotificationsSettingsFragment val health = assertNotNull(fragment.findPreference(targetContext.getString(R.string.ankiquest_health_key))) health.performClick() diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/preferences/AnkiquestSettingsMenuTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/preferences/AnkiquestSettingsMenuTest.kt new file mode 100644 index 000000000000..01897fd598c8 --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/anki/preferences/AnkiquestSettingsMenuTest.kt @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +package com.ichi2.anki.preferences + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.ichi2.anki.R +import com.ichi2.anki.RobolectricTest +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertEquals + +@RunWith(AndroidJUnit4::class) +class AnkiquestSettingsMenuTest : RobolectricTest() { + @Test + fun `each existing setting remains available exactly once`() { + val pages = + listOf( + R.xml.preferences_ankiquest, + R.xml.preferences_ankiquest_account, + R.xml.preferences_ankiquest_study, + R.xml.preferences_ankiquest_notifications, + R.xml.preferences_ankiquest_sharing, + R.xml.preferences_ankiquest_maintenance, + ) + val navigationKeys = + setOf( + R.string.ankiquest_screen_key, + R.string.ankiquest_account_screen_key, + R.string.ankiquest_study_screen_key, + R.string.ankiquest_notifications_screen_key, + R.string.ankiquest_sharing_screen_key, + R.string.ankiquest_maintenance_screen_key, + ).map(targetContext::getString).toSet() + val settings = + pages + .flatMap { PreferenceTestUtils.getAttrFromXml(targetContext, it, "key") } + .map { targetContext.getString(it.removePrefix("@").toInt()) } + .filterNot { it in navigationKeys } + val expected = + listOf( + R.string.ankiquest_dashboard_key, + R.string.ankiquest_url_key, + R.string.ankiquest_user_key, + R.string.ankiquest_token_key, + R.string.ankiquest_avatar_key, + R.string.ankiquest_test_key, + R.string.ankiquest_open_today_key, + R.string.ankiquest_summary_preference_key, + R.string.ankiquest_streak_protection_key, + R.string.ankiquest_notify_rank_key, + R.string.ankiquest_streak_hours_key, + R.string.ankiquest_nudges_key, + R.string.ankiquest_celebrations_key, + R.string.ankiquest_community_reminders_key, + R.string.ankiquest_message_alerts_key, + R.string.ankiquest_nudge_alerts_key, + R.string.ankiquest_health_key, + R.string.ankiquest_deck_notifications_key, + R.string.ankiquest_subscriptions_key, + R.string.ankiquest_upload_all_key, + R.string.ankiquest_update_channel_key, + R.string.ankiquest_check_updates_key, + ).map(targetContext::getString) + + assertEquals(expected.size, settings.size, "A setting was omitted or duplicated") + assertEquals(expected.toSet(), settings.toSet()) + assertEquals(5, PreferenceTestUtils.getAttrFromXml(targetContext, R.xml.preferences_ankiquest, "fragment").size) + } +}