diff --git a/AnkiDroid/src/main/AndroidManifest.xml b/AnkiDroid/src/main/AndroidManifest.xml
index ccd5e56ca826..571e354ac94d 100644
--- a/AnkiDroid/src/main/AndroidManifest.xml
+++ b/AnkiDroid/src/main/AndroidManifest.xml
@@ -535,6 +535,11 @@
/>
+
+
+
+package com.ichi2.anki.receiver
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import androidx.core.app.NotificationManagerCompat
+import com.ichi2.anki.R
+import com.ichi2.anki.common.utils.android.showThemedToast
+import com.ichi2.anki.notifications.NotificationId
+import com.ichi2.utils.copyToClipboard
+import timber.log.Timber
+
+class CopyToClipboardReceiver : BroadcastReceiver() {
+ override fun onReceive(
+ context: Context,
+ intent: Intent,
+ ) {
+ val text =
+ intent.getStringExtra(EXTRA_SYNC_ERROR_LOG) ?: run {
+ Timber.w("CopyToClipboardReceiver: no error log found")
+ showThemedToast(context, R.string.something_wrong, shortLength = true)
+ return
+ }
+ NotificationManagerCompat.from(context).cancel(NotificationId.SYNC_MEDIA)
+ context.copyToClipboard(text)
+ }
+
+ companion object {
+ const val EXTRA_SYNC_ERROR_LOG = "COPY ERROR"
+ }
+}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/internationalization/SentenceCase.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/internationalization/SentenceCase.kt
index 6320eac75c4c..6d07a2d0dc83 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/internationalization/SentenceCase.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/internationalization/SentenceCase.kt
@@ -134,6 +134,9 @@ object SentenceCase {
context(_: Fragment)
val toggleSuspend get() = TR.browsingToggleSuspend().toSentenceCase(R.string.sentence_toggle_suspend)
+ context(_: Context)
+ val copyToClipboard get() = TR.qtMiscCopyToClipboard().toSentenceCase(R.string.sentence_copy_to_clipboard)
+
context(_: Fragment)
val findAndReplace get() = TR.browsingFindAndReplace().toSentenceCase(R.string.sentence_find_and_replace)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/worker/SyncMediaWorker.kt b/AnkiDroid/src/main/java/com/ichi2/anki/worker/SyncMediaWorker.kt
index 6f99a1c98199..d7fd7898373d 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/worker/SyncMediaWorker.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/worker/SyncMediaWorker.kt
@@ -16,7 +16,9 @@
package com.ichi2.anki.worker
import android.app.Notification
+import android.app.PendingIntent
import android.content.Context
+import android.content.Intent
import android.content.pm.ServiceInfo
import android.os.Build
import androidx.core.app.NotificationCompat
@@ -37,9 +39,12 @@ import anki.sync.SyncAuth
import anki.sync.syncAuth
import com.ichi2.anki.Channel
import com.ichi2.anki.CollectionManager
+import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.R
import com.ichi2.anki.cancelMediaSync
import com.ichi2.anki.notifications.NotificationId
+import com.ichi2.anki.receiver.CopyToClipboardReceiver
+import com.ichi2.anki.ui.internationalization.sentenceCase
import com.ichi2.anki.utils.ext.trySetForeground
import com.ichi2.utils.Permissions
import kotlinx.coroutines.CancellationException
@@ -91,9 +96,19 @@ class SyncMediaWorker(
} catch (throwable: Throwable) {
Timber.w(throwable, "SyncMediaWorker failed")
notify {
- setContentTitle(CollectionManager.TR.syncMediaFailed())
+ setContentTitle(TR.syncMediaFailed())
throwable.localizedMessage?.let { message ->
setContentText(message)
+ setStyle(
+ NotificationCompat
+ .BigTextStyle()
+ .bigText(message),
+ )
+ addAction(
+ R.drawable.baseline_content_copy_24,
+ with(applicationContext) { TR.sentenceCase.copyToClipboard },
+ getCopyToClipboardIntent(message),
+ )
}
}
Timber.d("SyncMediaWorker: showing failure notification")
@@ -129,7 +144,7 @@ class SyncMediaWorker(
override suspend fun getForegroundInfo(): ForegroundInfo {
val title = applicationContext.getString(R.string.syncing_media)
- val cancelTitle = CollectionManager.TR.syncAbortButton()
+ val cancelTitle = TR.syncAbortButton()
val notification =
buildNotification {
setContentTitle(title)
@@ -145,6 +160,19 @@ class SyncMediaWorker(
}
}
+ private fun getCopyToClipboardIntent(text: String): PendingIntent {
+ val intent =
+ Intent(applicationContext, CopyToClipboardReceiver::class.java).apply {
+ putExtra(CopyToClipboardReceiver.EXTRA_SYNC_ERROR_LOG, text)
+ }
+ return PendingIntent.getBroadcast(
+ applicationContext,
+ 0,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT,
+ )
+ }
+
private fun notify(notification: Notification) = notificationManager?.notify(NotificationId.SYNC_MEDIA, notification)
private fun notify(builder: NotificationCompat.Builder.() -> Unit) {
@@ -164,7 +192,7 @@ class SyncMediaWorker(
private fun getProgressNotification(progress: CharSequence): Notification {
val title = applicationContext.getString(R.string.syncing_media)
- val cancelTitle = CollectionManager.TR.syncAbortButton()
+ val cancelTitle = TR.syncAbortButton()
return buildNotification {
setContentTitle(title)
diff --git a/AnkiDroid/src/main/res/values/sentence-case.xml b/AnkiDroid/src/main/res/values/sentence-case.xml
index 421c9083366d..9ea928c8c450 100644
--- a/AnkiDroid/src/main/res/values/sentence-case.xml
+++ b/AnkiDroid/src/main/res/values/sentence-case.xml
@@ -85,4 +85,5 @@ undoActionUndone()
Change deck
Toggle mark
Create deck
+ Copy to clipboard
\ No newline at end of file
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/ui/internationalization/SentenceCaseTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/ui/internationalization/SentenceCaseTest.kt
index 1c1817dc4508..45c1843e4a8a 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/ui/internationalization/SentenceCaseTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/ui/internationalization/SentenceCaseTest.kt
@@ -68,6 +68,7 @@ class SentenceCaseTest : RobolectricTest() {
assertThat(TR.sentenceCase.checkDatabase, equalTo("Check database"))
assertThat(TR.sentenceCase.checkMediaTitle, equalTo("Check media"))
assertThat(TR.sentenceCase.checkMediaAction, equalTo("Check media"))
+ assertThat(TR.sentenceCase.copyToClipboard, equalTo("Copy to clipboard"))
assertThat(TR.sentenceCase.frontTemplate, equalTo("Front template"))
assertThat(TR.sentenceCase.backTemplate, equalTo("Back template"))
assertThat(TR.sentenceCase.renameDeck, equalTo("Rename deck"))