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