Skip to content

Commit 8c446f2

Browse files
authored
Merge branch 'ankidroid:main' into main
2 parents 4a796d5 + 9bac316 commit 8c446f2

151 files changed

Lines changed: 1257 additions & 254 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.idea/dictionaries/davidallison.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
<w>Notetypes</w>
1818
<w>Pictogrammers</w>
1919
<w>RTRIGGER</w>
20+
<w>SPDX</w>
21+
<w>Spdx</w>
2022
<w>SuperMemo</w>
2123
<w>Unbury</w>
2224
<w>acos</w>

AnkiDroid/build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,6 @@ dependencies {
418418
implementation libs.androidx.exifinterface
419419
implementation libs.androidx.fragment.ktx
420420
implementation libs.androidx.lifecycle.process
421-
implementation libs.androidx.media
422421
implementation libs.androidx.preference.ktx
423422
implementation libs.androidx.recyclerview
424423
implementation libs.androidx.sqlite.framework

AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import com.ichi2.anki.android.input.ShortcutGroup
6262
import com.ichi2.anki.android.input.ShortcutGroupProvider
6363
import com.ichi2.anki.android.input.shortcut
6464
import com.ichi2.anki.common.annotations.LegacyNotifications
65+
import com.ichi2.anki.common.annotations.NeedsTest
6566
import com.ichi2.anki.common.crashreporting.CrashReportService
6667
import com.ichi2.anki.common.utils.annotation.KotlinCleanup
6768
import com.ichi2.anki.compat.CompatHelper
@@ -71,6 +72,7 @@ import com.ichi2.anki.dialogs.DatabaseErrorDialog
7172
import com.ichi2.anki.dialogs.DatabaseErrorDialog.CustomExceptionData
7273
import com.ichi2.anki.dialogs.DatabaseErrorDialog.DatabaseErrorDialogType
7374
import com.ichi2.anki.dialogs.DialogHandler
75+
import com.ichi2.anki.dialogs.ExportReadyDialog.Companion.ARG_SHARE_AS_TEXT
7476
import com.ichi2.anki.dialogs.ExportReadyDialog.Companion.KEY_EXPORT_PATH
7577
import com.ichi2.anki.dialogs.ExportReadyDialog.Companion.REQUEST_EXPORT_SAVE
7678
import com.ichi2.anki.dialogs.ExportReadyDialog.Companion.REQUEST_EXPORT_SHARE
@@ -81,6 +83,7 @@ import com.ichi2.anki.preferences.sharedPrefs
8183
import com.ichi2.anki.receiver.SdCardReceiver
8284
import com.ichi2.anki.settings.Prefs
8385
import com.ichi2.anki.snackbar.showSnackbar
86+
import com.ichi2.anki.utils.ext.requireString
8487
import com.ichi2.anki.utils.ext.showDialogFragment
8588
import com.ichi2.anki.workarounds.AppLoadedFromBackupWorkaround.showedActivityFailedScreen
8689
import com.ichi2.compat.customtabs.CustomTabActivityHelper
@@ -152,7 +155,8 @@ open class AnkiActivity(
152155
}
153156
supportFragmentManager.setFragmentResultListener(REQUEST_EXPORT_SHARE, this) { _, bundle ->
154157
shareFile(
155-
bundle.getString(KEY_EXPORT_PATH) ?: error("Missing required exportPath!"),
158+
path = bundle.requireString(KEY_EXPORT_PATH),
159+
asText = bundle.getBoolean(ARG_SHARE_AS_TEXT, false),
156160
)
157161
}
158162
if (savedInstanceState != null) {
@@ -704,7 +708,11 @@ open class AnkiActivity(
704708
super.onSaveInstanceState(outState)
705709
}
706710

707-
private fun shareFile(path: String) {
711+
@NeedsTest("#20993 verify that the proper mime type is used for the share intent")
712+
private fun shareFile(
713+
path: String,
714+
asText: Boolean = false,
715+
) {
708716
// Make sure the file actually exists
709717
val attachment = File(path)
710718
if (!attachment.exists()) {
@@ -723,10 +731,12 @@ open class AnkiActivity(
723731
showThemedToast(this, resources.getString(R.string.apk_share_error), false)
724732
return
725733
}
734+
val targetMimeType = if (asText) "text/plain" else "application/apkg"
735+
726736
val sendIntent =
727737
ShareCompat
728738
.IntentBuilder(this)
729-
.setType("application/apkg")
739+
.setType(targetMimeType)
730740
.setStream(uri)
731741
.setSubject(getString(R.string.export_email_subject, attachment.name))
732742
.setHtmlText(

AnkiDroid/src/main/java/com/ichi2/anki/BackendExporting.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ fun AnkiActivity.exportSelectedNotes(
9090
)
9191
}
9292
}
93-
showAsyncDialogFragment(ExportReadyDialog.newInstance(exportPath))
93+
showAsyncDialogFragment(ExportReadyDialog.newInstance(exportPath, asText = true))
9494
}
9595
}
9696

@@ -110,7 +110,7 @@ fun AnkiActivity.exportSelectedCards(
110110
exportCardsCsv(exportPath, withHtml, limit)
111111
}
112112
}
113-
showAsyncDialogFragment(ExportReadyDialog.newInstance(exportPath))
113+
showAsyncDialogFragment(ExportReadyDialog.newInstance(exportPath, asText = true))
114114
}
115115
}
116116

AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler.kt

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,12 @@ class IntentHandler : AbstractIntentHandler() {
8888
when (launchType) {
8989
LaunchType.FILE_IMPORT ->
9090
runIfStoragePermissions {
91-
handleFileImport(fileIntent, reloadIntent, action)
91+
handleFileImport(intent, reloadIntent, action)
9292
finish()
9393
}
9494
LaunchType.TEXT_IMPORT ->
9595
runIfStoragePermissions {
96-
onSelectedCsvForImport(fileIntent)
96+
onSelectedCsvForImport(intent)
9797
finish()
9898
}
9999
LaunchType.IMAGE_IMPORT ->
@@ -128,15 +128,6 @@ class IntentHandler : AbstractIntentHandler() {
128128
)
129129
}
130130

131-
private val fileIntent: Intent
132-
get() {
133-
return if (intent.action == Intent.ACTION_SEND) {
134-
IntentCompat.getParcelableExtra(intent, Intent.EXTRA_STREAM, Intent::class.java) ?: intent
135-
} else {
136-
intent
137-
}
138-
}
139-
140131
/**
141132
* Execute the runnable if one of the two following conditions are satisfied:
142133
*

AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserFragment.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,8 @@ class CardBrowserFragment :
416416
Timber.d("onCreateMenu()")
417417
menuInflater.inflate(R.menu.card_browser, menu)
418418
menu.findItem(R.id.action_search_by_flag).subMenu?.setupFlags()
419+
// note: this menu item is available with and without a selection of items
420+
menu.findItem(R.id.action_find_replace)?.title = TR.sentenceCase.findAndReplace
419421

420422
if (!useSearchView) {
421423
searchItem = menu.findItem(R.id.action_search)
@@ -562,6 +564,10 @@ class CardBrowserFragment :
562564
showFilteredDeckScreen()
563565
return true
564566
}
567+
R.id.action_find_replace -> {
568+
showFindAndReplaceDialog()
569+
return true
570+
}
565571
}
566572

567573
return false
@@ -594,11 +600,8 @@ class CardBrowserFragment :
594600
menu.findItem(R.id.action_reschedule_cards).title = TR.sentenceCase.setDueDate
595601
menu.findItem(R.id.action_grade_now).title = TR.sentenceCase.gradeNow
596602

597-
val isFindReplaceEnabled = sharedPrefs().getBoolean(getString(R.string.pref_browser_find_replace), false)
598-
menu.findItem(R.id.action_find_replace).apply {
599-
isVisible = isFindReplaceEnabled
600-
title = TR.browsingFindAndReplace().toSentenceCase(R.string.sentence_find_and_replace)
601-
}
603+
// note: this menu item is available with and without a selection of items
604+
menu.findItem(R.id.action_find_replace)?.title = TR.sentenceCase.findAndReplace
602605

603606
menu.findItem(R.id.action_undo).setupUndo()
604607

AnkiDroid/src/main/java/com/ichi2/anki/browser/FindAndReplaceDialogFragment.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ class FindAndReplaceDialogFragment : AnalyticsDialogFragment() {
9999
}
100100
}
101101

102+
// TODO maybe get rid of the html tags that come with the backend strings and handle the
103+
// sentence case for TR.browsingReplaceWith()
102104
private fun setupLabels() {
103105
binding.labelFind.text =
104106
HtmlCompat.fromHtml(TR.browsingFind(), HtmlCompat.FROM_HTML_MODE_LEGACY)
@@ -218,7 +220,10 @@ class FindAndReplaceDialogFragment : AnalyticsDialogFragment() {
218220
): FindAndReplaceDialogFragment {
219221
val file = IdsFile(context.cacheDir, noteIds, "find-replace")
220222
return FindAndReplaceDialogFragment().apply {
221-
arguments = bundleOf(ARG_IDS to file)
223+
arguments =
224+
Bundle().apply {
225+
putParcelable(ARG_IDS, file)
226+
}
222227
}
223228
}
224229
}

AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/SoundTagPlayer.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,18 @@
1717
package com.ichi2.anki.cardviewer
1818

1919
import android.content.Context
20-
import android.media.AudioAttributes
2120
import android.media.AudioManager
2221
import android.media.MediaPlayer
2322
import android.net.Uri
2423
import androidx.annotation.CheckResult
24+
import androidx.annotation.OptIn
2525
import androidx.annotation.VisibleForTesting
2626
import androidx.core.net.toUri
27-
import androidx.media.AudioFocusRequestCompat
28-
import androidx.media.AudioManagerCompat
27+
import androidx.media3.common.AudioAttributes
28+
import androidx.media3.common.C.AUDIO_CONTENT_TYPE_MUSIC
29+
import androidx.media3.common.audio.AudioFocusRequestCompat
30+
import androidx.media3.common.audio.AudioManagerCompat
31+
import androidx.media3.common.util.UnstableApi
2932
import com.ichi2.anki.AnkiDroidApp
3033
import com.ichi2.anki.CollectionManager.withCol
3134
import com.ichi2.anki.common.annotations.NeedsTest
@@ -39,6 +42,7 @@ import kotlin.coroutines.resume
3942
import kotlin.coroutines.resumeWithException
4043

4144
/** Player for (`[sound:...]`): [SoundOrVideoTag] */
45+
@OptIn(UnstableApi::class)
4246
@NeedsTest("CardSoundConfig.autoplay should mean that video also isn't played automatically")
4347
class SoundTagPlayer(
4448
private val soundUriBase: String,
@@ -49,7 +53,7 @@ class SoundTagPlayer(
4953
private val music =
5054
AudioAttributes
5155
.Builder()
52-
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
56+
.setContentType(AUDIO_CONTENT_TYPE_MUSIC)
5357
.build()
5458

5559
/**
@@ -118,7 +122,7 @@ class SoundTagPlayer(
118122
} else {
119123
(soundUriBase + Uri.encode(tag.filename)).toUri()
120124
}
121-
setAudioAttributes(music)
125+
setAudioAttributes(music.platformAudioAttributes)
122126
setOnErrorListener { mp, what, extra ->
123127
Timber.w("Media error %d", what)
124128
abandonAudioFocus()

AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ExportReadyDialog.kt

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import com.ichi2.utils.positiveButton
2929
class ExportReadyDialog : AsyncDialogFragment() {
3030
private val exportPath
3131
get() = requireArguments().getString(KEY_EXPORT_PATH) ?: error("Missing required argument: exportPath!")
32+
private val asText: Boolean
33+
get() = requireArguments().getBoolean(ARG_SHARE_AS_TEXT, false)
3234

3335
override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog {
3436
val dialog = AlertDialog.Builder(requireActivity())
@@ -43,7 +45,10 @@ class ExportReadyDialog : AsyncDialogFragment() {
4345
}.negativeButton(R.string.export_choice_share) {
4446
parentFragmentManager.setFragmentResult(
4547
REQUEST_EXPORT_SHARE,
46-
bundleOf(KEY_EXPORT_PATH to exportPath),
48+
Bundle().apply {
49+
putString(KEY_EXPORT_PATH, exportPath)
50+
putBoolean(ARG_SHARE_AS_TEXT, asText)
51+
},
4752
)
4853
}
4954

@@ -90,10 +95,17 @@ class ExportReadyDialog : AsyncDialogFragment() {
9095
const val REQUEST_EXPORT_SAVE = "request_export_save"
9196
const val REQUEST_EXPORT_SHARE = "request_export_share"
9297
const val KEY_EXPORT_PATH = "key_export_path"
98+
const val ARG_SHARE_AS_TEXT = "arg_share_as_text"
9399

94-
fun newInstance(exportPath: String) =
95-
ExportReadyDialog().apply {
96-
arguments = bundleOf(KEY_EXPORT_PATH to exportPath)
97-
}
100+
fun newInstance(
101+
exportPath: String,
102+
asText: Boolean = false,
103+
) = ExportReadyDialog().apply {
104+
arguments =
105+
Bundle().apply {
106+
putString(KEY_EXPORT_PATH, exportPath)
107+
putBoolean(ARG_SHARE_AS_TEXT, asText)
108+
}
109+
}
98110
}
99111
}

AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/PreferenceUpgradeService.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ object PreferenceUpgradeService {
137137
yield(UpgradeToggleBacksideOnlyControl())
138138
yield(UpgradeThemes())
139139
yield(UpgradeAnswerControls())
140+
yield(RemoveDeveloperFindReplace())
140141
}
141142

142143
/** Returns a list of preference upgrade classes which have not been applied */
@@ -900,6 +901,14 @@ object PreferenceUpgradeService {
900901
}
901902
}
902903
}
904+
905+
internal class RemoveDeveloperFindReplace : PreferenceUpgrade(28) {
906+
override fun upgrade(preferences: SharedPreferences) {
907+
preferences.edit {
908+
remove("browserFindReplace")
909+
}
910+
}
911+
}
903912
}
904913
}
905914

0 commit comments

Comments
 (0)