diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/ReviewerTest.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/ReviewerTest.kt
index 185a64bddd..00734356c4 100755
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/ReviewerTest.kt
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/ReviewerTest.kt
@@ -1,208 +1,208 @@
-/*
- * Copyright (c) 2023
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3 of the License, or (at your option) any later
- * version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
-package com.ichi2.anki
-
-import androidx.recyclerview.widget.RecyclerView
-import androidx.test.espresso.Espresso.onView
-import androidx.test.espresso.NoMatchingViewException
-import androidx.test.espresso.action.ViewActions.click
-import androidx.test.espresso.assertion.ViewAssertions.matches
-import androidx.test.espresso.contrib.RecyclerViewActions
-import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
-import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
-import androidx.test.espresso.matcher.ViewMatchers.withId
-import androidx.test.espresso.matcher.ViewMatchers.withResourceName
-import androidx.test.espresso.matcher.ViewMatchers.withText
-import androidx.test.ext.junit.rules.ActivityScenarioRule
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.ichi2.anki.tests.InstrumentedTest
-import com.ichi2.anki.tests.libanki.RetryRule
-import com.ichi2.anki.testutil.GrantStoragePermission.storagePermission
-import com.ichi2.anki.testutil.ThreadUtils
-import com.ichi2.anki.testutil.grantPermissions
-import com.ichi2.anki.testutil.notificationPermission
-import com.ichi2.libanki.Collection
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import java.lang.AssertionError
-
-@RunWith(AndroidJUnit4::class)
-class ReviewerTest : InstrumentedTest() {
-
- // Launch IntroductionActivity instead of DeckPicker activity because in CI
- // builds, it seems to create IntroductionActivity after the DeckPicker,
- // causing the DeckPicker activity to be destroyed. As a consequence, this
- // will throw RootViewWithoutFocusException when Espresso tries to interact
- // with an already destroyed activity. By launching IntroductionActivity, we
- // ensure that IntroductionActivity is launched first and navigate to the
- // DeckPicker -> Reviewer activities
- @get:Rule
- val activityScenarioRule = ActivityScenarioRule(IntroductionActivity::class.java)
-
- @get:Rule
- val runtimePermissionRule = grantPermissions(storagePermission, notificationPermission)
-
- @get:Rule
- val retry = RetryRule(10)
-
- @Test
- fun testCustomSchedulerWithCustomData() {
- col.cardStateCustomizer =
- """
- states.good.normal.review.easeFactor = 3.0;
- states.good.normal.review.scheduledDays = 123;
- customData.good.c += 1;
- """
- val note = addNoteUsingBasicModel("foo", "bar")
- val card = note.firstCard(col)
- val deck = col.decks.get(note.notetype.did)!!
- card.moveToReviewQueue()
- col.backend.updateCards(
- listOf(
- card.toBackendCard().toBuilder().setCustomData("""{"c":1}""").build()
- ),
- true
- )
-
- closeGetStartedScreenIfExists()
- closeBackupCollectionDialogIfExists()
- reviewDeckWithName(deck.name)
-
- var cardFromDb = col.getCard(card.id).toBackendCard()
- assertThat(cardFromDb.easeFactor, equalTo(card.factor))
- assertThat(cardFromDb.interval, equalTo(card.ivl))
- assertThat(cardFromDb.customData, equalTo("""{"c":1}"""))
-
- clickShowAnswerAndAnswerGood()
-
- fun runAssertion() {
- cardFromDb = col.getCard(card.id).toBackendCard()
- assertThat(cardFromDb.easeFactor, equalTo(3000))
- assertThat(cardFromDb.interval, equalTo(123))
- assertThat(cardFromDb.customData, equalTo("""{"c":2}"""))
- }
-
- try {
- runAssertion()
- } catch (e: Exception) {
- // Give separate threads a greater chance of doing the custom scheduling
- // if the card scheduling values aren't updated immediately
- ThreadUtils.sleep(2000)
- runAssertion()
- }
- }
-
- @Test
- fun testCustomSchedulerWithRuntimeError() {
- // Issue 15035 - runtime errors weren't handled
- col.cardStateCustomizer = "states.this_is_not_defined.normal.review = 12;"
- addNoteUsingBasicModel()
-
- closeGetStartedScreenIfExists()
- closeBackupCollectionDialogIfExists()
- reviewDeckWithName("Default")
-
- clickShowAnswer()
-
- ensureAnswerButtonsAreDisplayed()
- }
-
- private fun clickOnDeckWithName(deckName: String) {
- onView(withId(R.id.files)).checkWithTimeout(matches(hasDescendant(withText(deckName))))
- onView(withId(R.id.files)).perform(
- RecyclerViewActions.actionOnItem(
- hasDescendant(withText(deckName)),
- click()
- )
- )
- }
-
- private fun clickOnStudyButtonIfExists() {
- onView(withId(R.id.studyoptions_start))
- .withFailureHandler { _, _ -> }
- .perform(click())
- }
-
- private fun reviewDeckWithName(deckName: String) {
- clickOnDeckWithName(deckName)
- // Adding cards directly to the database while in the Deck Picker screen
- // will not update the page with correct card counts. Hence, clicking
- // on the deck will bring us to the study options page where we need to
- // click on the Study button. If we have added cards to the database
- // before the Deck Picker screen has fully loaded, then we skip clicking
- // the Study button
- clickOnStudyButtonIfExists()
- }
-
- private fun clickShowAnswerAndAnswerGood() {
- clickShowAnswer()
- ensureAnswerButtonsAreDisplayed()
- try {
- // ...on the command line it has resource name "good_button"...
- onView(withResourceName("good_button")).perform(click())
- } catch (e: NoMatchingViewException) {
- // ...but in Android Studio it has resource name "flashcard_layout_ease3" !?
- onView(withResourceName("flashcard_layout_ease3")).perform(click())
- }
- }
-
- private fun clickShowAnswer() {
- try {
- // ... on the command line, it has resource name "show_answer"...
- onView(withResourceName("show_answer")).perform(click())
- } catch (e: NoMatchingViewException) {
- // ... but in Android Studio it has resource name "flashcard_layout_flip" !?
- onView(withResourceName("flashcard_layout_flip")).perform(click())
- }
- }
-
- private fun ensureAnswerButtonsAreDisplayed() {
- // We need to wait for the card to fully load to allow enough time for
- // the messages to be passed in and out of the WebView when evaluating
- // the custom JS scheduler code. The ease buttons are hidden until the
- // custom scheduler has finished running
- try {
- // ...on the command line it has resource name "good_button"...
- onView(withResourceName("good_button")).checkWithTimeout(
- matches(isDisplayed()),
- 100
- )
- } catch (e: AssertionError) {
- // ...but in Android Studio it has resource name "flashcard_layout_ease3" !?
- onView(withResourceName("flashcard_layout_ease3")).checkWithTimeout(
- matches(isDisplayed()),
- 100
- )
- }
- }
-}
-
-private var Collection.cardStateCustomizer: String?
- get() = config.get("cardStateCustomizer")
- set(value) { config.set("cardStateCustomizer", value) }
-
-fun closeGetStartedScreenIfExists() {
- onView(withId(R.id.get_started)).withFailureHandler { _, _ -> }.perform(click())
-}
-
-fun closeBackupCollectionDialogIfExists() {
- onView(withText(R.string.button_backup_later))
- .withFailureHandler { _, _ -> }
- .perform(click())
-}
+/*
+ * Copyright (c) 2023
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+package com.ichi2.anki
+
+import androidx.recyclerview.widget.RecyclerView
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.NoMatchingViewException
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.contrib.RecyclerViewActions
+import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withResourceName
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.ichi2.anki.tests.InstrumentedTest
+import com.ichi2.anki.tests.libanki.RetryRule
+import com.ichi2.anki.testutil.GrantStoragePermission.storagePermission
+import com.ichi2.anki.testutil.ThreadUtils
+import com.ichi2.anki.testutil.grantPermissions
+import com.ichi2.anki.testutil.notificationPermission
+import com.ichi2.libanki.Collection
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.equalTo
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.lang.AssertionError
+
+@RunWith(AndroidJUnit4::class)
+class ReviewerTest : InstrumentedTest() {
+
+ // Launch IntroductionActivity instead of DeckPicker activity because in CI
+ // builds, it seems to create IntroductionActivity after the DeckPicker,
+ // causing the DeckPicker activity to be destroyed. As a consequence, this
+ // will throw RootViewWithoutFocusException when Espresso tries to interact
+ // with an already destroyed activity. By launching IntroductionActivity, we
+ // ensure that IntroductionActivity is launched first and navigate to the
+ // DeckPicker -> Reviewer activities
+ @get:Rule
+ val activityScenarioRule = ActivityScenarioRule(IntroductionActivity::class.java)
+
+ @get:Rule
+ val runtimePermissionRule = grantPermissions(storagePermission, notificationPermission)
+
+ @get:Rule
+ val retry = RetryRule(10)
+
+ @Test
+ fun testCustomSchedulerWithCustomData() {
+ col.cardStateCustomizer =
+ """
+ states.good.normal.review.easeFactor = 3.0;
+ states.good.normal.review.scheduledDays = 123;
+ customData.good.c += 1;
+ """
+ val note = addNoteUsingBasicModel("foo", "bar")
+ val card = note.firstCard(col)
+ val deck = col.decks.get(note.notetype.did)!!
+ card.moveToReviewQueue()
+ col.backend.updateCards(
+ listOf(
+ card.toBackendCard().toBuilder().setCustomData("""{"c":1}""").build()
+ ),
+ true
+ )
+
+ closeGetStartedScreenIfExists()
+ closeBackupCollectionDialogIfExists()
+ reviewDeckWithName(deck.name)
+
+ var cardFromDb = col.getCard(card.id).toBackendCard()
+ assertThat(cardFromDb.easeFactor, equalTo(card.factor))
+ assertThat(cardFromDb.interval, equalTo(card.ivl))
+ assertThat(cardFromDb.customData, equalTo("""{"c":1}"""))
+
+ clickShowAnswerAndAnswerGood()
+
+ fun runAssertion() {
+ cardFromDb = col.getCard(card.id).toBackendCard()
+ assertThat(cardFromDb.easeFactor, equalTo(3000))
+ assertThat(cardFromDb.interval, equalTo(123))
+ assertThat(cardFromDb.customData, equalTo("""{"c":2}"""))
+ }
+
+ try {
+ runAssertion()
+ } catch (e: Exception) {
+ // Give separate threads a greater chance of doing the custom scheduling
+ // if the card scheduling values aren't updated immediately
+ ThreadUtils.sleep(2000)
+ runAssertion()
+ }
+ }
+
+ @Test
+ fun testCustomSchedulerWithRuntimeError() {
+ // Issue 15035 - runtime errors weren't handled
+ col.cardStateCustomizer = "states.this_is_not_defined.normal.review = 12;"
+ addNoteUsingBasicModel()
+
+ closeGetStartedScreenIfExists()
+ closeBackupCollectionDialogIfExists()
+ reviewDeckWithName("Default")
+
+ clickShowAnswer()
+
+ ensureAnswerButtonsAreDisplayed()
+ }
+
+ private fun clickOnDeckWithName(deckName: String) {
+ onView(withId(R.id.files)).checkWithTimeout(matches(hasDescendant(withText(deckName))))
+ onView(withId(R.id.files)).perform(
+ RecyclerViewActions.actionOnItem(
+ hasDescendant(withText(deckName)),
+ click()
+ )
+ )
+ }
+
+ private fun clickOnStudyButtonIfExists() {
+ onView(withId(R.id.studyoptions_start))
+ .withFailureHandler { _, _ -> }
+ .perform(click())
+ }
+
+ private fun reviewDeckWithName(deckName: String) {
+ clickOnDeckWithName(deckName)
+ // Adding cards directly to the database while in the Deck Picker screen
+ // will not update the page with correct card counts. Hence, clicking
+ // on the deck will bring us to the study options page where we need to
+ // click on the Study button. If we have added cards to the database
+ // before the Deck Picker screen has fully loaded, then we skip clicking
+ // the Study button
+ clickOnStudyButtonIfExists()
+ }
+
+ private fun clickShowAnswerAndAnswerGood() {
+ clickShowAnswer()
+ ensureAnswerButtonsAreDisplayed()
+ try {
+ // ...on the command line it has resource name "good_button"...
+ onView(withResourceName("good_button")).perform(click())
+ } catch (e: NoMatchingViewException) {
+ // ...but in Android Studio it has resource name "flashcard_layout_ease3" !?
+ onView(withResourceName("flashcard_layout_ease3")).perform(click())
+ }
+ }
+
+ private fun clickShowAnswer() {
+ try {
+ // ... on the command line, it has resource name "show_answer"...
+ onView(withResourceName("show_answer")).perform(click())
+ } catch (e: NoMatchingViewException) {
+ // ... but in Android Studio it has resource name "flashcard_layout_flip" !?
+ onView(withResourceName("flashcard_layout_flip")).perform(click())
+ }
+ }
+
+ private fun ensureAnswerButtonsAreDisplayed() {
+ // We need to wait for the card to fully load to allow enough time for
+ // the messages to be passed in and out of the WebView when evaluating
+ // the custom JS scheduler code. The ease buttons are hidden until the
+ // custom scheduler has finished running
+ try {
+ // ...on the command line it has resource name "good_button"...
+ onView(withResourceName("good_button")).checkWithTimeout(
+ matches(isDisplayed()),
+ 100
+ )
+ } catch (e: AssertionError) {
+ // ...but in Android Studio it has resource name "flashcard_layout_ease3" !?
+ onView(withResourceName("flashcard_layout_ease3")).checkWithTimeout(
+ matches(isDisplayed()),
+ 100
+ )
+ }
+ }
+}
+
+private var Collection.cardStateCustomizer: String?
+ get() = config.get("cardStateCustomizer")
+ set(value) { config.set("cardStateCustomizer", value) }
+
+fun closeGetStartedScreenIfExists() {
+ onView(withId(R.id.get_started)).withFailureHandler { _, _ -> }.perform(click())
+}
+
+fun closeBackupCollectionDialogIfExists() {
+ onView(withText(R.string.button_backup_later))
+ .withFailureHandler { _, _ -> }
+ .perform(click())
+}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/pages/ImageOcclusion.kt b/AnkiDroid/src/main/java/com/ichi2/anki/pages/ImageOcclusion.kt
index 5a92bfc4e9..a9e7ab27dd 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/pages/ImageOcclusion.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/pages/ImageOcclusion.kt
@@ -1,99 +1,99 @@
-/*
- * Copyright (c) 2023 Abdo
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3 of the License, or (at your option) any later
- * version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
-package com.ichi2.anki.pages
-
-import android.content.Context
-import android.content.Intent
-import android.os.Bundle
-import android.view.View
-import android.webkit.WebView
-import androidx.activity.addCallback
-import androidx.core.os.bundleOf
-import com.google.android.material.appbar.MaterialToolbar
-import com.ichi2.anki.R
-import com.ichi2.anki.SingleFragmentActivity
-import com.ichi2.anki.dialogs.DiscardChangesDialog
-import org.json.JSONObject
-import timber.log.Timber
-
-class ImageOcclusion : PageFragment(R.layout.image_occlusion) {
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- with(requireActivity()) {
- onBackPressedDispatcher.addCallback(this) {
- DiscardChangesDialog.showDialog(this@with) {
- finish()
- }
- }
- }
-
- view.findViewById(R.id.toolbar).setOnMenuItemClickListener {
- if (it.itemId == R.id.action_save) {
- Timber.i("save item selected")
- webView.evaluateJavascript("anki.imageOcclusion.save()", null)
- }
- return@setOnMenuItemClickListener true
- }
- }
-
- override fun onCreateWebViewClient(savedInstanceState: Bundle?): PageWebViewClient {
- return object : PageWebViewClient() {
- override fun onPageFinished(view: WebView?, url: String?) {
- super.onPageFinished(view, url)
-
- val kind = requireArguments().getString(ARG_KEY_KIND)
- val noteOrNotetypeId = requireArguments().getLong(ARG_KEY_ID)
- val imagePath = requireArguments().getString(ARG_KEY_PATH)
-
- val options = JSONObject()
- options.put("kind", kind)
- if (kind == "add") {
- options.put("imagePath", imagePath)
- options.put("notetypeId", noteOrNotetypeId)
- } else {
- options.put("noteId", noteOrNotetypeId)
- }
-
- view?.evaluateJavascript("globalThis.anki.imageOcclusion.mode = $options") {
- super.onPageFinished(view, url)
- }
- }
- }
- }
-
- companion object {
- private const val ARG_KEY_KIND = "kind"
- private const val ARG_KEY_ID = "id"
- private const val ARG_KEY_PATH = "imagePath"
-
- fun getIntent(context: Context, kind: String, noteOrNotetypeId: Long, imagePath: String?): Intent {
- val suffix = if (kind == "edit") {
- "/$noteOrNotetypeId"
- } else {
- imagePath
- }
- val arguments = bundleOf(
- ARG_KEY_KIND to kind,
- ARG_KEY_ID to noteOrNotetypeId,
- ARG_KEY_PATH to imagePath,
- PATH_ARG_KEY to "image-occlusion$suffix"
- )
- return SingleFragmentActivity.getIntent(context, ImageOcclusion::class, arguments)
- }
- }
-}
+/*
+ * Copyright (c) 2023 Abdo
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+package com.ichi2.anki.pages
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import android.webkit.WebView
+import androidx.activity.addCallback
+import androidx.core.os.bundleOf
+import com.google.android.material.appbar.MaterialToolbar
+import com.ichi2.anki.R
+import com.ichi2.anki.SingleFragmentActivity
+import com.ichi2.anki.dialogs.DiscardChangesDialog
+import org.json.JSONObject
+import timber.log.Timber
+
+class ImageOcclusion : PageFragment(R.layout.image_occlusion) {
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ with(requireActivity()) {
+ onBackPressedDispatcher.addCallback(this) {
+ DiscardChangesDialog.showDialog(this@with) {
+ finish()
+ }
+ }
+ }
+
+ view.findViewById(R.id.toolbar).setOnMenuItemClickListener {
+ if (it.itemId == R.id.action_save) {
+ Timber.i("save item selected")
+ webView.evaluateJavascript("anki.imageOcclusion.save()", null)
+ }
+ return@setOnMenuItemClickListener true
+ }
+ }
+
+ override fun onCreateWebViewClient(savedInstanceState: Bundle?): PageWebViewClient {
+ return object : PageWebViewClient() {
+ override fun onPageFinished(view: WebView?, url: String?) {
+ super.onPageFinished(view, url)
+
+ val kind = requireArguments().getString(ARG_KEY_KIND)
+ val noteOrNotetypeId = requireArguments().getLong(ARG_KEY_ID)
+ val imagePath = requireArguments().getString(ARG_KEY_PATH)
+
+ val options = JSONObject()
+ options.put("kind", kind)
+ if (kind == "add") {
+ options.put("imagePath", imagePath)
+ options.put("notetypeId", noteOrNotetypeId)
+ } else {
+ options.put("noteId", noteOrNotetypeId)
+ }
+
+ view?.evaluateJavascript("globalThis.anki.imageOcclusion.mode = $options") {
+ super.onPageFinished(view, url)
+ }
+ }
+ }
+ }
+
+ companion object {
+ private const val ARG_KEY_KIND = "kind"
+ private const val ARG_KEY_ID = "id"
+ private const val ARG_KEY_PATH = "imagePath"
+
+ fun getIntent(context: Context, kind: String, noteOrNotetypeId: Long, imagePath: String?): Intent {
+ val suffix = if (kind == "edit") {
+ "/$noteOrNotetypeId"
+ } else {
+ imagePath
+ }
+ val arguments = bundleOf(
+ ARG_KEY_KIND to kind,
+ ARG_KEY_ID to noteOrNotetypeId,
+ ARG_KEY_PATH to imagePath,
+ PATH_ARG_KEY to "image-occlusion$suffix"
+ )
+ return SingleFragmentActivity.getIntent(context, ImageOcclusion::class, arguments)
+ }
+ }
+}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AccessibilitySettingsFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AccessibilitySettingsFragment.kt
index 2b12c1f85b..3be3ed3f59 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AccessibilitySettingsFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AccessibilitySettingsFragment.kt
@@ -1,31 +1,31 @@
-/*
- * Copyright (c) 2022 Brayan Oliveira
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3 of the License, or (at your option) any later
- * version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
-package com.ichi2.anki.preferences
-
-import com.ichi2.anki.R
-
-/**
- * Fragment with preferences related to notifications
- */
-class AccessibilitySettingsFragment : SettingsFragment() {
- override val preferenceResource: Int
- get() = R.xml.preferences_accessibility
- override val analyticsScreenNameConstant: String
- get() = "prefs.accessibility"
-
- override fun initSubscreen() {
- }
-}
+/*
+ * Copyright (c) 2022 Brayan Oliveira
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+package com.ichi2.anki.preferences
+
+import com.ichi2.anki.R
+
+/**
+ * Fragment with preferences related to notifications
+ */
+class AccessibilitySettingsFragment : SettingsFragment() {
+ override val preferenceResource: Int
+ get() = R.xml.preferences_accessibility
+ override val analyticsScreenNameConstant: String
+ get() = "prefs.accessibility"
+
+ override fun initSubscreen() {
+ }
+}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/ConstantUniquenessTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/ConstantUniquenessTest.kt
index b37cded87d..357ae23cfa 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/ConstantUniquenessTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/ConstantUniquenessTest.kt
@@ -1,55 +1,55 @@
-/*
- * Copyright (c) 2024 WPum <27683756+WPum@users.noreply.github.com>
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3 of the License, or (at your option) any later
- * version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
-
-package com.ichi2.anki
-
-import com.ichi2.anki.notifications.NotificationId
-import com.ichi2.anki.worker.UniqueWorkNames
-import org.junit.Test
-import kotlin.reflect.KClass
-import kotlin.reflect.KVisibility
-import kotlin.reflect.full.declaredMemberProperties
-import kotlin.test.assertFalse
-import kotlin.test.assertNotNull
-
-class ConstantUniquenessTest {
-
- @Test
- fun testConstantUniqueness() {
- assertConstantUniqueness(NotificationId::class)
- assertConstantUniqueness(UniqueWorkNames::class)
- }
-
- companion object {
-
- /**
- * To check whether all PUBLIC CONST values in an object are unique.
- */
- fun assertConstantUniqueness(clazz: KClass) {
- assertNotNull(clazz.objectInstance, "Can only check objects for uniqueness")
- val valueSet = HashSet()
- for (prop in clazz.declaredMemberProperties) {
- if (!prop.isConst || prop.visibility != KVisibility.PUBLIC) {
- continue
- }
- // use .call() since clazz represents an object
- val value = prop.call()
- assertFalse(valueSet.contains(value), "Duplicate value ('$value') for constant in ${clazz.qualifiedName}")
- valueSet.add(value)
- }
- }
- }
-}
+/*
+ * Copyright (c) 2024 WPum <27683756+WPum@users.noreply.github.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+package com.ichi2.anki
+
+import com.ichi2.anki.notifications.NotificationId
+import com.ichi2.anki.worker.UniqueWorkNames
+import org.junit.Test
+import kotlin.reflect.KClass
+import kotlin.reflect.KVisibility
+import kotlin.reflect.full.declaredMemberProperties
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+
+class ConstantUniquenessTest {
+
+ @Test
+ fun testConstantUniqueness() {
+ assertConstantUniqueness(NotificationId::class)
+ assertConstantUniqueness(UniqueWorkNames::class)
+ }
+
+ companion object {
+
+ /**
+ * To check whether all PUBLIC CONST values in an object are unique.
+ */
+ fun assertConstantUniqueness(clazz: KClass) {
+ assertNotNull(clazz.objectInstance, "Can only check objects for uniqueness")
+ val valueSet = HashSet()
+ for (prop in clazz.declaredMemberProperties) {
+ if (!prop.isConst || prop.visibility != KVisibility.PUBLIC) {
+ continue
+ }
+ // use .call() since clazz represents an object
+ val value = prop.call()
+ assertFalse(valueSet.contains(value), "Duplicate value ('$value') for constant in ${clazz.qualifiedName}")
+ valueSet.add(value)
+ }
+ }
+ }
+}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/previewer/PreviewerViewModelTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/previewer/PreviewerViewModelTest.kt
index 6275c77bbd..f13ed608c2 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/previewer/PreviewerViewModelTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/previewer/PreviewerViewModelTest.kt
@@ -1,30 +1,30 @@
-/*
- * Copyright (c) 2024 Brayan Oliveira
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3 of the License, or (at your option) any later
- * version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
-package com.ichi2.anki.previewer
-
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.equalTo
-import org.junit.jupiter.api.Test
-
-class PreviewerViewModelTest {
- @Test
- fun `type answer fields are removed in questions`() {
- assertThat(
- PreviewerViewModel.typeAnsQuestionFilter("creu [[type:leu]]"),
- equalTo("creu ")
- )
- }
-}
+/*
+ * Copyright (c) 2024 Brayan Oliveira
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+package com.ichi2.anki.previewer
+
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.equalTo
+import org.junit.jupiter.api.Test
+
+class PreviewerViewModelTest {
+ @Test
+ fun `type answer fields are removed in questions`() {
+ assertThat(
+ PreviewerViewModel.typeAnsQuestionFilter("creu [[type:leu]]"),
+ equalTo("creu ")
+ )
+ }
+}
diff --git a/jitpack.yml b/jitpack.yml
index 384a44ef25..83314e1e15 100644
--- a/jitpack.yml
+++ b/jitpack.yml
@@ -1,15 +1,15 @@
-# We want control over the SDK used to build since we need newer JDKs
-# Use the latest LTS supported by temurin with version format xx.yy.zz-tem (ignore sub-version)
-# You may find versions e.g. for JDK21 like so https://adoptium.net/temurin/archive/?version=21
-# You may verify that a JDK is installed by looking at the "sdk list java" output from, for example:
-# https://jitpack.io/com/github/ankidroid/Anki-Android/v2.18alpha7/build.log (just use a recent tag,
-# if it has not built yet you will have to wait for it to build then on refresh the build log should exist)
-before_install:
- - sdk update
- - sdk list java
- - sdk install java 21.0.2-tem
- - sdk use java 21.0.2-tem
-
-# We can do the absolute minimum to build the API module, no need to build AnkiDroid module
-install:
- - ./gradlew :api:publishToMavenLocal
+# We want control over the SDK used to build since we need newer JDKs
+# Use the latest LTS supported by temurin with version format xx.yy.zz-tem (ignore sub-version)
+# You may find versions e.g. for JDK21 like so https://adoptium.net/temurin/archive/?version=21
+# You may verify that a JDK is installed by looking at the "sdk list java" output from, for example:
+# https://jitpack.io/com/github/ankidroid/Anki-Android/v2.18alpha7/build.log (just use a recent tag,
+# if it has not built yet you will have to wait for it to build then on refresh the build log should exist)
+before_install:
+ - sdk update
+ - sdk list java
+ - sdk install java 21.0.2-tem
+ - sdk use java 21.0.2-tem
+
+# We can do the absolute minimum to build the API module, no need to build AnkiDroid module
+install:
+ - ./gradlew :api:publishToMavenLocal