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"))