mirror of
https://github.com/ankidroid/Anki-Android.git
synced 2024-09-19 19:42:17 +02:00
Dependency Updates 20220219 (#13305)
* Bump org.jlleitschuh.gradle:ktlint-gradle from 11.1.0 to 11.2.0 (#13265) * Bump org.jlleitschuh.gradle:ktlint-gradle from 11.1.0 to 11.2.0 Bumps org.jlleitschuh.gradle:ktlint-gradle from 11.1.0 to 11.2.0. --- updated-dependencies: - dependency-name: org.jlleitschuh.gradle:ktlint-gradle dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * build(deps): move ktlint rules to .editorconfig With new syntax ---- An individual property can be enabled or disabled with a rule property. The name of the rule property consists of the ktlint_ prefix followed by the rule set id followed by a _ and the rule id. > https://pinterest.github.io/ktlint/faq/#why-is-editorconfig-property-disabled_rules-deprecated-and-how-do-i-resolve-this ---- disabled_rules is deprecated: https://pinterest.github.io/ktlint/faq/#why-is-editorconfig-property-disabled_rules-deprecated-and-how-do-i-resolve-this ktlint-gradle v11.2.0 resolves a few issues around this > Fixed disabled_rules set only in editorconfig in ktlint 0.46+ > Fixed disabled_rules warning when using new editorconfig syntax in ktlint 0.48+ https://github.com/JLLeitschuh/ktlint-gradle/releases/tag/v11.2.0 * lint: fix new ktlint errors Caused when we remove the version specifier in ktlint-gradle * Argument should be on a separate line (unless all arguments can fit a single line) * File annotations should be separated from file contents with a blank line * Declarations and declarations with comments should have an empty space between. * Multiple annotations should not be placed on the same line as the annotated construct Removed unused `KotlinCleanup` class rather than fixing lint errors * build(deps): depend on ktlint-gradle's ktlint Some lint indentation was reduced due to this. Fixed it manually as I didn't like the change --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: David Allison <62114487+david-allison@users.noreply.github.com> * autoformat squash --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: David Allison <62114487+david-allison@users.noreply.github.com>
This commit is contained in:
parent
741ed5e034
commit
a06e727f60
3
.editorconfig
Normal file
3
.editorconfig
Normal file
@ -0,0 +1,3 @@
|
||||
[*.{kt,kts}]
|
||||
ktlint_disabled_rules = no-wildcard-imports
|
||||
# ktlint_standard_no-wildcard-imports = disabled
|
@ -63,7 +63,6 @@ class ACRATest : InstrumentedTest() {
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testDebugConfiguration() {
|
||||
|
||||
// Debug mode overrides all saved state so no setup needed
|
||||
CrashReportService.setDebugACRAConfig(sharedPrefs)
|
||||
assertArrayEquals(
|
||||
@ -91,7 +90,6 @@ class ACRATest : InstrumentedTest() {
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testProductionConfigurationUserDisabled() {
|
||||
|
||||
// set up as if the user had prefs saved to disable completely
|
||||
setReportConfig(CrashReportService.FEEDBACK_REPORT_NEVER)
|
||||
|
||||
|
@ -71,6 +71,7 @@ class ContentProviderTest : InstrumentedTest() {
|
||||
|
||||
// Whether tear down should be executed. I.e. if set up was not cancelled.
|
||||
private var mTearDown = false
|
||||
|
||||
@KotlinCleanup("lateinit")
|
||||
private var mNumDecksBeforeTest = 0
|
||||
|
||||
@ -290,7 +291,8 @@ class ContentProviderTest : InstrumentedTest() {
|
||||
col = reopenCol() // test that the changes are physically saved to the DB
|
||||
assertNotNull("Check template uri", templateUri)
|
||||
assertEquals(
|
||||
"Check template uri ord", expectedOrd.toLong(),
|
||||
"Check template uri ord",
|
||||
expectedOrd.toLong(),
|
||||
ContentUris.parseId(
|
||||
templateUri!!
|
||||
)
|
||||
@ -606,7 +608,8 @@ class ContentProviderTest : InstrumentedTest() {
|
||||
i.toString()
|
||||
)
|
||||
assertThat(
|
||||
"Update rows", cr.update(tmplUri, cv, null, null),
|
||||
"Update rows",
|
||||
cr.update(tmplUri, cv, null, null),
|
||||
`is`(
|
||||
greaterThan(0)
|
||||
)
|
||||
@ -649,7 +652,8 @@ class ContentProviderTest : InstrumentedTest() {
|
||||
assertNotNull(allModels)
|
||||
allModels.use {
|
||||
assertThat(
|
||||
"Check that there is at least one result", allModels.count,
|
||||
"Check that there is at least one result",
|
||||
allModels.count,
|
||||
`is`(greaterThan(0))
|
||||
)
|
||||
while (allModels.moveToNext()) {
|
||||
@ -689,7 +693,8 @@ class ContentProviderTest : InstrumentedTest() {
|
||||
val numCards =
|
||||
allModels.getInt(allModels.getColumnIndex(FlashCardsContract.Model.NUM_CARDS))
|
||||
assertThat(
|
||||
"Check that valid number of cards", numCards,
|
||||
"Check that valid number of cards",
|
||||
numCards,
|
||||
`is`(
|
||||
greaterThanOrEqualTo(1)
|
||||
)
|
||||
@ -958,7 +963,11 @@ class ContentProviderTest : InstrumentedTest() {
|
||||
val col = col
|
||||
val sched = col.sched
|
||||
val reviewInfoCursor = contentResolver.query(
|
||||
FlashCardsContract.ReviewInfo.CONTENT_URI, null, null, null, null
|
||||
FlashCardsContract.ReviewInfo.CONTENT_URI,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
assertNotNull(reviewInfoCursor)
|
||||
assertEquals("Check that we actually received one card", 1, reviewInfoCursor.count)
|
||||
@ -1002,7 +1011,11 @@ class ContentProviderTest : InstrumentedTest() {
|
||||
val selectedDeckBeforeTest = col.decks.selected()
|
||||
col.decks.select(1) // select Default deck
|
||||
val reviewInfoCursor = contentResolver.query(
|
||||
FlashCardsContract.ReviewInfo.CONTENT_URI, null, deckSelector, deckArguments, null
|
||||
FlashCardsContract.ReviewInfo.CONTENT_URI,
|
||||
null,
|
||||
deckSelector,
|
||||
deckArguments,
|
||||
null
|
||||
)
|
||||
assertNotNull(reviewInfoCursor)
|
||||
assertEquals("Check that we actually received one card", 1, reviewInfoCursor.count)
|
||||
@ -1168,7 +1181,6 @@ class ContentProviderTest : InstrumentedTest() {
|
||||
*/
|
||||
@Test
|
||||
fun testSuspendCard() {
|
||||
|
||||
// get the first card due
|
||||
// ----------------------
|
||||
val col = col
|
||||
@ -1190,6 +1202,7 @@ class ContentProviderTest : InstrumentedTest() {
|
||||
val reviewInfoUri = FlashCardsContract.ReviewInfo.CONTENT_URI
|
||||
val noteId = card.note().id
|
||||
val cardOrd = card.ord
|
||||
|
||||
@KotlinCleanup("rename, while valid suspend is a kotlin soft keyword")
|
||||
val values = ContentValues().apply {
|
||||
val suspend = 1
|
||||
@ -1304,6 +1317,7 @@ class ContentProviderTest : InstrumentedTest() {
|
||||
private val TEST_MODEL_AFMT = arrayOf("{{BACK}}", "{{FRONTS}}")
|
||||
private val TEST_NOTE_FIELDS = arrayOf("dis is za Fr0nt", "Te\$t")
|
||||
private const val TEST_MODEL_CSS = "styleeeee"
|
||||
|
||||
@Suppress("SameParameterValue")
|
||||
private fun setupNewNote(
|
||||
col: com.ichi2.libanki.Collection,
|
||||
@ -1318,7 +1332,8 @@ class ContentProviderTest : InstrumentedTest() {
|
||||
}
|
||||
newNote.addTag(tag)
|
||||
assertThat(
|
||||
"At least one card added for note", col.addNote(newNote),
|
||||
"At least one card added for note",
|
||||
col.addNote(newNote),
|
||||
`is`(
|
||||
greaterThanOrEqualTo(1)
|
||||
)
|
||||
|
@ -38,6 +38,7 @@ class LayoutValidationTest : InstrumentedTest() {
|
||||
@JvmField // required for Parameter
|
||||
@Parameterized.Parameter(1)
|
||||
var name: String? = null
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun ensureLayout() {
|
||||
|
@ -44,8 +44,10 @@ class NotificationChannelTest : InstrumentedTest() {
|
||||
var runtimePermissionRule = GrantStoragePermission.instance
|
||||
private var mCurrentAPI = -1
|
||||
private var mTargetAPI = -1
|
||||
|
||||
@KotlinCleanup("lateinit")
|
||||
private var mManager: NotificationManager? = null
|
||||
|
||||
@Before
|
||||
@UiThreadTest
|
||||
fun setUp() {
|
||||
|
@ -44,7 +44,6 @@ class HttpTest {
|
||||
@Suppress("DEPRECATION")
|
||||
@Test
|
||||
fun testLogin() {
|
||||
|
||||
val username = "AnkiDroidInstrumentedTestUser"
|
||||
val password = "AnkiDroidInstrumentedTestInvalidPass"
|
||||
val invalidPayload = Connection.Payload(arrayOf(username, password, HostNum(null)))
|
||||
|
@ -59,6 +59,7 @@ class ImportTest : InstrumentedTest() {
|
||||
// Allowing it to re-run now, 3 times, in case it flakes again.
|
||||
@get:Rule
|
||||
var retry = RetryRule(10)
|
||||
|
||||
@Before
|
||||
@Throws(IOException::class)
|
||||
fun setUp() {
|
||||
@ -75,7 +76,6 @@ class ImportTest : InstrumentedTest() {
|
||||
@Test
|
||||
@Throws(IOException::class, JSONException::class, ImportExportException::class)
|
||||
fun testAnki2Mediadupes() {
|
||||
|
||||
// add a note that references a sound
|
||||
var n = testCol.newNote()
|
||||
n.setField(0, "[sound:foo.mp3]")
|
||||
|
@ -26,10 +26,11 @@ object GrantStoragePermission {
|
||||
val storagePermission = if (
|
||||
targetSdkVersion >= Build.VERSION_CODES.R &&
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
)
|
||||
) {
|
||||
null
|
||||
else
|
||||
} else {
|
||||
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
}
|
||||
|
||||
/**
|
||||
* Storage is longer necessary for API 30+
|
||||
@ -38,8 +39,11 @@ object GrantStoragePermission {
|
||||
val instance: TestRule = grantPermissions(storagePermission)
|
||||
}
|
||||
|
||||
val notificationPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
android.Manifest.permission.POST_NOTIFICATIONS else null
|
||||
val notificationPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
android.Manifest.permission.POST_NOTIFICATIONS
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
/** Grants permissions, given some may be invalid */
|
||||
fun grantPermissions(vararg permissions: String?): TestRule {
|
||||
|
@ -30,32 +30,50 @@ object ViewAnimation {
|
||||
when (type) {
|
||||
SLIDE_IN_FROM_RIGHT -> {
|
||||
animation = translateAnimation(
|
||||
+1.0f, 0.0f, 0.0f, 0.0f
|
||||
+1.0f,
|
||||
0.0f,
|
||||
0.0f,
|
||||
0.0f
|
||||
)
|
||||
}
|
||||
SLIDE_OUT_TO_RIGHT -> {
|
||||
animation = translateAnimation(
|
||||
0.0f, +1.0f, 0.0f, 0.0f
|
||||
0.0f,
|
||||
+1.0f,
|
||||
0.0f,
|
||||
0.0f
|
||||
)
|
||||
}
|
||||
SLIDE_IN_FROM_LEFT -> {
|
||||
animation = translateAnimation(
|
||||
-1.0f, 0.0f, 0.0f, 0.0f
|
||||
-1.0f,
|
||||
0.0f,
|
||||
0.0f,
|
||||
0.0f
|
||||
)
|
||||
}
|
||||
SLIDE_OUT_TO_LEFT -> {
|
||||
animation = translateAnimation(
|
||||
0.0f, -1.0f, 0.0f, 0.0f
|
||||
0.0f,
|
||||
-1.0f,
|
||||
0.0f,
|
||||
0.0f
|
||||
)
|
||||
}
|
||||
SLIDE_IN_FROM_BOTTOM -> {
|
||||
animation = translateAnimation(
|
||||
0.0f, 0.0f, +1.0f, 0.0f
|
||||
0.0f,
|
||||
0.0f,
|
||||
+1.0f,
|
||||
0.0f
|
||||
)
|
||||
}
|
||||
SLIDE_IN_FROM_TOP -> {
|
||||
animation = translateAnimation(
|
||||
0.0f, 0.0f, -1.0f, 0.0f
|
||||
0.0f,
|
||||
0.0f,
|
||||
-1.0f,
|
||||
0.0f
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -177,12 +177,16 @@ abstract class AbstractFlashcardViewer :
|
||||
protected var answerField: FixedEditText? = null
|
||||
protected var flipCardLayout: LinearLayout? = null
|
||||
private var easeButtonsLayout: LinearLayout? = null
|
||||
|
||||
@KotlinCleanup("internal for AnkiDroidJsApi")
|
||||
internal var easeButton1: EaseButton? = null
|
||||
|
||||
@KotlinCleanup("internal for AnkiDroidJsApi")
|
||||
internal var easeButton2: EaseButton? = null
|
||||
|
||||
@KotlinCleanup("internal for AnkiDroidJsApi")
|
||||
internal var easeButton3: EaseButton? = null
|
||||
|
||||
@KotlinCleanup("internal for AnkiDroidJsApi")
|
||||
internal var easeButton4: EaseButton? = null
|
||||
protected var topBarLayout: RelativeLayout? = null
|
||||
@ -223,6 +227,7 @@ abstract class AbstractFlashcardViewer :
|
||||
private var mViewerUrl: String? = null
|
||||
private var mAssetLoader: WebViewAssetLoader? = null
|
||||
private val mFadeDuration = 300
|
||||
|
||||
@KotlinCleanup("made internal for tests")
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
|
||||
internal var sched: AbstractSched? = null
|
||||
@ -651,7 +656,9 @@ abstract class AbstractFlashcardViewer :
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||
return if (processCardFunction { cardWebView: WebView? -> processHardwareButtonScroll(keyCode, cardWebView) }) {
|
||||
true
|
||||
} else super.onKeyDown(keyCode, event)
|
||||
} else {
|
||||
super.onKeyDown(keyCode, event)
|
||||
}
|
||||
}
|
||||
|
||||
public override val currentCardId: CardId? get() = currentCard?.id
|
||||
@ -824,7 +831,6 @@ abstract class AbstractFlashcardViewer :
|
||||
legacyUndo()
|
||||
} else {
|
||||
return launchCatchingTask {
|
||||
|
||||
if (!backendUndoAndShowPopup(findViewById(R.id.flip_card))) {
|
||||
legacyUndo()
|
||||
}
|
||||
@ -1246,7 +1252,6 @@ abstract class AbstractFlashcardViewer :
|
||||
}
|
||||
|
||||
protected open fun restoreCollectionPreferences(col: Collection) {
|
||||
|
||||
// These are preferences we pull out of the collection instead of SharedPreferences
|
||||
try {
|
||||
mShowNextReviewTime = col.get_config_boolean("estTimes")
|
||||
@ -1652,104 +1657,106 @@ abstract class AbstractFlashcardViewer :
|
||||
override fun executeCommand(which: ViewerCommand, fromGesture: Gesture?): Boolean {
|
||||
return if (isControlBlocked && which !== ViewerCommand.EXIT) {
|
||||
false
|
||||
} else when (which) {
|
||||
ViewerCommand.SHOW_ANSWER -> {
|
||||
if (displayAnswer) {
|
||||
return false
|
||||
} else {
|
||||
when (which) {
|
||||
ViewerCommand.SHOW_ANSWER -> {
|
||||
if (displayAnswer) {
|
||||
return false
|
||||
}
|
||||
displayCardAnswer()
|
||||
true
|
||||
}
|
||||
displayCardAnswer()
|
||||
true
|
||||
}
|
||||
ViewerCommand.FLIP_OR_ANSWER_EASE1 -> {
|
||||
flipOrAnswerCard(EASE_1)
|
||||
true
|
||||
}
|
||||
ViewerCommand.FLIP_OR_ANSWER_EASE2 -> {
|
||||
flipOrAnswerCard(EASE_2)
|
||||
true
|
||||
}
|
||||
ViewerCommand.FLIP_OR_ANSWER_EASE3 -> {
|
||||
flipOrAnswerCard(EASE_3)
|
||||
true
|
||||
}
|
||||
ViewerCommand.FLIP_OR_ANSWER_EASE4 -> {
|
||||
flipOrAnswerCard(EASE_4)
|
||||
true
|
||||
}
|
||||
ViewerCommand.FLIP_OR_ANSWER_RECOMMENDED -> {
|
||||
flipOrAnswerCard(getRecommendedEase(false))
|
||||
true
|
||||
}
|
||||
ViewerCommand.FLIP_OR_ANSWER_BETTER_THAN_RECOMMENDED -> {
|
||||
flipOrAnswerCard(getRecommendedEase(true))
|
||||
true
|
||||
}
|
||||
ViewerCommand.EXIT -> {
|
||||
closeReviewer(RESULT_DEFAULT, false)
|
||||
true
|
||||
}
|
||||
ViewerCommand.UNDO -> {
|
||||
if (!isUndoAvailable) {
|
||||
return false
|
||||
ViewerCommand.FLIP_OR_ANSWER_EASE1 -> {
|
||||
flipOrAnswerCard(EASE_1)
|
||||
true
|
||||
}
|
||||
ViewerCommand.FLIP_OR_ANSWER_EASE2 -> {
|
||||
flipOrAnswerCard(EASE_2)
|
||||
true
|
||||
}
|
||||
ViewerCommand.FLIP_OR_ANSWER_EASE3 -> {
|
||||
flipOrAnswerCard(EASE_3)
|
||||
true
|
||||
}
|
||||
ViewerCommand.FLIP_OR_ANSWER_EASE4 -> {
|
||||
flipOrAnswerCard(EASE_4)
|
||||
true
|
||||
}
|
||||
ViewerCommand.FLIP_OR_ANSWER_RECOMMENDED -> {
|
||||
flipOrAnswerCard(getRecommendedEase(false))
|
||||
true
|
||||
}
|
||||
ViewerCommand.FLIP_OR_ANSWER_BETTER_THAN_RECOMMENDED -> {
|
||||
flipOrAnswerCard(getRecommendedEase(true))
|
||||
true
|
||||
}
|
||||
ViewerCommand.EXIT -> {
|
||||
closeReviewer(RESULT_DEFAULT, false)
|
||||
true
|
||||
}
|
||||
ViewerCommand.UNDO -> {
|
||||
if (!isUndoAvailable) {
|
||||
return false
|
||||
}
|
||||
undo()
|
||||
true
|
||||
}
|
||||
ViewerCommand.EDIT -> {
|
||||
editCard(fromGesture)
|
||||
true
|
||||
}
|
||||
ViewerCommand.TAG -> {
|
||||
showTagsDialog()
|
||||
true
|
||||
}
|
||||
ViewerCommand.BURY_CARD -> buryCard()
|
||||
ViewerCommand.BURY_NOTE -> buryNote()
|
||||
ViewerCommand.SUSPEND_CARD -> suspendCard()
|
||||
ViewerCommand.SUSPEND_NOTE -> suspendNote()
|
||||
ViewerCommand.DELETE -> {
|
||||
showDeleteNoteDialog()
|
||||
true
|
||||
}
|
||||
ViewerCommand.PLAY_MEDIA -> {
|
||||
playSounds(true)
|
||||
true
|
||||
}
|
||||
ViewerCommand.PAGE_UP -> {
|
||||
onPageUp()
|
||||
true
|
||||
}
|
||||
ViewerCommand.PAGE_DOWN -> {
|
||||
onPageDown()
|
||||
true
|
||||
}
|
||||
ViewerCommand.ABORT_AND_SYNC -> {
|
||||
abortAndSync()
|
||||
true
|
||||
}
|
||||
ViewerCommand.RECORD_VOICE -> {
|
||||
recordVoice()
|
||||
true
|
||||
}
|
||||
ViewerCommand.REPLAY_VOICE -> {
|
||||
replayVoice()
|
||||
true
|
||||
}
|
||||
ViewerCommand.TOGGLE_WHITEBOARD -> {
|
||||
toggleWhiteboard()
|
||||
true
|
||||
}
|
||||
ViewerCommand.SHOW_HINT -> {
|
||||
loadUrlInViewer("javascript: showHint();")
|
||||
true
|
||||
}
|
||||
ViewerCommand.SHOW_ALL_HINTS -> {
|
||||
loadUrlInViewer("javascript: showAllHints();")
|
||||
true
|
||||
}
|
||||
else -> {
|
||||
Timber.w("Unknown command requested: %s", which)
|
||||
false
|
||||
}
|
||||
undo()
|
||||
true
|
||||
}
|
||||
ViewerCommand.EDIT -> {
|
||||
editCard(fromGesture)
|
||||
true
|
||||
}
|
||||
ViewerCommand.TAG -> {
|
||||
showTagsDialog()
|
||||
true
|
||||
}
|
||||
ViewerCommand.BURY_CARD -> buryCard()
|
||||
ViewerCommand.BURY_NOTE -> buryNote()
|
||||
ViewerCommand.SUSPEND_CARD -> suspendCard()
|
||||
ViewerCommand.SUSPEND_NOTE -> suspendNote()
|
||||
ViewerCommand.DELETE -> {
|
||||
showDeleteNoteDialog()
|
||||
true
|
||||
}
|
||||
ViewerCommand.PLAY_MEDIA -> {
|
||||
playSounds(true)
|
||||
true
|
||||
}
|
||||
ViewerCommand.PAGE_UP -> {
|
||||
onPageUp()
|
||||
true
|
||||
}
|
||||
ViewerCommand.PAGE_DOWN -> {
|
||||
onPageDown()
|
||||
true
|
||||
}
|
||||
ViewerCommand.ABORT_AND_SYNC -> {
|
||||
abortAndSync()
|
||||
true
|
||||
}
|
||||
ViewerCommand.RECORD_VOICE -> {
|
||||
recordVoice()
|
||||
true
|
||||
}
|
||||
ViewerCommand.REPLAY_VOICE -> {
|
||||
replayVoice()
|
||||
true
|
||||
}
|
||||
ViewerCommand.TOGGLE_WHITEBOARD -> {
|
||||
toggleWhiteboard()
|
||||
true
|
||||
}
|
||||
ViewerCommand.SHOW_HINT -> {
|
||||
loadUrlInViewer("javascript: showHint();")
|
||||
true
|
||||
}
|
||||
ViewerCommand.SHOW_ALL_HINTS -> {
|
||||
loadUrlInViewer("javascript: showAllHints();")
|
||||
true
|
||||
}
|
||||
else -> {
|
||||
Timber.w("Unknown command requested: %s", which)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2602,6 +2609,7 @@ abstract class AbstractFlashcardViewer :
|
||||
* Should be protected, using non-JVM static members protected in the superclass companion is unsupported yet
|
||||
*/
|
||||
const val INITIAL_HIDE_DELAY = 200
|
||||
|
||||
// I don't see why we don't do this by intent.
|
||||
/** to be sent to and from the card editor */
|
||||
@set:VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
|
@ -64,6 +64,7 @@ open class AnkiDroidApp : Application() {
|
||||
/** An exception if the WebView subsystem fails to load */
|
||||
private var mWebViewError: Throwable? = null
|
||||
private val mNotifications = MutableLiveData<Void?>()
|
||||
|
||||
@KotlinCleanup("can move analytics here now")
|
||||
override fun attachBaseContext(base: Context) {
|
||||
// update base context with preferred app language before attach
|
||||
@ -260,7 +261,9 @@ open class AnkiDroidApp : Application() {
|
||||
// throw new IllegalStateException(
|
||||
// "Synthetic stacktrace didn't have enough elements: are you using proguard?");
|
||||
// --- end of alteration from upstream Timber.DebugTree.getTag ---
|
||||
} else createStackElementTag(stackTrace[CALL_STACK_INDEX])
|
||||
} else {
|
||||
createStackElementTag(stackTrace[CALL_STACK_INDEX])
|
||||
}
|
||||
}
|
||||
|
||||
// ---- END copied from Timber.DebugTree because DebugTree.getTag() is package private ----
|
||||
|
@ -39,7 +39,7 @@ fun DeckPicker.importColpkg(colpkgPath: String) {
|
||||
if (progress.hasImporting()) {
|
||||
text = progress.importing
|
||||
}
|
||||
},
|
||||
}
|
||||
) {
|
||||
CollectionManager.importColpkg(colpkgPath)
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ fun DeckPicker.importApkgs(apkgPaths: List<String>) {
|
||||
if (progress.hasImporting()) {
|
||||
text = progress.importing
|
||||
}
|
||||
},
|
||||
}
|
||||
) {
|
||||
undoableOp {
|
||||
importAnkiPackage(apkgPath)
|
||||
@ -105,7 +105,7 @@ suspend fun AnkiActivity.exportApkg(
|
||||
if (progress.hasExporting()) {
|
||||
text = progress.exporting
|
||||
}
|
||||
},
|
||||
}
|
||||
) {
|
||||
withCol {
|
||||
newBackend.exportAnkiPackage(apkgPath, withScheduling, withMedia, limit)
|
||||
@ -115,14 +115,14 @@ suspend fun AnkiActivity.exportApkg(
|
||||
|
||||
suspend fun AnkiActivity.exportColpkg(
|
||||
colpkgPath: String,
|
||||
withMedia: Boolean,
|
||||
withMedia: Boolean
|
||||
) {
|
||||
withProgress(
|
||||
extractProgress = {
|
||||
if (progress.hasExporting()) {
|
||||
text = progress.exporting
|
||||
}
|
||||
},
|
||||
}
|
||||
) {
|
||||
withCol {
|
||||
newBackend.exportCollectionPackage(colpkgPath, withMedia, true)
|
||||
|
@ -35,7 +35,6 @@ suspend fun FragmentActivity.backendUndoAndShowPopup(anchorView: View? = null):
|
||||
}
|
||||
|
||||
showSnackbar(TR.undoActionUndone(changes.operation)) {
|
||||
|
||||
// A snackbar may obscure vital elements (e.g: the answer buttons on the Reviewer)
|
||||
// `anchorView` stops this
|
||||
anchorView?.let { setAnchorView(anchorView) }
|
||||
|
@ -113,7 +113,9 @@ open class BackupManager {
|
||||
// If have no backups, then a backup is necessary
|
||||
return if (len <= 0) {
|
||||
false
|
||||
} else colBackups[len - 1].lastModified() == colFile.lastModified()
|
||||
} else {
|
||||
colBackups[len - 1].lastModified() == colFile.lastModified()
|
||||
}
|
||||
|
||||
// no collection changes means we don't need a backup
|
||||
}
|
||||
|
@ -239,6 +239,7 @@ open class CardBrowser :
|
||||
@get:VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
var isInMultiSelectMode = false
|
||||
private set
|
||||
|
||||
@get:VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
var isTruncated = false
|
||||
private val mCheckedCards = Collections.synchronizedSet(LinkedHashSet<CardCache>())
|
||||
@ -399,7 +400,8 @@ open class CardBrowser :
|
||||
if (searchName.isNullOrEmpty()) {
|
||||
showThemedToast(
|
||||
this@CardBrowser,
|
||||
getString(R.string.card_browser_list_my_searches_new_search_error_empty_name), true
|
||||
getString(R.string.card_browser_list_my_searches_new_search_error_empty_name),
|
||||
true
|
||||
)
|
||||
return
|
||||
}
|
||||
@ -413,7 +415,8 @@ open class CardBrowser :
|
||||
} else {
|
||||
showThemedToast(
|
||||
this@CardBrowser,
|
||||
getString(R.string.card_browser_list_my_searches_new_search_error_dup), true
|
||||
getString(R.string.card_browser_list_my_searches_new_search_error_dup),
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -443,8 +446,8 @@ open class CardBrowser :
|
||||
* Change Deck
|
||||
* @param did Id of the deck
|
||||
*/
|
||||
@VisibleForTesting
|
||||
// TODO: This function can be simplified a lot
|
||||
@VisibleForTesting
|
||||
fun moveSelectedCardsToDeck(did: DeckId) {
|
||||
val selectedDeck = col.decks.get(did)
|
||||
// TODO: Currently try-catch is at every level which isn't required, simplify that
|
||||
@ -577,7 +580,8 @@ open class CardBrowser :
|
||||
val cardsColumn1Spinner = findViewById<Spinner>(R.id.browser_column1_spinner)
|
||||
val column1Adapter = ArrayAdapter.createFromResource(
|
||||
this,
|
||||
R.array.browser_column1_headings, android.R.layout.simple_spinner_item
|
||||
R.array.browser_column1_headings,
|
||||
android.R.layout.simple_spinner_item
|
||||
)
|
||||
column1Adapter.setDropDownViewResource(R.layout.spinner_custom_layout)
|
||||
cardsColumn1Spinner.adapter = column1Adapter
|
||||
@ -606,7 +610,8 @@ open class CardBrowser :
|
||||
val cardsColumn2Spinner = findViewById<Spinner>(R.id.browser_column2_spinner)
|
||||
val column2Adapter = ArrayAdapter.createFromResource(
|
||||
this,
|
||||
R.array.browser_column2_headings, android.R.layout.simple_spinner_item
|
||||
R.array.browser_column2_headings,
|
||||
android.R.layout.simple_spinner_item
|
||||
)
|
||||
// The custom layout for the adapter is used to prevent the overlapping of various interactive components on the screen
|
||||
column2Adapter.setDropDownViewResource(R.layout.spinner_custom_layout)
|
||||
@ -638,7 +643,8 @@ open class CardBrowser :
|
||||
cardsAdapter = MultiColumnListAdapter(
|
||||
this,
|
||||
R.layout.card_item_browser,
|
||||
columnsContent, intArrayOf(R.id.card_sfld, R.id.card_column2),
|
||||
columnsContent,
|
||||
intArrayOf(R.id.card_sfld, R.id.card_column2),
|
||||
sflRelativeFontSize,
|
||||
sflCustomFont
|
||||
)
|
||||
@ -666,12 +672,7 @@ open class CardBrowser :
|
||||
cardsListView!!.setOnItemLongClickListener { _: AdapterView<*>?, view: View?, position: Int, _: Long ->
|
||||
if (isInMultiSelectMode) {
|
||||
var hasChanged = false
|
||||
for (
|
||||
i in min(mLastSelectedPosition, position)..max(
|
||||
mLastSelectedPosition,
|
||||
position
|
||||
)
|
||||
) {
|
||||
for (i in min(mLastSelectedPosition, position)..max(mLastSelectedPosition, position)) {
|
||||
val card = cardsListView!!.getItemAtPosition(i) as CardCache
|
||||
|
||||
// Add to the set of checked cards
|
||||
@ -697,8 +698,12 @@ open class CardBrowser :
|
||||
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
|
||||
val deckId = col.decks.selected()
|
||||
deckSpinnerSelection = DeckSpinnerSelection(
|
||||
this, col, findViewById(R.id.toolbar_spinner),
|
||||
showAllDecks = true, alwaysShowDefault = false, showFilteredDecks = true
|
||||
this,
|
||||
col,
|
||||
findViewById(R.id.toolbar_spinner),
|
||||
showAllDecks = true,
|
||||
alwaysShowDefault = false,
|
||||
showFilteredDecks = true
|
||||
)
|
||||
inCardsMode = AnkiDroidApp.getSharedPrefs(this).getBoolean("inCardsMode", true)
|
||||
isTruncated = AnkiDroidApp.getSharedPrefs(this).getBoolean("isTruncated", false)
|
||||
@ -738,7 +743,6 @@ open class CardBrowser :
|
||||
}
|
||||
}
|
||||
KeyEvent.KEYCODE_E -> {
|
||||
|
||||
// Ctrl+Shift+E: Export (TODO)
|
||||
if (event.isCtrlPressed) {
|
||||
Timber.i("Ctrl+E: Add Note")
|
||||
@ -1127,8 +1131,10 @@ open class CardBrowser :
|
||||
val searchTerms = mSearchView!!.query.toString()
|
||||
showDialogFragment(
|
||||
newInstance(
|
||||
null, mMySearchesDialogListener,
|
||||
searchTerms, CardBrowserMySearchesDialog.CARD_BROWSER_MY_SEARCHES_TYPE_SAVE
|
||||
null,
|
||||
mMySearchesDialogListener,
|
||||
searchTerms,
|
||||
CardBrowserMySearchesDialog.CARD_BROWSER_MY_SEARCHES_TYPE_SAVE
|
||||
)
|
||||
)
|
||||
return true
|
||||
@ -1142,8 +1148,10 @@ open class CardBrowser :
|
||||
)
|
||||
showDialogFragment(
|
||||
newInstance(
|
||||
savedFilters, mMySearchesDialogListener,
|
||||
"", CardBrowserMySearchesDialog.CARD_BROWSER_MY_SEARCHES_TYPE_LIST
|
||||
savedFilters,
|
||||
mMySearchesDialogListener,
|
||||
"",
|
||||
CardBrowserMySearchesDialog.CARD_BROWSER_MY_SEARCHES_TYPE_LIST
|
||||
)
|
||||
)
|
||||
return true
|
||||
@ -1566,7 +1574,9 @@ open class CardBrowser :
|
||||
mTagsDialogListenerAction = TagsDialogListenerAction.EDIT_TAGS
|
||||
val dialog = mTagsDialogFactory!!.newTagsDialog().withArguments(
|
||||
TagsDialog.DialogType.EDIT_TAGS,
|
||||
checkedTags, uncheckedTags, allTags
|
||||
checkedTags,
|
||||
uncheckedTags,
|
||||
allTags
|
||||
)
|
||||
showDialogFragment(dialog)
|
||||
}
|
||||
@ -1574,7 +1584,9 @@ open class CardBrowser :
|
||||
private fun showFilterByTagsDialog() {
|
||||
mTagsDialogListenerAction = TagsDialogListenerAction.FILTER
|
||||
val dialog = mTagsDialogFactory!!.newTagsDialog().withArguments(
|
||||
TagsDialog.DialogType.FILTER_BY_TAG, ArrayList(0), col.tags.all()
|
||||
TagsDialog.DialogType.FILTER_BY_TAG,
|
||||
ArrayList(0),
|
||||
col.tags.all()
|
||||
)
|
||||
showDialogFragment(dialog)
|
||||
}
|
||||
@ -1708,10 +1720,12 @@ open class CardBrowser :
|
||||
override val subtitleText: String
|
||||
get() {
|
||||
val count = cardCount
|
||||
@androidx.annotation.StringRes val subtitleId = if (inCardsMode)
|
||||
|
||||
@androidx.annotation.StringRes val subtitleId = if (inCardsMode) {
|
||||
R.plurals.card_browser_subtitle
|
||||
else
|
||||
} else {
|
||||
R.plurals.card_browser_subtitle_notes_mode
|
||||
}
|
||||
return resources.getQuantityString(subtitleId, count, count)
|
||||
}
|
||||
|
||||
@ -2035,8 +2049,11 @@ open class CardBrowser :
|
||||
protected suspend fun renderBrowserQAParams(firstVisibleItem: Int, visibleItemCount: Int, cards: CardCollection<CardCache>) {
|
||||
Timber.d("Starting Q&A background rendering")
|
||||
val result = renderBrowserQA(
|
||||
cards, firstVisibleItem, visibleItemCount,
|
||||
mColumn1Index, mColumn2Index
|
||||
cards,
|
||||
firstVisibleItem,
|
||||
visibleItemCount,
|
||||
mColumn1Index,
|
||||
mColumn2Index
|
||||
) {
|
||||
// Note: This is called every time a card is rendered.
|
||||
// It blocks the long-click callback while the task is running, so usage of the task should be minimized
|
||||
@ -2466,7 +2483,9 @@ open class CardBrowser :
|
||||
}
|
||||
return if (javaClass != other.javaClass) {
|
||||
false
|
||||
} else id == (other as CardCache).id
|
||||
} else {
|
||||
id == (other as CardCache).id
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
|
@ -267,7 +267,9 @@ class CardInfo : AnkiActivity() {
|
||||
fun intervalAsTimeSeconds(): Long {
|
||||
return if (ivl < 0) {
|
||||
-ivl
|
||||
} else ivl * Stats.SECONDS_PER_DAY
|
||||
} else {
|
||||
ivl * Stats.SECONDS_PER_DAY
|
||||
}
|
||||
}
|
||||
|
||||
// saves space if we just use seconds rather than a "s" suffix
|
||||
|
@ -187,13 +187,15 @@ class CardTemplateBrowserAppearanceEditor : AnkiActivity() {
|
||||
fun fromIntent(intent: Intent?): Result? {
|
||||
return if (intent == null) {
|
||||
null
|
||||
} else try {
|
||||
val question = intent.getStringExtra(INTENT_QUESTION_FORMAT)
|
||||
val answer = intent.getStringExtra(INTENT_ANSWER_FORMAT)
|
||||
Result(question, answer)
|
||||
} catch (e: Exception) {
|
||||
Timber.w(e, "Could not read result from intent")
|
||||
null
|
||||
} else {
|
||||
try {
|
||||
val question = intent.getStringExtra(INTENT_QUESTION_FORMAT)
|
||||
val answer = intent.getStringExtra(INTENT_ANSWER_FORMAT)
|
||||
Result(question, answer)
|
||||
} catch (e: Exception) {
|
||||
Timber.w(e, "Could not read result from intent")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -205,6 +207,7 @@ class CardTemplateBrowserAppearanceEditor : AnkiActivity() {
|
||||
|
||||
/** Specified the card browser should use the default template formatter */
|
||||
const val VALUE_USE_DEFAULT = ""
|
||||
|
||||
@CheckResult
|
||||
fun getIntentFromTemplate(context: Context, template: JSONObject): Intent {
|
||||
val browserQuestionTemplate = template.getString("bqfmt")
|
||||
|
@ -584,7 +584,8 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener {
|
||||
return false
|
||||
}
|
||||
},
|
||||
viewLifecycleOwner, Lifecycle.State.RESUMED
|
||||
viewLifecycleOwner,
|
||||
Lifecycle.State.RESUMED
|
||||
)
|
||||
}
|
||||
|
||||
@ -688,7 +689,6 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener {
|
||||
val currentDeletes = tempModel.getDeleteDbOrds(position)
|
||||
// TODO - this is a SQL query on GUI thread - should see a DeckTask conversion ideally
|
||||
if (col.models.getCardIdsForModel(tempModel.modelId, currentDeletes) == null) {
|
||||
|
||||
// It is possible but unlikely that a user has an in-memory template addition that would
|
||||
// generate cards making the deletion safe, but we don't handle that. All users who do
|
||||
// not already have cards generated making it safe will see this error message:
|
||||
@ -748,7 +748,8 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener {
|
||||
R.plurals.card_template_editor_confirm_delete,
|
||||
numAffectedCards
|
||||
),
|
||||
numAffectedCards, tmpl.optString("name")
|
||||
numAffectedCards,
|
||||
tmpl.optString("name")
|
||||
)
|
||||
d.setArgs(msg)
|
||||
|
||||
@ -918,8 +919,10 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener {
|
||||
private const val EDITOR_NOTE_ID = "noteId"
|
||||
private const val EDITOR_START_ORD_ID = "ordId"
|
||||
private const val CARD_INDEX = "card_ord"
|
||||
|
||||
@Suppress("unused")
|
||||
private const val REQUEST_PREVIEWER = 0
|
||||
|
||||
@Suppress("unused")
|
||||
private const val REQUEST_CARD_BROWSER_APPEARANCE = 1
|
||||
}
|
||||
|
@ -385,7 +385,9 @@ open class CardTemplatePreviewer : AbstractFlashcardViewer() {
|
||||
override val isEmpty: Boolean
|
||||
get() = if (mNote != null) {
|
||||
false
|
||||
} else super.isEmpty
|
||||
} else {
|
||||
super.isEmpty
|
||||
}
|
||||
|
||||
/** Override the method that fetches the model so we can render unsaved models */
|
||||
override fun model(): Model {
|
||||
|
@ -75,7 +75,8 @@ open class CollectionHelper {
|
||||
Timber.i("Begin openCollection: %s", path)
|
||||
val backend = BackendFactory.getBackend(context)
|
||||
val collection = Storage.collection(
|
||||
context, path,
|
||||
context,
|
||||
path,
|
||||
server = false,
|
||||
log = true,
|
||||
backend = backend
|
||||
@ -223,13 +224,15 @@ open class CollectionHelper {
|
||||
}
|
||||
val required = Formatter.formatShortFileSize(context, mRequiredSpace)
|
||||
val insufficientSpace = context.resources.getString(
|
||||
R.string.integrity_check_insufficient_space, required
|
||||
R.string.integrity_check_insufficient_space,
|
||||
required
|
||||
)
|
||||
|
||||
// Also concat in the extra content showing the current free space.
|
||||
val currentFree = Formatter.formatShortFileSize(context, mFreeSpace)
|
||||
val insufficientSpaceCurrentFree = context.resources.getString(
|
||||
R.string.integrity_check_insufficient_space_extra_content, currentFree
|
||||
R.string.integrity_check_insufficient_space_extra_content,
|
||||
currentFree
|
||||
)
|
||||
return insufficientSpace + insufficientSpaceCurrentFree
|
||||
}
|
||||
@ -539,10 +542,12 @@ open class CollectionHelper {
|
||||
getDefaultAnkiDroidDirectory(context),
|
||||
"androidTest"
|
||||
).absolutePath
|
||||
} else PreferenceExtensions.getOrSetString(
|
||||
preferences,
|
||||
PREF_COLLECTION_PATH
|
||||
) { getDefaultAnkiDroidDirectory(context) }
|
||||
} else {
|
||||
PreferenceExtensions.getOrSetString(
|
||||
preferences,
|
||||
PREF_COLLECTION_PATH
|
||||
) { getDefaultAnkiDroidDirectory(context) }
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetches additional collection data not required for
|
||||
|
@ -296,12 +296,14 @@ object CollectionManager {
|
||||
// out our own code, and standard dalvik/java.lang stack frames
|
||||
val caller = stackTraceElements.filter {
|
||||
val klass = it.className
|
||||
for (
|
||||
text in listOf(
|
||||
"CollectionManager", "dalvik", "java.lang",
|
||||
"CollectionHelper", "AnkiActivity"
|
||||
)
|
||||
) {
|
||||
val toCheck = listOf(
|
||||
"CollectionManager",
|
||||
"dalvik",
|
||||
"java.lang",
|
||||
"CollectionHelper",
|
||||
"AnkiActivity"
|
||||
)
|
||||
for (text in toCheck) {
|
||||
if (text in klass) {
|
||||
return@filter false
|
||||
}
|
||||
|
@ -180,7 +180,7 @@ private fun showError(context: Context, msg: String, exception: Throwable) {
|
||||
suspend fun <T> Backend.withProgress(
|
||||
extractProgress: ProgressContext.() -> Unit,
|
||||
updateUi: ProgressContext.() -> Unit,
|
||||
block: suspend CoroutineScope.() -> T,
|
||||
block: suspend CoroutineScope.() -> T
|
||||
): T {
|
||||
return coroutineScope {
|
||||
val monitor = launch {
|
||||
@ -292,7 +292,7 @@ private suspend fun <T> withProgressDialog(
|
||||
private suspend fun monitorProgress(
|
||||
backend: Backend,
|
||||
extractProgress: ProgressContext.() -> Unit,
|
||||
updateUi: ProgressContext.() -> Unit,
|
||||
updateUi: ProgressContext.() -> Unit
|
||||
) {
|
||||
val state = ProgressContext(Progress.getDefaultInstance())
|
||||
while (true) {
|
||||
@ -313,7 +313,7 @@ data class ProgressContext(
|
||||
var progress: Progress,
|
||||
var text: String = "",
|
||||
/** If set, shows progress bar with a of b complete. */
|
||||
var amount: Pair<Int, Int>? = null,
|
||||
var amount: Pair<Int, Int>? = null
|
||||
)
|
||||
|
||||
@Suppress("Deprecation") // ProgressDialog deprecation
|
||||
|
@ -48,12 +48,22 @@ object CrashReportService {
|
||||
/** Our ACRA configurations, initialized during Application.onCreate() */
|
||||
@JvmStatic
|
||||
private var logcatArgs = arrayOf(
|
||||
"-t", "100", "-v", "time", "ActivityManager:I", "SQLiteLog:W", AnkiDroidApp.TAG + ":D", "*:S"
|
||||
"-t",
|
||||
"100",
|
||||
"-v",
|
||||
"time",
|
||||
"ActivityManager:I",
|
||||
"SQLiteLog:W",
|
||||
AnkiDroidApp.TAG + ":D",
|
||||
"*:S"
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
private var dialogEnabled = true
|
||||
|
||||
@JvmStatic
|
||||
private lateinit var toastText: String
|
||||
|
||||
@JvmStatic
|
||||
lateinit var acraCoreConfigBuilder: CoreConfigurationBuilder
|
||||
private set
|
||||
|
@ -34,7 +34,7 @@ fun DeckPicker.handleDatabaseCheck() {
|
||||
}
|
||||
}
|
||||
},
|
||||
onCancel = null,
|
||||
onCancel = null
|
||||
) {
|
||||
withCol {
|
||||
newBackend.fixIntegrity()
|
||||
|
@ -136,7 +136,9 @@ class DeckOptionsActivity :
|
||||
|
||||
mValues["reminderEnabled"] = reminder.getBoolean("enabled").toString()
|
||||
mValues["reminderTime"] = String.format(
|
||||
"%1$02d:%2$02d", reminderTime.getLong(0), reminderTime.getLong(1)
|
||||
"%1$02d:%2$02d",
|
||||
reminderTime.getLong(0),
|
||||
reminderTime.getLong(1)
|
||||
)
|
||||
} else {
|
||||
mValues["reminderEnabled"] = "false"
|
||||
@ -171,8 +173,10 @@ class DeckOptionsActivity :
|
||||
fun preConfChange() {
|
||||
val res = deckOptionsActivity.resources
|
||||
progressDialog = StyledProgressDialog.show(
|
||||
deckOptionsActivity as Context, null,
|
||||
res?.getString(R.string.reordering_cards), false
|
||||
deckOptionsActivity as Context,
|
||||
null,
|
||||
res?.getString(R.string.reordering_cards),
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
@ -282,7 +286,8 @@ class DeckOptionsActivity :
|
||||
// Don't remove the options group if it's the default group
|
||||
UIUtils.showThemedToast(
|
||||
this@DeckOptionsActivity,
|
||||
resources.getString(R.string.default_conf_delete_error), false
|
||||
resources.getString(R.string.default_conf_delete_error),
|
||||
false
|
||||
)
|
||||
} else {
|
||||
// Remove options group, handling the case where the user needs to confirm full sync
|
||||
@ -346,7 +351,8 @@ class DeckOptionsActivity :
|
||||
applicationContext,
|
||||
mOptions.getLong("id").toInt(),
|
||||
Intent(applicationContext, ReminderService::class.java).putExtra(
|
||||
ReminderService.EXTRA_DECK_OPTION_ID, mOptions.getLong("id")
|
||||
ReminderService.EXTRA_DECK_OPTION_ID,
|
||||
mOptions.getLong("id")
|
||||
),
|
||||
0
|
||||
)
|
||||
@ -698,7 +704,6 @@ class DeckOptionsActivity :
|
||||
|
||||
companion object {
|
||||
fun reminderToCalendar(time: Time, reminder: JSONObject): Calendar {
|
||||
|
||||
val calendar = time.calendar()
|
||||
|
||||
calendar[Calendar.HOUR_OF_DAY] = reminder.getJSONArray("time").getInt(0)
|
||||
|
@ -22,6 +22,7 @@
|
||||
// usage of 'this' in constructors when class is non-final - weak warning
|
||||
// should be OK as this is only non-final for tests
|
||||
@file:Suppress("LeakingThis")
|
||||
|
||||
package com.ichi2.anki
|
||||
|
||||
import android.Manifest
|
||||
@ -137,6 +138,7 @@ import kotlin.system.measureTimeMillis
|
||||
|
||||
const val MIGRATION_WAS_LAST_POSTPONED_AT_SECONDS = "secondWhenMigrationWasPostponedLast"
|
||||
const val POSTPONE_MIGRATION_INTERVAL_DAYS = 5L
|
||||
|
||||
/**
|
||||
* The current entry point for AnkiDroid. Displays decks, allowing users to study. Many other functions.
|
||||
*
|
||||
@ -179,6 +181,7 @@ open class DeckPicker :
|
||||
private var mShortAnimDuration = 0
|
||||
private var mBackButtonPressedToExit = false
|
||||
private lateinit var mDeckPickerContent: RelativeLayout
|
||||
|
||||
@Suppress("Deprecation") // TODO: Encapsulate ProgressDialog within a class to limit the use of deprecated functionality
|
||||
private var mProgressDialog: android.app.ProgressDialog? = null
|
||||
private var mStudyoptionsFrame: View? = null // not lateInit - can be null
|
||||
@ -191,6 +194,7 @@ open class DeckPicker :
|
||||
private lateinit var mNoDecksPlaceholder: LinearLayout
|
||||
private lateinit var mPullToSyncWrapper: SwipeRefreshLayout
|
||||
private lateinit var mReviewSummaryTextView: TextView
|
||||
|
||||
@KotlinCleanup("make lateinit, but needs more changes")
|
||||
private var mUnmountReceiver: BroadcastReceiver? = null
|
||||
private lateinit var mFloatingActionMenu: DeckPickerFloatingActionMenu
|
||||
@ -343,7 +347,9 @@ open class DeckPicker :
|
||||
if (context.mProgressDialog == null || !context.mProgressDialog!!.isShowing) {
|
||||
context.mProgressDialog = StyledProgressDialog.show(
|
||||
context,
|
||||
context.resources.getString(R.string.import_title), null, false
|
||||
context.resources.getString(R.string.import_title),
|
||||
null,
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -377,7 +383,8 @@ open class DeckPicker :
|
||||
context.mProgressDialog = StyledProgressDialog.show(
|
||||
context,
|
||||
context.resources.getString(R.string.import_title),
|
||||
context.resources.getString(R.string.import_replacing), false
|
||||
context.resources.getString(R.string.import_replacing),
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -390,6 +397,7 @@ open class DeckPicker :
|
||||
context.mProgressDialog!!.setMessage(value)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ANDROID ACTIVITY METHODS
|
||||
// ----------------------------------------------------------------------------
|
||||
@ -1176,7 +1184,6 @@ open class DeckPicker :
|
||||
}
|
||||
|
||||
private fun showStartupScreensAndDialogs(preferences: SharedPreferences, skip: Int) {
|
||||
|
||||
// For Android 8/8.1 we want to use software rendering by default or the Reviewer UI is broken #7369
|
||||
if (sdkVersion == Build.VERSION_CODES.O ||
|
||||
sdkVersion == Build.VERSION_CODES.O_MR1
|
||||
@ -1693,7 +1700,8 @@ open class DeckPicker :
|
||||
if (mProgressDialog == null || !mProgressDialog!!.isShowing) {
|
||||
try {
|
||||
mProgressDialog = StyledProgressDialog.show(
|
||||
this@DeckPicker, resources.getString(R.string.sync_title),
|
||||
this@DeckPicker,
|
||||
resources.getString(R.string.sync_title),
|
||||
"""
|
||||
${resources.getString(R.string.sync_title)}
|
||||
${resources.getString(R.string.sync_up_down_size, mCountUp, mCountDown)}
|
||||
@ -1818,7 +1826,8 @@ open class DeckPicker :
|
||||
diff >= 86100 -> {
|
||||
// The difference if more than a day minus 5 minutes acceptable by ankiweb error
|
||||
res.getString(
|
||||
R.string.sync_log_clocks_unsynchronized, diff,
|
||||
R.string.sync_log_clocks_unsynchronized,
|
||||
diff,
|
||||
res.getString(R.string.sync_log_clocks_unsynchronized_date)
|
||||
)
|
||||
}
|
||||
@ -1826,7 +1835,8 @@ open class DeckPicker :
|
||||
// The difference would be within limit if we adjusted the time by few hours
|
||||
// It doesn't work for all timezones, but it covers most and it's a guess anyway
|
||||
res.getString(
|
||||
R.string.sync_log_clocks_unsynchronized, diff,
|
||||
R.string.sync_log_clocks_unsynchronized,
|
||||
diff,
|
||||
res.getString(R.string.sync_log_clocks_unsynchronized_tz)
|
||||
)
|
||||
}
|
||||
@ -1922,7 +1932,8 @@ open class DeckPicker :
|
||||
if (dialogMessage == null) {
|
||||
dialogMessage = res.getString(
|
||||
R.string.sync_log_error_specific,
|
||||
code.toString(), result[1]
|
||||
code.toString(),
|
||||
result[1]
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@ -2041,7 +2052,9 @@ open class DeckPicker :
|
||||
val frag = supportFragmentManager.findFragmentById(R.id.studyoptions_fragment)
|
||||
return if (frag is StudyOptionsFragment) {
|
||||
frag
|
||||
} else null
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2161,7 +2174,8 @@ open class DeckPicker :
|
||||
setAction(R.string.study_more) {
|
||||
val d = mCustomStudyDialogFactory.newCustomStudyDialog().withArguments(
|
||||
CustomStudyDialog.ContextMenuConfiguration.LIMITS,
|
||||
col.decks.selected(), true
|
||||
col.decks.selected(),
|
||||
true
|
||||
)
|
||||
showDialogFragment(d)
|
||||
}
|
||||
@ -2200,7 +2214,8 @@ open class DeckPicker :
|
||||
setAction(R.string.custom_study) {
|
||||
val d = mCustomStudyDialogFactory.newCustomStudyDialog().withArguments(
|
||||
CustomStudyDialog.ContextMenuConfiguration.EMPTY_SCHEDULE,
|
||||
col.decks.selected(), true
|
||||
col.decks.selected(),
|
||||
true
|
||||
)
|
||||
showDialogFragment(d)
|
||||
}
|
||||
@ -2306,7 +2321,8 @@ open class DeckPicker :
|
||||
mNoDecksPlaceholder.visibility = if (isEmpty) View.VISIBLE else View.GONE
|
||||
} else {
|
||||
val translation = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, 8f,
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
8f,
|
||||
resources.displayMetrics
|
||||
)
|
||||
val decksListShown = mDeckPickerContent.visibility == View.VISIBLE
|
||||
@ -2691,8 +2707,10 @@ open class DeckPicker :
|
||||
internal inner class CheckDatabaseListener : TaskListener<String, Pair<Boolean, CheckDatabaseResult?>?>() {
|
||||
override fun onPreExecute() {
|
||||
mProgressDialog = StyledProgressDialog.show(
|
||||
this@DeckPicker, AnkiDroidApp.appResources.getString(R.string.app_name),
|
||||
resources.getString(R.string.check_db_message), false
|
||||
this@DeckPicker,
|
||||
AnkiDroidApp.appResources.getString(R.string.app_name),
|
||||
resources.getString(R.string.check_db_message),
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
@ -2930,6 +2948,7 @@ open class DeckPicker :
|
||||
get() = getSharedPrefs(baseContext).getLong(MIGRATION_WAS_LAST_POSTPONED_AT_SECONDS, 0L)
|
||||
set(timeInSecond) = getSharedPrefs(baseContext)
|
||||
.edit { putLong(MIGRATION_WAS_LAST_POSTPONED_AT_SECONDS, timeInSecond) }
|
||||
|
||||
/**
|
||||
* Show a dialog offering to migrate, postpone or learn more.
|
||||
*/
|
||||
@ -2945,7 +2964,8 @@ open class DeckPicker :
|
||||
else -> R.string.migration_update_request_dialog_download_warning
|
||||
}
|
||||
|
||||
@Language("HTML") val message = """${getString(R.string.migration_update_request)}
|
||||
@Language("HTML")
|
||||
val message = """${getString(R.string.migration_update_request)}
|
||||
<br>
|
||||
<br>${getString(ifYouUninstallMessageId)}"""
|
||||
|
||||
@ -2993,7 +3013,7 @@ data class OptionsMenuState(
|
||||
/** If undo is available, a string describing the action. */
|
||||
val undoIcon: String?,
|
||||
val syncIcon: SyncIconState,
|
||||
val offerToMigrate: Boolean,
|
||||
val offerToMigrate: Boolean
|
||||
)
|
||||
|
||||
enum class SyncIconState {
|
||||
@ -3001,10 +3021,11 @@ enum class SyncIconState {
|
||||
PendingChanges,
|
||||
FullSync,
|
||||
NotLoggedIn,
|
||||
|
||||
/**
|
||||
* The icon should appear as disabled. Currently only occurs during scoped storage migration.
|
||||
*/
|
||||
Disabled,
|
||||
Disabled
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,7 +57,7 @@ class DeckSpinnerSelection(
|
||||
private val spinner: Spinner,
|
||||
private val showAllDecks: Boolean,
|
||||
private val alwaysShowDefault: Boolean,
|
||||
private val showFilteredDecks: Boolean,
|
||||
private val showFilteredDecks: Boolean
|
||||
) {
|
||||
/**
|
||||
* All of the decks shown to the user.
|
||||
@ -112,7 +112,6 @@ class DeckSpinnerSelection(
|
||||
}
|
||||
val noteDeckAdapter: ArrayAdapter<String?> = object : ArrayAdapter<String?>(context, R.layout.multiline_spinner_item, deckNames as List<String?>) {
|
||||
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
|
||||
// Cast the drop down items (popup items) as text view
|
||||
val tv = super.getDropDownView(position, convertView, parent) as TextView
|
||||
|
||||
@ -189,7 +188,9 @@ class DeckSpinnerSelection(
|
||||
fun selectDeckById(deckId: DeckId, setAsCurrentDeck: Boolean): Boolean {
|
||||
return if (deckId == ALL_DECKS_ID) {
|
||||
selectAllDecks()
|
||||
} else selectDeck(deckId, setAsCurrentDeck)
|
||||
} else {
|
||||
selectDeck(deckId, setAsCurrentDeck)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,6 +55,7 @@ class FieldEditText : FixedEditText, NoteService.NoteField {
|
||||
private var mOrigBackground: Drawable? = null
|
||||
private var mSelectionChangeListener: TextSelectionListener? = null
|
||||
private var mImageListener: ImagePasteListener? = null
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
var clipboard: ClipboardManager? = null
|
||||
|
||||
@ -103,7 +104,8 @@ class FieldEditText : FixedEditText, NoteService.NoteField {
|
||||
val inputConnection = super.onCreateInputConnection(editorInfo) ?: return null
|
||||
EditorInfoCompat.setContentMimeTypes(editorInfo, IMAGE_MIME_TYPES)
|
||||
ViewCompat.setOnReceiveContentListener(
|
||||
this, IMAGE_MIME_TYPES,
|
||||
this,
|
||||
IMAGE_MIME_TYPES,
|
||||
object : OnReceiveContentListener {
|
||||
override fun onReceiveContent(view: View, payload: ContentInfoCompat): ContentInfoCompat? {
|
||||
val pair = payload.partition { item -> item.uri != null }
|
||||
@ -219,7 +221,9 @@ class FieldEditText : FixedEditText, NoteService.NoteField {
|
||||
private fun onImagePaste(imageUri: Uri?): Boolean {
|
||||
return if (imageUri == null) {
|
||||
false
|
||||
} else mImageListener!!.onImagePaste(this, imageUri)
|
||||
} else {
|
||||
mImageListener!!.onImagePaste(this, imageUri)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(state: Parcelable) {
|
||||
|
@ -54,8 +54,10 @@ class FilterSheetBottomFragment :
|
||||
private lateinit var flagListAdapter: FlagsAdapter
|
||||
|
||||
private lateinit var flagToggleIcon: ImageView
|
||||
|
||||
/** Heading of the Flags filter section */
|
||||
private lateinit var filterHeaderFlags: TextView
|
||||
|
||||
/** Icon of the Flags filter section */
|
||||
private lateinit var filterIconFlags: ImageView
|
||||
|
||||
@ -145,9 +147,7 @@ class FilterSheetBottomFragment :
|
||||
}
|
||||
|
||||
flagsHeaderLayout.setOnClickListener {
|
||||
|
||||
if (SystemClock.elapsedRealtime() - lastClickTime > DELAY_TIME) {
|
||||
|
||||
lastClickTime = SystemClock.elapsedRealtime().toInt()
|
||||
|
||||
if (flagsRecyclerViewLayout.isVisible) {
|
||||
@ -164,7 +164,6 @@ class FilterSheetBottomFragment :
|
||||
private fun createQuery(
|
||||
flagList: Set<SearchNode.Flag>
|
||||
): String {
|
||||
|
||||
if (flagList.isEmpty()) {
|
||||
return ""
|
||||
}
|
||||
@ -207,6 +206,7 @@ class FilterSheetBottomFragment :
|
||||
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
private val itemTextView: TextView = view.findViewById(R.id.filter_list_item)
|
||||
val icon: ImageView = view.findViewById(R.id.filter_list_icon)
|
||||
|
||||
/** Checks whether flagSearchItems was empty before adding new element to it */
|
||||
|
||||
private fun onFlagItemClicked(item: Flags) {
|
||||
|
@ -47,6 +47,7 @@ private const val GITHUB_COMMITS = "https://github.com/ankidroid/Anki-Android/co
|
||||
*/
|
||||
class Info : AnkiActivity() {
|
||||
private var mWebView: WebView? = null
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
if (showedActivityFailedScreen(savedInstanceState)) {
|
||||
@ -110,7 +111,6 @@ class Info : AnkiActivity() {
|
||||
mWebView!!.settings.javaScriptEnabled = true
|
||||
mWebView!!.webViewClient = object : WebViewClient() {
|
||||
override fun onPageFinished(view: WebView, url: String) {
|
||||
|
||||
/* The order of below javascript code must not change (this order works both in debug and release mode)
|
||||
* or else it will break in any one mode.
|
||||
*/
|
||||
|
@ -30,7 +30,6 @@ object InitialActivity {
|
||||
/** Returns null on success */
|
||||
@CheckResult
|
||||
fun getStartupFailureType(context: Context): StartupFailure? {
|
||||
|
||||
// A WebView failure means that we skip `AnkiDroidApp`, and therefore haven't loaded the collection
|
||||
if (AnkiDroidApp.webViewFailedToLoad()) {
|
||||
return StartupFailure.WEBVIEW_FAILED
|
||||
|
@ -33,7 +33,7 @@ object LeakCanaryConfiguration {
|
||||
retainedVisibleThreshold = 0,
|
||||
referenceMatchers = AndroidReferenceMatchers.appDefaults,
|
||||
computeRetainedHeapSize = false,
|
||||
maxStoredHeapDumps = 0,
|
||||
maxStoredHeapDumps = 0
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -63,10 +63,11 @@ object MetaDB {
|
||||
private fun openDB(context: Context) {
|
||||
try {
|
||||
mMetaDb = context.openOrCreateDatabase(DATABASE_NAME, 0, null).let {
|
||||
if (it.needUpgrade(DATABASE_VERSION))
|
||||
if (it.needUpgrade(DATABASE_VERSION)) {
|
||||
upgradeDB(it, DATABASE_VERSION)
|
||||
else
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
Timber.v("Opening MetaDB")
|
||||
} catch (e: Exception) {
|
||||
@ -225,7 +226,10 @@ object MetaDB {
|
||||
mMetaDb!!.execSQL(
|
||||
"INSERT INTO languages (did, ord, qa, language) " + " VALUES (?, ?, ?, ?);",
|
||||
arrayOf<Any>(
|
||||
did, ord, qa.int, language
|
||||
did,
|
||||
ord,
|
||||
qa.int,
|
||||
language
|
||||
)
|
||||
)
|
||||
Timber.v("Store language for deck %d", did)
|
||||
@ -233,7 +237,10 @@ object MetaDB {
|
||||
mMetaDb!!.execSQL(
|
||||
"UPDATE languages SET language = ? WHERE did = ? AND ord = ? AND qa = ?;",
|
||||
arrayOf<Any>(
|
||||
language, did, ord, qa.int
|
||||
language,
|
||||
did,
|
||||
ord,
|
||||
qa.int
|
||||
)
|
||||
)
|
||||
Timber.v("Update language for deck %d", did)
|
||||
@ -460,8 +467,13 @@ object MetaDB {
|
||||
var cursor: Cursor? = null
|
||||
try {
|
||||
cursor = mMetaDb!!.query(
|
||||
"smallWidgetStatus", arrayOf("due", "eta"),
|
||||
null, null, null, null, null
|
||||
"smallWidgetStatus",
|
||||
arrayOf("due", "eta"),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
if (cursor.moveToNext()) {
|
||||
return intArrayOf(cursor.getInt(0), cursor.getInt(1))
|
||||
|
@ -50,6 +50,7 @@ open class MyAccount : AnkiActivity() {
|
||||
private lateinit var mUsername: EditText
|
||||
private lateinit var mPassword: TextInputEditField
|
||||
private lateinit var mUsernameLoggedIn: TextView
|
||||
|
||||
@Suppress("Deprecation")
|
||||
private var mProgressDialog: android.app.ProgressDialog? = null
|
||||
var toolbar: Toolbar? = null
|
||||
@ -116,7 +117,8 @@ open class MyAccount : AnkiActivity() {
|
||||
mLoginListener,
|
||||
Connection.Payload(
|
||||
arrayOf(
|
||||
username, password,
|
||||
username,
|
||||
password,
|
||||
getInstance(this)
|
||||
)
|
||||
)
|
||||
@ -155,7 +157,8 @@ open class MyAccount : AnkiActivity() {
|
||||
mLoginListener,
|
||||
Connection.Payload(
|
||||
arrayOf(
|
||||
username, password,
|
||||
username,
|
||||
password,
|
||||
getInstance(this)
|
||||
)
|
||||
)
|
||||
@ -258,8 +261,10 @@ open class MyAccount : AnkiActivity() {
|
||||
Timber.d("loginListener.onPreExecute()")
|
||||
if (mProgressDialog == null || !mProgressDialog!!.isShowing) {
|
||||
mProgressDialog = StyledProgressDialog.show(
|
||||
this@MyAccount, null,
|
||||
resources.getString(R.string.alert_logging_message), false
|
||||
this@MyAccount,
|
||||
null,
|
||||
resources.getString(R.string.alert_logging_message),
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -61,6 +61,7 @@ abstract class NavigationDrawerActivity :
|
||||
*/
|
||||
protected var fragmented = false
|
||||
private var mNavButtonGoesBack = false
|
||||
|
||||
// Navigation drawer list item entries
|
||||
private lateinit var mDrawerLayout: DrawerLayout
|
||||
private lateinit var mNavigationView: NavigationView
|
||||
@ -77,7 +78,9 @@ abstract class NavigationDrawerActivity :
|
||||
|
||||
// Using ClosableDrawerLayout as a parent view.
|
||||
val closableDrawerLayout = LayoutInflater.from(this).inflate(
|
||||
navigationDrawerLayout, null, false
|
||||
navigationDrawerLayout,
|
||||
null,
|
||||
false
|
||||
) as ClosableDrawerLayout
|
||||
// Get CoordinatorLayout using resource ID
|
||||
val coordinatorLayout = LayoutInflater.from(this)
|
||||
|
@ -134,6 +134,7 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
|
||||
|
||||
// non-null after onCollectionLoaded
|
||||
private var mEditorNote: Note? = null
|
||||
|
||||
/* Null if adding a new card. Presently NonNull if editing an existing note - but this is subject to change */
|
||||
private var mCurrentEditedCard: Card? = null
|
||||
private var mSelectedTags: ArrayList<String>? = null
|
||||
@ -142,6 +143,7 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
|
||||
var deckId: DeckId = 0
|
||||
private set
|
||||
private var mAllModelIds: List<Long>? = null
|
||||
|
||||
@KotlinCleanup("this ideally should be Int, Int?")
|
||||
private var mModelChangeFieldMap: MutableMap<Int, Int>? = null
|
||||
private var mModelChangeCardMap: HashMap<Int, Int?>? = null
|
||||
@ -201,7 +203,9 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
|
||||
}
|
||||
return if (allFieldsHaveContent()) {
|
||||
R.string.note_editor_no_cards_created_all_fields
|
||||
} else R.string.note_editor_no_cards_created
|
||||
} else {
|
||||
R.string.note_editor_no_cards_created
|
||||
}
|
||||
|
||||
// Otherwise, display "no cards created".
|
||||
}
|
||||
@ -360,7 +364,9 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
|
||||
}
|
||||
mDeckSpinnerSelection =
|
||||
DeckSpinnerSelection(
|
||||
this, col, findViewById(R.id.note_deck_spinner),
|
||||
this,
|
||||
col,
|
||||
findViewById(R.id.note_deck_spinner),
|
||||
showAllDecks = false,
|
||||
alwaysShowDefault = true,
|
||||
showFilteredDecks = false
|
||||
@ -429,7 +435,6 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
|
||||
}
|
||||
|
||||
private fun modifyCurrentSelection(formatter: Toolbar.TextFormatter, textBox: FieldEditText) {
|
||||
|
||||
// get the current text and selection locations
|
||||
val selectionStart = textBox.selectionStart
|
||||
val selectionEnd = textBox.selectionEnd
|
||||
@ -623,7 +628,9 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
|
||||
// changed fields?
|
||||
return if (isFieldEdited) {
|
||||
true
|
||||
} else isTagsEdited
|
||||
} else {
|
||||
isTagsEdited
|
||||
}
|
||||
// changed tags?
|
||||
}
|
||||
|
||||
@ -805,7 +812,6 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
|
||||
val dialog = ConfirmationDialog()
|
||||
dialog.setArgs(res.getString(R.string.full_sync_confirmation))
|
||||
val confirm = Runnable {
|
||||
|
||||
// Bypass the check once the user confirms
|
||||
col.modSchemaNoCheck()
|
||||
try {
|
||||
@ -1182,7 +1188,6 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
|
||||
}
|
||||
}
|
||||
REQUEST_TEMPLATE_EDIT -> {
|
||||
|
||||
// Model can change regardless of exit type - update ourselves and CardBrowser
|
||||
mReloadRequired = true
|
||||
mEditorNote!!.reloadModel()
|
||||
@ -1416,8 +1421,9 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
|
||||
|
||||
@NeedsTest("If a field is sticky after synchronization, the toggleStickyButton should be activated.")
|
||||
private fun setToggleStickyButtonListener(toggleStickyButton: ImageButton?, index: Int) {
|
||||
if (currentFields.getJSONObject(index).getBoolean("sticky"))
|
||||
if (currentFields.getJSONObject(index).getBoolean("sticky")) {
|
||||
mToggleStickyText.getOrPut(index) { "" }
|
||||
}
|
||||
if (mToggleStickyText[index] == null) {
|
||||
toggleStickyButton!!.background.alpha = 64
|
||||
} else {
|
||||
@ -1684,8 +1690,12 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
|
||||
private fun updateToolbar() {
|
||||
val editorLayout = findViewById<View>(R.id.note_editor_layout)
|
||||
val bottomMargin =
|
||||
if (shouldHideToolbar()) 0 else resources.getDimension(R.dimen.note_editor_toolbar_height)
|
||||
.toInt()
|
||||
if (shouldHideToolbar()) {
|
||||
0
|
||||
} else {
|
||||
resources.getDimension(R.dimen.note_editor_toolbar_height)
|
||||
.toInt()
|
||||
}
|
||||
val params = editorLayout.layoutParams as MarginLayoutParams
|
||||
params.bottomMargin = bottomMargin
|
||||
editorLayout.layoutParams = params
|
||||
@ -1718,7 +1728,6 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
|
||||
}
|
||||
val buttons = toolbarButtons
|
||||
for (b in buttons) {
|
||||
|
||||
// 0th button shows as '1' and is Ctrl + 1
|
||||
val visualIndex = b.index + 1
|
||||
var text = visualIndex.toString()
|
||||
|
@ -80,7 +80,7 @@ abstract class Onboarding<Feature>(
|
||||
DECK_PICKER_ONBOARDING,
|
||||
REVIEWER_ONBOARDING,
|
||||
NOTE_EDITOR_ONBOARDING,
|
||||
CARD_BROWSER_ONBOARDING,
|
||||
CARD_BROWSER_ONBOARDING
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ class OnboardingUtils {
|
||||
* Preference can be toggled by visiting 'Advanced' settings in the app.
|
||||
*/
|
||||
const val SHOW_ONBOARDING = "showOnboarding"
|
||||
|
||||
@VisibleForTesting
|
||||
val featureConstants: MutableSet<String> = HashSet()
|
||||
|
||||
|
@ -121,6 +121,7 @@ open class Reviewer :
|
||||
@get:VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
var whiteboard: Whiteboard? = null
|
||||
protected set
|
||||
|
||||
// Record Audio
|
||||
/** File of the temporary mic record */
|
||||
@get:VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
@ -150,7 +151,8 @@ open class Reviewer :
|
||||
val cardCount: Int = result!!.value.result.size
|
||||
showThemedToast(
|
||||
this,
|
||||
resources.getQuantityString(toastResourceId, cardCount, cardCount), true
|
||||
resources.getQuantityString(toastResourceId, cardCount, cardCount),
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -201,7 +203,9 @@ open class Reviewer :
|
||||
val shownAsToolbarButton = mActionButtons.findMenuItem(ActionButtons.RES_FLAG)?.isActionButton == true
|
||||
return if (shownAsToolbarButton && !mPrefFullscreenReview) {
|
||||
CardMarker.FLAG_NONE
|
||||
} else actualValue
|
||||
} else {
|
||||
actualValue
|
||||
}
|
||||
}
|
||||
|
||||
override fun createWebView(): WebView {
|
||||
@ -600,7 +604,8 @@ open class Reviewer :
|
||||
private fun openOrToggleMicToolbar() {
|
||||
if (!canRecordAudio(this)) {
|
||||
ActivityCompat.requestPermissions(
|
||||
this, arrayOf(Manifest.permission.RECORD_AUDIO),
|
||||
this,
|
||||
arrayOf(Manifest.permission.RECORD_AUDIO),
|
||||
REQUEST_AUDIO_PERMISSION
|
||||
)
|
||||
} else {
|
||||
@ -632,7 +637,8 @@ open class Reviewer :
|
||||
return
|
||||
}
|
||||
val lp2 = FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
audioView!!.layoutParams = lp2
|
||||
val micToolBarLayer = findViewById<LinearLayout>(R.id.mic_tool_bar_layer)
|
||||
@ -924,7 +930,9 @@ open class Reviewer :
|
||||
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
|
||||
return if (mProcessor.onKeyUp(keyCode, event)) {
|
||||
true
|
||||
} else super.onKeyUp(keyCode, event)
|
||||
} else {
|
||||
super.onKeyUp(keyCode, event)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> setupSubMenu(menu: Menu, @IdRes parentMenu: Int, subMenuProvider: T) where T : ActionProvider?, T : SubMenuProvider? {
|
||||
@ -1419,20 +1427,27 @@ open class Reviewer :
|
||||
private fun suspendNoteAvailable(): Boolean {
|
||||
return if (currentCard == null || isControlBlocked) {
|
||||
false
|
||||
} else col.db.queryScalar(
|
||||
"select 1 from cards where nid = ? and id != ? and queue != " + Consts.QUEUE_TYPE_SUSPENDED + " limit 1",
|
||||
currentCard!!.nid, currentCard!!.id
|
||||
) == 1
|
||||
} else {
|
||||
col.db.queryScalar(
|
||||
"select 1 from cards where nid = ? and id != ? and queue != " + Consts.QUEUE_TYPE_SUSPENDED + " limit 1",
|
||||
currentCard!!.nid,
|
||||
currentCard!!.id
|
||||
) == 1
|
||||
}
|
||||
// whether there exists a sibling not buried.
|
||||
}
|
||||
|
||||
@KotlinCleanup("mCurrentCard handling")
|
||||
private fun buryNoteAvailable(): Boolean {
|
||||
return if (currentCard == null || isControlBlocked) {
|
||||
false
|
||||
} else col.db.queryScalar(
|
||||
"select 1 from cards where nid = ? and id != ? and queue >= " + Consts.QUEUE_TYPE_NEW + " limit 1",
|
||||
currentCard!!.nid, currentCard!!.id
|
||||
) == 1
|
||||
} else {
|
||||
col.db.queryScalar(
|
||||
"select 1 from cards where nid = ? and id != ? and queue >= " + Consts.QUEUE_TYPE_NEW + " limit 1",
|
||||
currentCard!!.nid,
|
||||
currentCard!!.id
|
||||
) == 1
|
||||
}
|
||||
// Whether there exists a sibling which is neither suspended nor buried
|
||||
}
|
||||
|
||||
|
@ -186,5 +186,5 @@ data class DownloadFile(
|
||||
val url: String,
|
||||
val userAgent: String,
|
||||
val contentDisposition: String,
|
||||
val mimeType: String,
|
||||
val mimeType: String
|
||||
) : Serializable
|
||||
|
@ -140,7 +140,8 @@ class SharedDecksDownloadFragment : Fragment() {
|
||||
activity?.registerReceiver(mOnComplete, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
|
||||
|
||||
val currentFileName = URLUtil.guessFileName(
|
||||
fileToBeDownloaded.url, fileToBeDownloaded.contentDisposition,
|
||||
fileToBeDownloaded.url,
|
||||
fileToBeDownloaded.contentDisposition,
|
||||
fileToBeDownloaded.mimeType
|
||||
)
|
||||
|
||||
|
@ -115,8 +115,12 @@ class Statistics : NavigationDrawerActivity(), DeckSelectionListener, SubtitleLi
|
||||
else -> Timber.w("Unknown defaultDeck: %s", defaultDeck)
|
||||
}
|
||||
mDeckSpinnerSelection = DeckSpinnerSelection(
|
||||
this, col,
|
||||
findViewById(R.id.toolbar_spinner), showAllDecks = true, alwaysShowDefault = true, showFilteredDecks = true
|
||||
this,
|
||||
col,
|
||||
findViewById(R.id.toolbar_spinner),
|
||||
showAllDecks = true,
|
||||
alwaysShowDefault = true,
|
||||
showFilteredDecks = true
|
||||
)
|
||||
mDeckSpinnerSelection.initializeActionBarDeckSpinner(this.supportActionBar!!)
|
||||
mDeckSpinnerSelection.selectDeckById(mStatsDeckId, false)
|
||||
@ -285,7 +289,8 @@ class Statistics : NavigationDrawerActivity(), DeckSelectionListener, SubtitleLi
|
||||
mTabLayoutMediator.detach()
|
||||
}
|
||||
mTabLayoutMediator = TabLayoutMediator(
|
||||
slidingTabLayout, mActivityPager
|
||||
slidingTabLayout,
|
||||
mActivityPager
|
||||
) { tab: TabLayout.Tab, position: Int -> tab.text = getTabTitle(position) }
|
||||
mTabLayoutMediator.attach()
|
||||
}
|
||||
|
@ -583,7 +583,6 @@ class StudyOptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||
private fun rebuildUi(result: DeckStudyData?, refreshDecklist: Boolean) {
|
||||
dismissProgressDialog()
|
||||
if (result != null) {
|
||||
|
||||
// Don't do anything if the fragment is no longer attached to it's Activity or col has been closed
|
||||
if (activity == null) {
|
||||
Timber.e("StudyOptionsFragment.mRefreshFragmentListener :: can't refresh")
|
||||
@ -718,8 +717,10 @@ class StudyOptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||
*/
|
||||
@Suppress("unused")
|
||||
private const val BROWSE_CARDS = 3
|
||||
|
||||
@Suppress("unused")
|
||||
private const val STATISTICS = 4
|
||||
|
||||
@Suppress("unused")
|
||||
private const val DECK_OPTIONS = 5
|
||||
|
||||
|
@ -47,6 +47,7 @@ object SyncPreferences {
|
||||
const val CURRENT_SYNC_URI = "currentSyncUri"
|
||||
const val CUSTOM_SYNC_URI = "syncBaseUrl"
|
||||
const val CUSTOM_SYNC_ENABLED = CUSTOM_SYNC_URI + VersatileTextWithASwitchPreference.SWITCH_SUFFIX
|
||||
|
||||
// Used in the legacy schema path
|
||||
const val HOSTNUM = "hostNum"
|
||||
}
|
||||
@ -106,7 +107,7 @@ fun isLoggedIn() = AnkiDroidApp.getSharedPrefs(AnkiDroidApp.instance).getString(
|
||||
|
||||
fun DeckPicker.handleNewSync(
|
||||
conflict: Connection.ConflictResolution?,
|
||||
syncMedia: Boolean,
|
||||
syncMedia: Boolean
|
||||
) {
|
||||
val auth = this.syncAuth() ?: return
|
||||
val deckPicker = this
|
||||
@ -177,7 +178,7 @@ private fun cancelSync(backend: Backend) {
|
||||
private suspend fun handleNormalSync(
|
||||
deckPicker: DeckPicker,
|
||||
auth: SyncAuth,
|
||||
syncMedia: Boolean,
|
||||
syncMedia: Boolean
|
||||
) {
|
||||
val output = deckPicker.withProgress(
|
||||
extractProgress = {
|
||||
@ -242,7 +243,7 @@ private fun fullDownloadProgress(title: String): ProgressContext.() -> Unit {
|
||||
private suspend fun handleDownload(
|
||||
deckPicker: DeckPicker,
|
||||
auth: SyncAuth,
|
||||
syncMedia: Boolean,
|
||||
syncMedia: Boolean
|
||||
) {
|
||||
deckPicker.withProgress(
|
||||
extractProgress = fullDownloadProgress(TR.syncDownloadingFromAnkiweb()),
|
||||
@ -272,7 +273,7 @@ private suspend fun handleDownload(
|
||||
private suspend fun handleUpload(
|
||||
deckPicker: DeckPicker,
|
||||
auth: SyncAuth,
|
||||
syncMedia: Boolean,
|
||||
syncMedia: Boolean
|
||||
) {
|
||||
deckPicker.withProgress(
|
||||
extractProgress = fullDownloadProgress(TR.syncUploadingToAnkiweb()),
|
||||
@ -325,7 +326,7 @@ private suspend fun handleMediaSync(
|
||||
},
|
||||
updateUi = {
|
||||
dialog.setMessage(text)
|
||||
},
|
||||
}
|
||||
) {
|
||||
withContext(Dispatchers.IO) {
|
||||
backend.syncMedia(auth)
|
||||
|
@ -172,7 +172,9 @@ class Whiteboard(activity: AnkiActivity, handleMultiTouch: Boolean, inverted: Bo
|
||||
MotionEvent.ACTION_POINTER_UP -> trySecondFingerClick(event)
|
||||
else -> false
|
||||
}
|
||||
} else false
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -521,7 +523,8 @@ class Whiteboard(activity: AnkiActivity, handleMultiTouch: Boolean, inverted: Bo
|
||||
val whiteboard = Whiteboard(context, handleMultiTouch, currentTheme.isNightMode)
|
||||
mWhiteboardMultiTouchMethods = whiteboardMultiTouchMethods
|
||||
val lp2 = FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
whiteboard.layoutParams = lp2
|
||||
val fl = context.findViewById<FrameLayout>(R.id.whiteboard)
|
||||
|
@ -66,7 +66,9 @@ class AnkiDroidCrashReportDialog : CrashReportDialog(), DialogInterface.OnClickL
|
||||
override fun buildCustomView(savedInstanceState: Bundle?): View {
|
||||
val preferences = AnkiDroidApp.getSharedPrefs(this)
|
||||
val inflater = layoutInflater
|
||||
@SuppressLint("InflateParams") val rootView = // when you inflate into an alert dialog, you have no parent view
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
val rootView = // when you inflate into an alert dialog, you have no parent view
|
||||
inflater.inflate(R.layout.feedback, null)
|
||||
mAlwaysReportCheckBox = rootView.findViewById(R.id.alwaysReportCheckbox)
|
||||
mAlwaysReportCheckBox?.isChecked = preferences.getBoolean("autoreportCheckboxValue", true)
|
||||
|
@ -39,6 +39,7 @@ import timber.log.Timber
|
||||
@KotlinCleanup("see if we can make variables lazy, or properties without the `s` prefix")
|
||||
object UsageAnalytics {
|
||||
const val ANALYTICS_OPTIN_KEY = "analyticsOptIn"
|
||||
|
||||
@KotlinCleanup("lateinit")
|
||||
private var sAnalytics: GoogleAnalytics? = null
|
||||
private var sOriginalUncaughtExceptionHandler: Thread.UncaughtExceptionHandler? = null
|
||||
@ -136,13 +137,9 @@ object UsageAnalytics {
|
||||
Thread.setDefaultUncaughtExceptionHandler(sOriginalUncaughtExceptionHandler)
|
||||
sOriginalUncaughtExceptionHandler = null
|
||||
}
|
||||
/**
|
||||
* Determine whether we are disabled or not
|
||||
*/
|
||||
|
||||
/**
|
||||
* Allow users to enable or disable analytics
|
||||
*
|
||||
* @param optIn true allows collection of analytics information
|
||||
*/
|
||||
@set:Synchronized
|
||||
private var optIn: Boolean
|
||||
@ -174,7 +171,6 @@ object UsageAnalytics {
|
||||
*/
|
||||
@Synchronized
|
||||
fun reInitialize() {
|
||||
|
||||
// send any pending async hits, re-chain default exception handlers and re-init
|
||||
Timber.i("reInitialize()")
|
||||
sAnalytics!!.flush()
|
||||
@ -283,7 +279,9 @@ object UsageAnalytics {
|
||||
// if we're not under the ACRA process then we're fine to initialize a WebView
|
||||
return if (!ACRA.isACRASenderServiceProcess()) {
|
||||
true
|
||||
} else hasSetDataDirectory()
|
||||
} else {
|
||||
hasSetDataDirectory()
|
||||
}
|
||||
|
||||
// If we have a custom data directory, then the crash will not occur.
|
||||
}
|
||||
@ -313,7 +311,6 @@ object UsageAnalytics {
|
||||
*/
|
||||
private class AndroidDefaultRequest : DefaultRequest() {
|
||||
fun setAndroidRequestParameters(context: Context): DefaultRequest {
|
||||
|
||||
// Are we running on really large screens or small screens? Send raw screen size
|
||||
try {
|
||||
val size = DisplayUtils.getDisplayDimensions(context)
|
||||
|
@ -87,6 +87,7 @@ class CardAppearance(private val customFonts: ReviewerCustomFonts, private val c
|
||||
// font-weight to 700
|
||||
return content.replace("font-weight:600;", "font-weight:700;")
|
||||
}
|
||||
|
||||
/**
|
||||
* hasUserDefinedNightMode finds out if the user has included class .night_mode in card's stylesheet
|
||||
*/
|
||||
|
@ -43,7 +43,7 @@ class CardHtml(
|
||||
private val getAnswerContentWithoutFrontSide_slow: (() -> String),
|
||||
@RustCleanup("too many variables, combine once we move away from backend")
|
||||
private var questionSound: List<SoundOrVideoTag>? = null,
|
||||
private var answerSound: List<SoundOrVideoTag>? = null,
|
||||
private var answerSound: List<SoundOrVideoTag>? = null
|
||||
) {
|
||||
fun getSoundTags(sideFor: Side): List<SoundOrVideoTag> {
|
||||
if (sideFor == this.side) {
|
||||
|
@ -24,6 +24,7 @@ class CardTemplate(template: String) {
|
||||
private var mPreClass: String? = null
|
||||
private var mPreContent: String? = null
|
||||
private var mPostContent: String? = null
|
||||
|
||||
@CheckResult
|
||||
fun render(content: String, style: String, script: String, cardClass: String): String {
|
||||
return mPreStyle + style + mPreScript + script + mPreClass + cardClass + mPreContent + content + mPostContent
|
||||
|
@ -36,7 +36,7 @@ fun interface GestureListener {
|
||||
}
|
||||
|
||||
enum class Gesture(
|
||||
@get:JvmName("getResourceId") val resourceId: Int,
|
||||
@get:JvmName("getResourceId") val resourceId: Int
|
||||
) {
|
||||
SWIPE_UP(R.string.gestures_swipe_up),
|
||||
SWIPE_DOWN(R.string.gestures_swipe_down),
|
||||
@ -75,6 +75,7 @@ enum class TapGestureMode {
|
||||
* are ambiguous in a nine-point system and thus not interchangeable
|
||||
*/
|
||||
FOUR_POINT,
|
||||
|
||||
/**
|
||||
* Divide the screen into 9 equally sized squares for touch targets.
|
||||
* Better for tablets
|
||||
|
@ -54,8 +54,9 @@ class GestureProcessor(private val processor: ViewerCommand.CommandProcessor?) {
|
||||
val associatedCommands = HashMap<Gesture, ViewerCommand>()
|
||||
for (command in ViewerCommand.values()) {
|
||||
for (mappableBinding in MappableBinding.fromPreference(preferences, command)) {
|
||||
if (mappableBinding.binding.isGesture)
|
||||
if (mappableBinding.binding.isGesture) {
|
||||
associatedCommands[mappableBinding.binding.gesture!!] = command
|
||||
}
|
||||
}
|
||||
}
|
||||
gestureDoubleTap = associatedCommands[Gesture.DOUBLE_TAP]
|
||||
|
@ -46,9 +46,11 @@ class TypeAnswer(
|
||||
|
||||
/** What the learner actually typed (externally mutable) */
|
||||
var input = ""
|
||||
|
||||
/** Font face of the 'compare to' field */
|
||||
var font = ""
|
||||
private set
|
||||
|
||||
/** The font size of the 'compare to' field */
|
||||
var size = 0
|
||||
private set
|
||||
|
@ -124,19 +124,23 @@ enum class ViewerCommand(val resourceId: Int) {
|
||||
when (this) {
|
||||
FLIP_OR_ANSWER_EASE1 -> from(
|
||||
keyCode(KeyEvent.KEYCODE_BUTTON_Y, CardSide.BOTH),
|
||||
keyCode(KeyEvent.KEYCODE_1, CardSide.ANSWER), keyCode(KeyEvent.KEYCODE_NUMPAD_1, CardSide.ANSWER)
|
||||
keyCode(KeyEvent.KEYCODE_1, CardSide.ANSWER),
|
||||
keyCode(KeyEvent.KEYCODE_NUMPAD_1, CardSide.ANSWER)
|
||||
)
|
||||
FLIP_OR_ANSWER_EASE2 -> from(
|
||||
keyCode(KeyEvent.KEYCODE_BUTTON_X, CardSide.BOTH),
|
||||
keyCode(KeyEvent.KEYCODE_2, CardSide.ANSWER), keyCode(KeyEvent.KEYCODE_NUMPAD_2, CardSide.ANSWER)
|
||||
keyCode(KeyEvent.KEYCODE_2, CardSide.ANSWER),
|
||||
keyCode(KeyEvent.KEYCODE_NUMPAD_2, CardSide.ANSWER)
|
||||
)
|
||||
FLIP_OR_ANSWER_EASE3 -> from(
|
||||
keyCode(KeyEvent.KEYCODE_BUTTON_B, CardSide.BOTH),
|
||||
keyCode(KeyEvent.KEYCODE_3, CardSide.ANSWER), keyCode(KeyEvent.KEYCODE_NUMPAD_3, CardSide.ANSWER)
|
||||
keyCode(KeyEvent.KEYCODE_3, CardSide.ANSWER),
|
||||
keyCode(KeyEvent.KEYCODE_NUMPAD_3, CardSide.ANSWER)
|
||||
)
|
||||
FLIP_OR_ANSWER_EASE4 -> from(
|
||||
keyCode(KeyEvent.KEYCODE_BUTTON_A, CardSide.BOTH),
|
||||
keyCode(KeyEvent.KEYCODE_4, CardSide.ANSWER), keyCode(KeyEvent.KEYCODE_NUMPAD_4, CardSide.ANSWER)
|
||||
keyCode(KeyEvent.KEYCODE_4, CardSide.ANSWER),
|
||||
keyCode(KeyEvent.KEYCODE_NUMPAD_4, CardSide.ANSWER)
|
||||
)
|
||||
FLIP_OR_ANSWER_RECOMMENDED -> from(
|
||||
keyCode(KeyEvent.KEYCODE_DPAD_CENTER, CardSide.BOTH),
|
||||
|
@ -172,7 +172,6 @@ class CreateDeckDialog(private val context: Context, private val title: Int, pri
|
||||
if (deckName.isNotEmpty()) {
|
||||
when (deckDialogType) {
|
||||
DeckDialogType.DECK -> {
|
||||
|
||||
// create deck
|
||||
createDeck(deckName)
|
||||
}
|
||||
@ -180,12 +179,10 @@ class CreateDeckDialog(private val context: Context, private val title: Int, pri
|
||||
renameDeck(deckName)
|
||||
}
|
||||
DeckDialogType.SUB_DECK -> {
|
||||
|
||||
// create sub deck
|
||||
createSubDeck(parentId!!, deckName)
|
||||
}
|
||||
DeckDialogType.FILTERED_DECK -> {
|
||||
|
||||
// create filtered deck
|
||||
createFilteredDeck(deckName)
|
||||
}
|
||||
|
@ -64,7 +64,6 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
|
||||
}
|
||||
return when (type) {
|
||||
DIALOG_LOAD_FAILED -> {
|
||||
|
||||
// Collection failed to load; give user the option of either choosing from repair options, or closing
|
||||
// the activity
|
||||
dialog.show {
|
||||
@ -81,7 +80,6 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
|
||||
}
|
||||
}
|
||||
DIALOG_DB_ERROR -> {
|
||||
|
||||
// Database Check failed to execute successfully; give user the option of either choosing from repair
|
||||
// options, submitting an error report, or closing the activity
|
||||
dialog.show {
|
||||
@ -103,7 +101,6 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
|
||||
}
|
||||
}
|
||||
DIALOG_ERROR_HANDLING -> {
|
||||
|
||||
// The user has asked to see repair options; allow them to choose one of the repair options or go back
|
||||
// to the previous dialog
|
||||
val options = ArrayList<String>(6)
|
||||
@ -176,7 +173,6 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
|
||||
}
|
||||
}
|
||||
DIALOG_REPAIR_COLLECTION -> {
|
||||
|
||||
// Allow user to run BackupManager.repairCollection()
|
||||
dialog.show {
|
||||
contentNullable(message)
|
||||
@ -189,7 +185,6 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
|
||||
}
|
||||
}
|
||||
DIALOG_RESTORE_BACKUP -> {
|
||||
|
||||
// Allow user to restore one of the backups
|
||||
val path = CollectionHelper.getCollectionPath(requireContext())
|
||||
mBackups = BackupManager.getBackups(File(path))
|
||||
@ -244,7 +239,6 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
|
||||
dialog
|
||||
}
|
||||
DIALOG_NEW_COLLECTION -> {
|
||||
|
||||
// Allow user to create a new empty collection
|
||||
dialog.show {
|
||||
contentNullable(message)
|
||||
@ -263,7 +257,6 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
|
||||
}
|
||||
}
|
||||
DIALOG_CONFIRM_DATABASE_CHECK -> {
|
||||
|
||||
// Confirmation dialog for database check
|
||||
dialog.show {
|
||||
contentNullable(message)
|
||||
@ -275,7 +268,6 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
|
||||
}
|
||||
}
|
||||
DIALOG_CONFIRM_RESTORE_BACKUP -> {
|
||||
|
||||
// Confirmation dialog for backup restore
|
||||
dialog.show {
|
||||
contentNullable(message)
|
||||
@ -287,7 +279,6 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
|
||||
}
|
||||
}
|
||||
DIALOG_FULL_SYNC_FROM_SERVER -> {
|
||||
|
||||
// Allow user to do a full-sync from the server
|
||||
dialog.show {
|
||||
contentNullable(message)
|
||||
@ -299,7 +290,6 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
|
||||
}
|
||||
}
|
||||
DIALOG_DB_LOCKED -> {
|
||||
|
||||
// If the database is locked, all we can do is ask the user to exit.
|
||||
dialog.show {
|
||||
contentNullable(message)
|
||||
@ -444,8 +434,10 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
|
||||
|
||||
/** If the database is at a version higher than what we can currently handle */
|
||||
const val INCOMPATIBLE_DB_VERSION = 10
|
||||
|
||||
/** If the disk space is full **/
|
||||
const val DIALOG_DISK_FULL = 11
|
||||
|
||||
// public flag which lets us distinguish between inaccessible and corrupt database
|
||||
var databaseCorruptFlag = false
|
||||
|
||||
|
@ -180,7 +180,9 @@ class DeckPickerContextMenu(private val collection: Collection) : AnalyticsDialo
|
||||
val cls = loadFragmentClass(classLoader, className)
|
||||
return if (cls == DeckPickerContextMenu::class.java) {
|
||||
newDeckPickerContextMenu()
|
||||
} else super.instantiate(classLoader, className)
|
||||
} else {
|
||||
super.instantiate(classLoader, className)
|
||||
}
|
||||
}
|
||||
|
||||
private fun newDeckPickerContextMenu(): DeckPickerContextMenu =
|
||||
|
@ -344,11 +344,15 @@ open class DeckSelectionDialog : AnalyticsDialogFragment() {
|
||||
if (deckId == Stats.ALL_DECKS_ID) {
|
||||
return if (other.deckId == Stats.ALL_DECKS_ID) {
|
||||
0
|
||||
} else -1
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
return if (other.deckId == Stats.ALL_DECKS_ID) {
|
||||
1
|
||||
} else DeckNameComparator.INSTANCE.compare(name, other.name)
|
||||
} else {
|
||||
DeckNameComparator.INSTANCE.compare(name, other.name)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -42,7 +42,7 @@ class ExportCompleteDialog(private val listener: ExportCompleteDialogListener) :
|
||||
super.onCreate(savedInstanceState)
|
||||
val options = listOf(
|
||||
getString(R.string.export_select_save_app),
|
||||
getString(R.string.export_select_share_app),
|
||||
getString(R.string.export_select_share_app)
|
||||
)
|
||||
return MaterialDialog(requireActivity()).show {
|
||||
title(text = notificationTitle)
|
||||
|
@ -52,18 +52,26 @@ object HelpDialog {
|
||||
UsageAnalytics.sendAnalyticsEvent(UsageAnalytics.Category.LINK_CLICKED, UsageAnalytics.Actions.OPENED_HELPDIALOG)
|
||||
val allItems = arrayOf<RecursivePictureMenu.Item>(
|
||||
ItemHeader(
|
||||
R.string.help_title_using_ankidroid, R.drawable.ic_manual_black_24dp, UsageAnalytics.Actions.OPENED_USING_ANKIDROID,
|
||||
R.string.help_title_using_ankidroid,
|
||||
R.drawable.ic_manual_black_24dp,
|
||||
UsageAnalytics.Actions.OPENED_USING_ANKIDROID,
|
||||
FunctionItem(
|
||||
R.string.help_item_ankidroid_manual, R.drawable.ic_manual_black_24dp, UsageAnalytics.Actions.OPENED_ANKIDROID_MANUAL
|
||||
R.string.help_item_ankidroid_manual,
|
||||
R.drawable.ic_manual_black_24dp,
|
||||
UsageAnalytics.Actions.OPENED_ANKIDROID_MANUAL
|
||||
) { activity -> openManual(activity) },
|
||||
LinkItem(R.string.help_item_anki_manual, R.drawable.ic_manual_black_24dp, UsageAnalytics.Actions.OPENED_ANKI_MANUAL, R.string.link_anki_manual),
|
||||
LinkItem(R.string.help_item_ankidroid_faq, R.drawable.ic_help_black_24dp, UsageAnalytics.Actions.OPENED_ANKIDROID_FAQ, R.string.link_ankidroid_faq)
|
||||
),
|
||||
ItemHeader(
|
||||
R.string.help_title_get_help, R.drawable.ic_help_black_24dp, UsageAnalytics.Actions.OPENED_GET_HELP,
|
||||
R.string.help_title_get_help,
|
||||
R.drawable.ic_help_black_24dp,
|
||||
UsageAnalytics.Actions.OPENED_GET_HELP,
|
||||
LinkItem(R.string.help_item_mailing_list, R.drawable.ic_email_black_24dp, UsageAnalytics.Actions.OPENED_MAILING_LIST, R.string.link_forum),
|
||||
FunctionItem(
|
||||
R.string.help_item_report_bug, R.drawable.ic_bug_report_black_24dp, UsageAnalytics.Actions.OPENED_REPORT_BUG
|
||||
R.string.help_item_report_bug,
|
||||
R.drawable.ic_bug_report_black_24dp,
|
||||
UsageAnalytics.Actions.OPENED_REPORT_BUG
|
||||
) { activity -> openFeedback(activity) },
|
||||
exceptionReportItem
|
||||
),
|
||||
@ -77,7 +85,9 @@ object HelpDialog {
|
||||
LinkItem(R.string.help_item_twitter, R.drawable.twitter, UsageAnalytics.Actions.OPENED_TWITTER, R.string.link_twitter)
|
||||
),
|
||||
ItemHeader(
|
||||
R.string.help_title_privacy, R.drawable.ic_baseline_privacy_tip_24, UsageAnalytics.Actions.OPENED_PRIVACY,
|
||||
R.string.help_title_privacy,
|
||||
R.drawable.ic_baseline_privacy_tip_24,
|
||||
UsageAnalytics.Actions.OPENED_PRIVACY,
|
||||
LinkItem(R.string.help_item_ankidroid_privacy_policy, R.drawable.ic_baseline_policy_24, UsageAnalytics.Actions.OPENED_ANKIDROID_PRIVACY_POLICY, R.string.link_ankidroid_privacy_policy),
|
||||
LinkItem(R.string.help_item_ankiweb_privacy_policy, R.drawable.ic_baseline_policy_24, UsageAnalytics.Actions.OPENED_ANKIWEB_PRIVACY_POLICY, R.string.link_ankiweb_privacy_policy),
|
||||
LinkItem(R.string.help_item_ankiweb_terms_and_conditions, R.drawable.ic_baseline_description_24, UsageAnalytics.Actions.OPENED_ANKIWEB_TERMS_AND_CONDITIONS, R.string.link_ankiweb_terms_and_conditions)
|
||||
@ -96,7 +106,9 @@ object HelpDialog {
|
||||
rateAppItem,
|
||||
LinkItem(R.string.help_item_support_other_ankidroid, R.drawable.ic_help_black_24dp, UsageAnalytics.Actions.OPENED_OTHER, R.string.link_contribution),
|
||||
FunctionItem(
|
||||
R.string.send_feedback, R.drawable.ic_email_black_24dp, UsageAnalytics.Actions.OPENED_SEND_FEEDBACK
|
||||
R.string.send_feedback,
|
||||
R.drawable.ic_email_black_24dp,
|
||||
UsageAnalytics.Actions.OPENED_SEND_FEEDBACK
|
||||
) { activity -> openFeedback(activity) }
|
||||
)
|
||||
val itemList = ArrayList(listOf(*allItems))
|
||||
@ -234,7 +246,8 @@ object HelpDialog {
|
||||
val wasReportSent = CrashReportService.sendReport(activity)
|
||||
if (!wasReportSent) {
|
||||
showThemedToast(
|
||||
activity, activity.getString(R.string.help_dialog_exception_report_debounce),
|
||||
activity,
|
||||
activity.getString(R.string.help_dialog_exception_report_debounce),
|
||||
true
|
||||
)
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ class ImportFileSelectionFragment {
|
||||
R.drawable.ic_manual_black_24dp,
|
||||
UsageAnalytics.Actions.IMPORT_COLPKG_FILE,
|
||||
OpenFilePicker(DeckPicker.PICK_APKG_FILE)
|
||||
),
|
||||
)
|
||||
)
|
||||
if (!BackendFactory.defaultLegacySchema) {
|
||||
val mimes = arrayOf("text/plain", "text/comma-separated-values", "text/csv", "text/tab-separated-values")
|
||||
|
@ -31,7 +31,7 @@ object KeySelectionDialogUtils {
|
||||
KeyEvent.KEYCODE_ALT_RIGHT,
|
||||
KeyEvent.KEYCODE_META_LEFT,
|
||||
KeyEvent.KEYCODE_META_RIGHT,
|
||||
KeyEvent.KEYCODE_FUNCTION,
|
||||
KeyEvent.KEYCODE_FUNCTION
|
||||
)
|
||||
return { !modifierKeyCodes.contains(it) }
|
||||
}
|
||||
|
@ -124,7 +124,9 @@ class MediaCheckDialog : AsyncDialogFragment() {
|
||||
get() {
|
||||
return if (requireArguments().getInt("dialogType") == DIALOG_CONFIRM_MEDIA_CHECK) {
|
||||
resources.getString(R.string.check_media_title)
|
||||
} else resources.getString(R.string.app_name)
|
||||
} else {
|
||||
resources.getString(R.string.app_name)
|
||||
}
|
||||
}
|
||||
|
||||
override val dialogHandlerMessage: Message
|
||||
|
@ -40,5 +40,5 @@ class ModelBrowserContextMenu : AnalyticsDialogFragment() {
|
||||
enum class ModelBrowserContextMenuAction(val order: Int, @StringRes val actionTextResId: Int) {
|
||||
Template(0, R.string.model_browser_template),
|
||||
Rename(1, R.string.model_browser_rename),
|
||||
Delete(2, R.string.model_browser_delete),
|
||||
Delete(2, R.string.model_browser_delete)
|
||||
}
|
||||
|
@ -88,17 +88,19 @@ class RescheduleDialog : IntegerDialog() {
|
||||
)
|
||||
return if (currentCard.isInDynamicDeck) {
|
||||
message
|
||||
} else """
|
||||
} else {
|
||||
"""
|
||||
$message
|
||||
|
||||
${
|
||||
resources.getQuantityString(
|
||||
R.plurals.reschedule_card_dialog_interval,
|
||||
currentCard.ivl,
|
||||
currentCard.ivl
|
||||
)
|
||||
resources.getQuantityString(
|
||||
R.plurals.reschedule_card_dialog_interval,
|
||||
currentCard.ivl,
|
||||
currentCard.ivl
|
||||
)
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -168,7 +168,8 @@ fun openUrl(activity: AnkiActivity): OpenUri = activity::openUrl
|
||||
* @return the dialog
|
||||
*/
|
||||
fun AlertDialog.Builder.addScopedStorageLearnMoreLinkAndShow(@Language("HTML") message: String): AlertDialog {
|
||||
@Language("HTML") val messageWithLink = """$message
|
||||
@Language("HTML")
|
||||
val messageWithLink = """$message
|
||||
<br>
|
||||
<br><a href='${context.getString(R.string.link_scoped_storage_faq)}'>${context.getString(R.string.scoped_storage_learn_more)}</a>
|
||||
""".trimIndent().parseAsHtml()
|
||||
|
@ -61,8 +61,10 @@ class SimpleMessageDialog : AsyncDialogFragment() {
|
||||
companion object {
|
||||
/** The title of the notification/dialog */
|
||||
private const val ARGS_TITLE = "title"
|
||||
|
||||
/** The content of the notification/dialog */
|
||||
private const val ARGS_MESSAGE = "message"
|
||||
|
||||
/**
|
||||
* If the calling activity should be reloaded when 'OK' is pressed.
|
||||
* @see SimpleMessageDialogListener.dismissSimpleMessageDialog
|
||||
|
@ -48,7 +48,6 @@ class SyncErrorDialog : AsyncDialogFragment() {
|
||||
.cancelable(true)
|
||||
return when (requireArguments().getInt("dialogType")) {
|
||||
DIALOG_USER_NOT_LOGGED_IN_SYNC -> {
|
||||
|
||||
// User not logged in; take them to login screen
|
||||
dialog.show {
|
||||
iconAttr(R.attr.dialogSyncErrorIcon)
|
||||
@ -59,7 +58,6 @@ class SyncErrorDialog : AsyncDialogFragment() {
|
||||
}
|
||||
}
|
||||
DIALOG_CONNECTION_ERROR -> {
|
||||
|
||||
// Connection error; allow user to retry or cancel
|
||||
dialog.show {
|
||||
iconAttr(R.attr.dialogSyncErrorIcon)
|
||||
@ -73,7 +71,6 @@ class SyncErrorDialog : AsyncDialogFragment() {
|
||||
}
|
||||
}
|
||||
DIALOG_SYNC_CONFLICT_RESOLUTION -> {
|
||||
|
||||
// Sync conflict; allow user to cancel, or choose between local and remote versions
|
||||
dialog.show {
|
||||
iconAttr(R.attr.dialogSyncErrorIcon)
|
||||
@ -91,7 +88,6 @@ class SyncErrorDialog : AsyncDialogFragment() {
|
||||
}
|
||||
}
|
||||
DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_LOCAL -> {
|
||||
|
||||
// Confirmation before pushing local collection to server after sync conflict
|
||||
dialog.show {
|
||||
iconAttr(R.attr.dialogSyncErrorIcon)
|
||||
@ -104,7 +100,6 @@ class SyncErrorDialog : AsyncDialogFragment() {
|
||||
}
|
||||
}
|
||||
DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_REMOTE -> {
|
||||
|
||||
// Confirmation before overwriting local collection with server collection after sync conflict
|
||||
dialog.show {
|
||||
iconAttr(R.attr.dialogSyncErrorIcon)
|
||||
@ -117,7 +112,6 @@ class SyncErrorDialog : AsyncDialogFragment() {
|
||||
}
|
||||
}
|
||||
DIALOG_SYNC_SANITY_ERROR -> {
|
||||
|
||||
// Sync sanity check error; allow user to cancel, or choose between local and remote versions
|
||||
dialog.show {
|
||||
positiveButton(R.string.sync_sanity_local) {
|
||||
@ -132,7 +126,6 @@ class SyncErrorDialog : AsyncDialogFragment() {
|
||||
}
|
||||
}
|
||||
DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_LOCAL -> {
|
||||
|
||||
// Confirmation before pushing local collection to server after sanity check error
|
||||
dialog.show {
|
||||
positiveButton(R.string.dialog_positive_replace) {
|
||||
@ -143,7 +136,6 @@ class SyncErrorDialog : AsyncDialogFragment() {
|
||||
}
|
||||
}
|
||||
DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_REMOTE -> {
|
||||
|
||||
// Confirmation before overwriting local collection with server collection after sanity check error
|
||||
dialog.show {
|
||||
positiveButton(R.string.dialog_positive_replace) {
|
||||
@ -201,7 +193,9 @@ class SyncErrorDialog : AsyncDialogFragment() {
|
||||
get() {
|
||||
return if (requireArguments().getInt("dialogType") == DIALOG_USER_NOT_LOGGED_IN_SYNC) {
|
||||
resources.getString(R.string.sync_error)
|
||||
} else title
|
||||
} else {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
||||
private val message: String?
|
||||
@ -229,7 +223,9 @@ class SyncErrorDialog : AsyncDialogFragment() {
|
||||
get() {
|
||||
return if (requireArguments().getInt("dialogType") == DIALOG_USER_NOT_LOGGED_IN_SYNC) {
|
||||
resources.getString(R.string.not_logged_in_title)
|
||||
} else message
|
||||
} else {
|
||||
message
|
||||
}
|
||||
}
|
||||
|
||||
override val dialogHandlerMessage: Message
|
||||
|
@ -130,7 +130,6 @@ class CustomStudyDialog(private val collection: Collection, private val customSt
|
||||
customStudyListener?.showDialogFragment(d)
|
||||
}
|
||||
STUDY_TAGS -> {
|
||||
|
||||
/*
|
||||
* This is a special Dialog for CUSTOM STUDY, where instead of only collecting a
|
||||
* number, it is necessary to collect a list of tags. This case handles the creation
|
||||
@ -139,13 +138,13 @@ class CustomStudyDialog(private val collection: Collection, private val customSt
|
||||
val currentDeck = requireArguments().getLong("did")
|
||||
|
||||
val dialogFragment = TagsDialog().withArguments(
|
||||
TagsDialog.DialogType.CUSTOM_STUDY_TAGS, ArrayList(),
|
||||
TagsDialog.DialogType.CUSTOM_STUDY_TAGS,
|
||||
ArrayList(),
|
||||
ArrayList(collection.tags.byDeck(currentDeck, true))
|
||||
)
|
||||
customStudyListener?.showDialogFragment(dialogFragment)
|
||||
}
|
||||
else -> {
|
||||
|
||||
// User asked for a standard custom study option
|
||||
val d = CustomStudyDialog(collection, customStudyListener)
|
||||
.withArguments(
|
||||
@ -179,7 +178,8 @@ class CustomStudyDialog(private val collection: Collection, private val customSt
|
||||
*/
|
||||
// Input dialogs
|
||||
// Show input dialog for an individual custom study dialog
|
||||
@SuppressLint("InflateParams") val v = requireActivity().layoutInflater.inflate(R.layout.styled_custom_study_details_dialog, null)
|
||||
@SuppressLint("InflateParams")
|
||||
val v = requireActivity().layoutInflater.inflate(R.layout.styled_custom_study_details_dialog, null)
|
||||
val textView1 = v.findViewById<TextView>(R.id.custom_study_details_text1)
|
||||
val textView2 = v.findViewById<TextView>(R.id.custom_study_details_text2)
|
||||
val editText = v.findViewById<EditText>(R.id.custom_study_details_edittext2)
|
||||
@ -234,9 +234,11 @@ class CustomStudyDialog(private val collection: Collection, private val customSt
|
||||
arrayOf(
|
||||
String.format(
|
||||
Locale.US,
|
||||
"rated:%d:1", n
|
||||
"rated:%d:1",
|
||||
n
|
||||
),
|
||||
Consts.DYN_MAX_SIZE, Consts.DYN_RANDOM
|
||||
Consts.DYN_MAX_SIZE,
|
||||
Consts.DYN_RANDOM
|
||||
),
|
||||
false
|
||||
)
|
||||
@ -247,9 +249,11 @@ class CustomStudyDialog(private val collection: Collection, private val customSt
|
||||
arrayOf(
|
||||
String.format(
|
||||
Locale.US,
|
||||
"prop:due<=%d", n
|
||||
"prop:due<=%d",
|
||||
n
|
||||
),
|
||||
Consts.DYN_MAX_SIZE, Consts.DYN_DUE
|
||||
Consts.DYN_MAX_SIZE,
|
||||
Consts.DYN_DUE
|
||||
),
|
||||
true
|
||||
)
|
||||
@ -263,7 +267,8 @@ class CustomStudyDialog(private val collection: Collection, private val customSt
|
||||
arrayOf(
|
||||
"is:new added:" +
|
||||
n,
|
||||
Consts.DYN_MAX_SIZE, Consts.DYN_OLDEST
|
||||
Consts.DYN_MAX_SIZE,
|
||||
Consts.DYN_OLDEST
|
||||
),
|
||||
false
|
||||
)
|
||||
@ -333,7 +338,8 @@ class CustomStudyDialog(private val collection: Collection, private val customSt
|
||||
JSONArray(),
|
||||
arrayOf(
|
||||
sb.toString(),
|
||||
Consts.DYN_MAX_SIZE, Consts.DYN_RANDOM
|
||||
Consts.DYN_MAX_SIZE,
|
||||
Consts.DYN_RANDOM
|
||||
),
|
||||
true
|
||||
)
|
||||
@ -375,8 +381,12 @@ class CustomStudyDialog(private val collection: Collection, private val customSt
|
||||
}
|
||||
EMPTY_SCHEDULE -> // Special custom study options to show when extending the daily study limits is not applicable
|
||||
return listOf(
|
||||
STUDY_FORGOT, STUDY_AHEAD, STUDY_RANDOM,
|
||||
STUDY_PREVIEW, STUDY_TAGS, DECK_OPTIONS
|
||||
STUDY_FORGOT,
|
||||
STUDY_AHEAD,
|
||||
STUDY_RANDOM,
|
||||
STUDY_PREVIEW,
|
||||
STUDY_TAGS,
|
||||
DECK_OPTIONS
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -501,6 +511,7 @@ class CustomStudyDialog(private val collection: Collection, private val customSt
|
||||
*/
|
||||
interface ContextMenuAttribute<T> where T : Enum<*> {
|
||||
val value: Int
|
||||
|
||||
@get:StringRes val stringResource: Int?
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,9 @@ class CustomStudyDialogFactory(val collectionSupplier: Supplier<Collection>, pri
|
||||
val cls = loadFragmentClass(classLoader, className)
|
||||
return if (cls == CustomStudyDialog::class.java) {
|
||||
newCustomStudyDialog()
|
||||
} else super.instantiate(classLoader, className)
|
||||
} else {
|
||||
super.instantiate(classLoader, className)
|
||||
}
|
||||
}
|
||||
|
||||
fun newCustomStudyDialog(): CustomStudyDialog {
|
||||
|
@ -38,6 +38,7 @@ class TagsArrayAdapter(private val tags: TagsList, private val resources: Resour
|
||||
internal lateinit var node: TagTreeNode
|
||||
internal val mExpandButton: ImageButton = itemView.findViewById(R.id.id_expand_button)
|
||||
internal val mCheckBoxView: CheckBoxTriStates = itemView.findViewById(R.id.tags_dialog_tag_item_checkbox)
|
||||
|
||||
// TextView contains the displayed tag (only the last part), while the full tag is stored in TagTreeNode
|
||||
internal val mTextView: TextView = itemView.findViewById(R.id.tags_dialog_tag_item_text)
|
||||
|
||||
|
@ -136,7 +136,8 @@ class TagsDialog : AnalyticsDialogFragment {
|
||||
"filled as prefix properly. In other dialog types, long-clicking a tag behaves like a short click."
|
||||
)
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
@SuppressLint("InflateParams") val tagsDialogView = LayoutInflater.from(activity).inflate(R.layout.tags_dialog, null, false)
|
||||
@SuppressLint("InflateParams")
|
||||
val tagsDialogView = LayoutInflater.from(activity).inflate(R.layout.tags_dialog, null, false)
|
||||
mTagsListRecyclerView = tagsDialogView.findViewById(R.id.tags_dialog_tags_list)
|
||||
val tagsListRecyclerView: RecyclerView? = mTagsListRecyclerView
|
||||
tagsListRecyclerView?.requestFocus()
|
||||
@ -174,7 +175,8 @@ class TagsDialog : AnalyticsDialogFragment {
|
||||
.positiveButton(text = mPositiveText!!) {
|
||||
tagsDialogListener.onSelectedTags(
|
||||
mTags!!.copyOfCheckedTagList(),
|
||||
mTags!!.copyOfIndeterminateTagList(), mSelectedOption
|
||||
mTags!!.copyOfIndeterminateTagList(),
|
||||
mSelectedOption
|
||||
)
|
||||
}
|
||||
.negativeButton(R.string.dialog_cancel)
|
||||
|
@ -23,7 +23,9 @@ class TagsDialogFactory(val listener: TagsDialogListener) : ExtendedFragmentFact
|
||||
val cls = loadFragmentClass(classLoader, className)
|
||||
return if (cls == TagsDialog::class.java) {
|
||||
newTagsDialog()
|
||||
} else super.instantiate(classLoader, className)
|
||||
} else {
|
||||
super.instantiate(classLoader, className)
|
||||
}
|
||||
}
|
||||
|
||||
fun newTagsDialog(): TagsDialog {
|
||||
|
@ -37,7 +37,8 @@ interface TagsDialogListener {
|
||||
fun onSelectedTags(selectedTags: List<String>, indeterminateTags: List<String>, option: Int)
|
||||
fun <F> F.registerFragmentResultReceiver() where F : Fragment, F : TagsDialogListener {
|
||||
parentFragmentManager.setFragmentResultListener(
|
||||
ON_SELECTED_TAGS_KEY, this,
|
||||
ON_SELECTED_TAGS_KEY,
|
||||
this,
|
||||
FragmentResultListener { _: String?, bundle: Bundle ->
|
||||
val selectedTags: List<String> = bundle.getStringArrayList(ON_SELECTED_TAGS__SELECTED_TAGS)!!
|
||||
val indeterminateTags: List<String> = bundle.getStringArrayList(ON_SELECTED_TAGS__INDETERMINATE_TAGS)!!
|
||||
|
@ -129,6 +129,7 @@ class TagsList constructor(
|
||||
addAncestors(tag)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a tag as checked tag.
|
||||
* Optionally mark ancestors as indeterminate
|
||||
|
@ -202,7 +202,7 @@ class ActivityExportingDelegate(private val activity: AnkiActivity, private val
|
||||
activity.getString(
|
||||
R.string.export_email_text,
|
||||
activity.getString(R.string.link_manual),
|
||||
activity.getString(R.string.link_distributions),
|
||||
activity.getString(R.string.link_distributions)
|
||||
)
|
||||
)
|
||||
.intent.apply {
|
||||
@ -311,13 +311,15 @@ class ActivityExportingDelegate(private val activity: AnkiActivity, private val
|
||||
if (::mExportFileName.isInitialized && !mExportFileName.endsWith(".colpkg")) return
|
||||
AnkiDroidApp.getSharedPrefs(activity).edit {
|
||||
putLong(
|
||||
LAST_SUCCESSFUL_EXPORT_AT_SECOND_KEY, TimeManager.time.intTime()
|
||||
LAST_SUCCESSFUL_EXPORT_AT_SECOND_KEY,
|
||||
TimeManager.time.intTime()
|
||||
)
|
||||
}
|
||||
val col = collectionSupplier.get()
|
||||
AnkiDroidApp.getSharedPrefs(activity).edit {
|
||||
putLong(
|
||||
LAST_SUCCESSFUL_EXPORT_AT_MOD_KEY, col.mod
|
||||
LAST_SUCCESSFUL_EXPORT_AT_MOD_KEY,
|
||||
col.mod
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,9 @@ internal class ExportDialogsFactory(
|
||||
}
|
||||
return if (cls == ExportCompleteDialog::class.java) {
|
||||
newExportCompleteDialog()
|
||||
} else super.instantiate(classLoader, className)
|
||||
} else {
|
||||
super.instantiate(classLoader, className)
|
||||
}
|
||||
}
|
||||
|
||||
fun newExportDialog(): ExportDialog {
|
||||
|
@ -81,6 +81,7 @@ class SetupCollectionFragment : Fragment() {
|
||||
enum class CollectionSetupOption {
|
||||
/** Continues to the DeckPicker with a new collection */
|
||||
DeckPickerWithNewCollection,
|
||||
|
||||
/** Syncs an existing profile from AnkiWeb */
|
||||
SyncFromExistingAccount
|
||||
}
|
||||
|
@ -125,7 +125,6 @@ class TgzPackageExtract(private val context: Context) {
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
fun extractTarGzipToAddonFolder(tarballFile: File, addonsPackageDir: AddonsPackageDir) {
|
||||
|
||||
require(isGzip(tarballFile)) { context.getString(R.string.not_valid_js_addon, tarballFile.absolutePath) }
|
||||
|
||||
try {
|
||||
@ -285,7 +284,6 @@ class TgzPackageExtract(private val context: Context) {
|
||||
while (total + BUFFER <= TOO_BIG_SIZE &&
|
||||
tarInputStream.read(data, 0, BUFFER).also { count = it } != -1
|
||||
) {
|
||||
|
||||
bufferOutput.write(data, 0, count)
|
||||
total += count
|
||||
|
||||
|
@ -57,6 +57,7 @@ class Directory private constructor(val directory: File) {
|
||||
* Otherwise returns `null`.
|
||||
*/
|
||||
fun createInstance(path: String): Directory? = createInstance(File(path))
|
||||
|
||||
/**
|
||||
* Returns a [Directory] from [file] if `Directory` precondition holds; i.e. [file] is an existing directory.
|
||||
* Otherwise returns `null`.
|
||||
@ -67,6 +68,7 @@ class Directory private constructor(val directory: File) {
|
||||
}
|
||||
return Directory(file)
|
||||
}
|
||||
|
||||
/** Creates an instance. Only call it if [Directory] preconditions are known to be true */
|
||||
fun createInstanceUnsafe(file: File) = Directory(file)
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import java.io.File
|
||||
class DiskFile private constructor(val file: File) {
|
||||
/** @see [File.renameTo] */
|
||||
fun renameTo(destination: File): Boolean = file.renameTo(destination)
|
||||
|
||||
/** @see [FileUtils.contentEquals] */
|
||||
fun contentEquals(f2: File): Boolean = FileUtils.contentEquals(file, f2)
|
||||
override fun toString(): String = file.canonicalPath
|
||||
|
@ -352,7 +352,8 @@ class AudioView private constructor(context: Context, resPlay: Int, resPause: In
|
||||
CrashReportService.sendExceptionReport(e, "Unable to create recorder tool bar")
|
||||
showThemedToast(
|
||||
context,
|
||||
context.getText(R.string.multimedia_editor_audio_view_create_failed).toString(), true
|
||||
context.getText(R.string.multimedia_editor_audio_view_create_failed).toString(),
|
||||
true
|
||||
)
|
||||
null
|
||||
}
|
||||
|
@ -53,8 +53,9 @@ class MediaPlayer :
|
||||
|
||||
override fun setDataSource(context: Context, uri: Uri) {
|
||||
super.setDataSource(context, uri)
|
||||
if (state == IDLE)
|
||||
if (state == IDLE) {
|
||||
state = INITIALIZED
|
||||
}
|
||||
}
|
||||
|
||||
override fun setDataSource(
|
||||
@ -64,44 +65,51 @@ class MediaPlayer :
|
||||
cookies: MutableList<HttpCookie>?
|
||||
) {
|
||||
super.setDataSource(context, uri, headers, cookies)
|
||||
if (state == IDLE)
|
||||
if (state == IDLE) {
|
||||
state = INITIALIZED
|
||||
}
|
||||
}
|
||||
|
||||
override fun setDataSource(context: Context, uri: Uri, headers: MutableMap<String, String>?) {
|
||||
super.setDataSource(context, uri, headers)
|
||||
if (state == IDLE)
|
||||
if (state == IDLE) {
|
||||
state = INITIALIZED
|
||||
}
|
||||
}
|
||||
|
||||
override fun setDataSource(path: String?) {
|
||||
super.setDataSource(path)
|
||||
if (state == IDLE)
|
||||
if (state == IDLE) {
|
||||
state = INITIALIZED
|
||||
}
|
||||
}
|
||||
|
||||
override fun setDataSource(afd: AssetFileDescriptor) {
|
||||
super.setDataSource(afd)
|
||||
if (state == IDLE)
|
||||
if (state == IDLE) {
|
||||
state = INITIALIZED
|
||||
}
|
||||
}
|
||||
|
||||
override fun setDataSource(fd: FileDescriptor?) {
|
||||
super.setDataSource(fd)
|
||||
if (state == IDLE)
|
||||
if (state == IDLE) {
|
||||
state = INITIALIZED
|
||||
}
|
||||
}
|
||||
|
||||
override fun setDataSource(fd: FileDescriptor?, offset: Long, length: Long) {
|
||||
super.setDataSource(fd, offset, length)
|
||||
if (state == IDLE)
|
||||
if (state == IDLE) {
|
||||
state = INITIALIZED
|
||||
}
|
||||
}
|
||||
|
||||
override fun setDataSource(dataSource: MediaDataSource?) {
|
||||
super.setDataSource(dataSource)
|
||||
if (state == IDLE)
|
||||
if (state == IDLE) {
|
||||
state = INITIALIZED
|
||||
}
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
|
@ -103,7 +103,8 @@ open class LoadPronunciationActivity : AnkiActivity(), DialogInterface.OnCancelL
|
||||
mLanguageLister = LanguageListerBeolingus()
|
||||
mSpinnerFrom = Spinner(this)
|
||||
val adapter = ArrayAdapter(
|
||||
this, android.R.layout.simple_spinner_item,
|
||||
this,
|
||||
android.R.layout.simple_spinner_item,
|
||||
mLanguageLister.languages
|
||||
)
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
|
@ -102,7 +102,8 @@ class MultimediaEditFieldActivity : AnkiActivity(), OnRequestPermissionsResultCa
|
||||
if (field is AudioRecordingField && !Permissions.canRecordAudio(this)) {
|
||||
Timber.d("Requesting Audio Permissions")
|
||||
ActivityCompat.requestPermissions(
|
||||
this, arrayOf(Manifest.permission.RECORD_AUDIO),
|
||||
this,
|
||||
arrayOf(Manifest.permission.RECORD_AUDIO),
|
||||
REQUEST_AUDIO_PERMISSION
|
||||
)
|
||||
return true
|
||||
@ -114,7 +115,8 @@ class MultimediaEditFieldActivity : AnkiActivity(), OnRequestPermissionsResultCa
|
||||
) {
|
||||
Timber.d("Requesting Camera Permissions")
|
||||
ActivityCompat.requestPermissions(
|
||||
this, arrayOf(Manifest.permission.CAMERA),
|
||||
this,
|
||||
arrayOf(Manifest.permission.CAMERA),
|
||||
REQUEST_CAMERA_PERMISSION
|
||||
)
|
||||
return true
|
||||
|
@ -44,7 +44,8 @@ class PickStringDialogFragment : DialogFragment() {
|
||||
builder.setTitle(mTitle)
|
||||
val adapter = ArrayAdapter(
|
||||
requireActivity(),
|
||||
R.layout.simple_list_item_1, mPossibleChoices!!
|
||||
R.layout.simple_list_item_1,
|
||||
mPossibleChoices!!
|
||||
)
|
||||
builder.setAdapter(adapter, mListener)
|
||||
return builder.create()
|
||||
|
@ -96,6 +96,7 @@ class BasicImageFieldController : FieldControllerBase(), IFieldController {
|
||||
return min(height * 0.4, width * 0.6).toInt()
|
||||
}
|
||||
private lateinit var cropImageRequest: ActivityResultLauncher<CropImageContractOptions>
|
||||
|
||||
@VisibleForTesting
|
||||
lateinit var registryToUse: ActivityResultRegistry
|
||||
|
||||
|
@ -118,7 +118,8 @@ class BasicMediaClipFieldController : FieldControllerBase(), IFieldController {
|
||||
if (cursor == null) {
|
||||
showThemedToast(
|
||||
AnkiDroidApp.instance.applicationContext,
|
||||
AnkiDroidApp.instance.getString(R.string.multimedia_editor_something_wrong), true
|
||||
AnkiDroidApp.instance.getString(R.string.multimedia_editor_something_wrong),
|
||||
true
|
||||
)
|
||||
return
|
||||
}
|
||||
@ -137,7 +138,8 @@ class BasicMediaClipFieldController : FieldControllerBase(), IFieldController {
|
||||
CrashReportService.sendExceptionReport(e, "Media Clip addition failed. Name " + mediaClipFullName + " / cursor mime type column type " + cursor.getType(2))
|
||||
showThemedToast(
|
||||
AnkiDroidApp.instance.applicationContext,
|
||||
AnkiDroidApp.instance.getString(R.string.multimedia_editor_something_wrong), true
|
||||
AnkiDroidApp.instance.getString(R.string.multimedia_editor_something_wrong),
|
||||
true
|
||||
)
|
||||
return
|
||||
}
|
||||
@ -154,7 +156,8 @@ class BasicMediaClipFieldController : FieldControllerBase(), IFieldController {
|
||||
CrashReportService.sendExceptionReport(e, "handleMediaSelection:tempFile")
|
||||
showThemedToast(
|
||||
AnkiDroidApp.instance.applicationContext,
|
||||
AnkiDroidApp.instance.getString(R.string.multimedia_editor_something_wrong), true
|
||||
AnkiDroidApp.instance.getString(R.string.multimedia_editor_something_wrong),
|
||||
true
|
||||
)
|
||||
return
|
||||
}
|
||||
@ -175,7 +178,8 @@ class BasicMediaClipFieldController : FieldControllerBase(), IFieldController {
|
||||
CrashReportService.sendExceptionReport(e, "handleMediaSelection:copyFromProvider")
|
||||
showThemedToast(
|
||||
AnkiDroidApp.instance.applicationContext,
|
||||
AnkiDroidApp.instance.getString(R.string.multimedia_editor_something_wrong), true
|
||||
AnkiDroidApp.instance.getString(R.string.multimedia_editor_something_wrong),
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,7 @@ class ImageField : FieldBase(), IField {
|
||||
|
||||
companion object {
|
||||
private const val serialVersionUID = 4431611060655809687L
|
||||
|
||||
@VisibleForTesting
|
||||
fun formatImageFileName(file: File): String {
|
||||
return if (file.exists()) {
|
||||
|
@ -66,7 +66,9 @@ class MultimediaEditableNote : IMultimediaEditableNote {
|
||||
override fun getField(index: Int): IField? {
|
||||
return if (index in 0 until numberOfFields) {
|
||||
fieldsPrivate[index]
|
||||
} else null
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun setField(index: Int, field: IField?): Boolean {
|
||||
|
@ -41,7 +41,9 @@ open class LanguageListerBase {
|
||||
fun getCodeFor(Language: String): String? {
|
||||
return if (mLanguageMap.containsKey(Language)) {
|
||||
mLanguageMap[Language]
|
||||
} else null
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val languages: ArrayList<String>
|
||||
|
@ -64,6 +64,7 @@ class Toolbar : FrameLayout {
|
||||
var formatListener: TextFormatListener? = null
|
||||
private val mToolbar: LinearLayout
|
||||
private val mToolbarLayout: LinearLayout
|
||||
|
||||
/** A list of buttons, typically user-defined which modify text + selection */
|
||||
private val mCustomButtons: MutableList<View> = ArrayList()
|
||||
private val mRows: MutableList<LinearLayout> = ArrayList()
|
||||
|
@ -59,13 +59,13 @@ class ManageNotetypes : AnkiActivity() {
|
||||
launchForChanges<ModelFieldEditor>(
|
||||
mapOf(
|
||||
"title" to it.name,
|
||||
"noteTypeID" to it.id,
|
||||
"noteTypeID" to it.id
|
||||
)
|
||||
)
|
||||
},
|
||||
onEditCards = { launchForChanges<CardTemplateEditor>(mapOf("modelId" to it.id)) },
|
||||
onRename = ::renameNotetype,
|
||||
onDelete = ::deleteNotetype,
|
||||
onDelete = ::deleteNotetype
|
||||
)
|
||||
}
|
||||
private val outsideChangesLauncher =
|
||||
@ -227,7 +227,7 @@ class ManageNotetypes : AnkiActivity() {
|
||||
optionsToDisplay.map {
|
||||
String.format(
|
||||
if (it.isStandard) addPrefixStr else clonePrefixStr,
|
||||
it.name,
|
||||
it.name
|
||||
)
|
||||
}
|
||||
).apply {
|
||||
|
@ -46,7 +46,7 @@ internal class NotetypesAdapter(
|
||||
private val onShowFields: (NoteTypeUiModel) -> Unit,
|
||||
private val onEditCards: (NoteTypeUiModel) -> Unit,
|
||||
private val onRename: (NoteTypeUiModel) -> Unit,
|
||||
private val onDelete: (NoteTypeUiModel) -> Unit,
|
||||
private val onDelete: (NoteTypeUiModel) -> Unit
|
||||
) : ListAdapter<NoteTypeUiModel, NotetypeViewHolder>(notetypeNamesAndCountDiff) {
|
||||
private val layoutInflater = LayoutInflater.from(context)
|
||||
|
||||
@ -56,7 +56,7 @@ internal class NotetypesAdapter(
|
||||
onDelete = onDelete,
|
||||
onRename = onRename,
|
||||
onEditCards = onEditCards,
|
||||
onShowFields = onShowFields,
|
||||
onShowFields = onShowFields
|
||||
)
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ internal class NotetypeViewHolder(
|
||||
onShowFields: (NoteTypeUiModel) -> Unit,
|
||||
onEditCards: (NoteTypeUiModel) -> Unit,
|
||||
onRename: (NoteTypeUiModel) -> Unit,
|
||||
onDelete: (NoteTypeUiModel) -> Unit,
|
||||
onDelete: (NoteTypeUiModel) -> Unit
|
||||
) : RecyclerView.ViewHolder(rowView) {
|
||||
val name: TextView = rowView.findViewById(R.id.note_name)
|
||||
val useCount: TextView = rowView.findViewById(R.id.note_use_count)
|
||||
|
@ -25,7 +25,7 @@ import anki.notetypes.NotetypeNameIdUseCount
|
||||
internal data class NoteTypeUiModel(
|
||||
val id: Long,
|
||||
val name: String,
|
||||
val useCount: Int,
|
||||
val useCount: Int
|
||||
)
|
||||
|
||||
internal fun NotetypeNameIdUseCount.toUiModel(): NoteTypeUiModel =
|
||||
|
@ -84,6 +84,7 @@ class PagesActivity : AnkiActivity() {
|
||||
* as arguments of the [PageFragment] that will be opened
|
||||
*/
|
||||
const val EXTRA_PAGE_ARGS = "pageArgs"
|
||||
|
||||
/**
|
||||
* Extra key of [PagesActivity]'s intent that must be included and
|
||||
* hold the name of an [Anki HTML page](https://github.com/ankitects/anki/tree/main/ts)
|
||||
|
@ -31,7 +31,7 @@ class CustomSyncServerSettingsFragment : SettingsFragment() {
|
||||
|
||||
override fun initSubscreen() {
|
||||
listOf(
|
||||
R.string.custom_sync_server_collection_url_key,
|
||||
R.string.custom_sync_server_collection_url_key
|
||||
).forEach {
|
||||
requirePreference<VersatileTextPreference>(it).continuousValidator =
|
||||
VersatileTextPreference.Validator { value ->
|
||||
|
@ -53,8 +53,10 @@ class NotificationsSettingsFragment : SettingsFragment() {
|
||||
scheduleNotification(TimeManager.time, requireContext())
|
||||
} else {
|
||||
val intent = CompatHelper.compat.getImmutableBroadcastIntent(
|
||||
requireContext(), 0,
|
||||
Intent(requireContext(), NotificationService::class.java), 0
|
||||
requireContext(),
|
||||
0,
|
||||
Intent(requireContext(), NotificationService::class.java),
|
||||
0
|
||||
)
|
||||
val alarmManager = requireActivity().getSystemService(ALARM_SERVICE) as AlarmManager
|
||||
alarmManager.cancel(intent)
|
||||
|
@ -123,7 +123,8 @@ class Preferences :
|
||||
pref: Preference
|
||||
): Boolean {
|
||||
val fragment = supportFragmentManager.fragmentFactory.instantiate(
|
||||
classLoader, pref.fragment!!
|
||||
classLoader,
|
||||
pref.fragment!!
|
||||
)
|
||||
fragment.arguments = pref.extras
|
||||
supportFragmentManager.commit {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user