Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions AnkiDroid/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,11 @@
/>
</receiver>

<!-- Copy Sync Error Message to Clipboard -->
<receiver
android:name="com.ichi2.anki.receiver.CopyToClipboardReceiver"
android:exported="false" />

<!-- "Add Note" widget -->
<receiver
android:name="com.ichi2.widget.AddNoteWidget"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright (c) 2026 LUwUcifer <luwucifwer@proton.me>
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")
Comment thread
david-allison marked this conversation as resolved.
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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
34 changes: 31 additions & 3 deletions AnkiDroid/src/main/java/com/ichi2/anki/worker/SyncMediaWorker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why Application context over context?

getCopyToClipboardIntent(message),
)
}
}
Timber.d("SyncMediaWorker: showing failure notification")
Expand Down Expand Up @@ -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)
Expand All @@ -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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a limit to text, about 1MB, it needs to be checked and trimmed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would that issue ever arise given error messages are exclusively provided from the backend?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlikely, most likely from a StackOverflowError

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this was handled

}
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) {
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions AnkiDroid/src/main/res/values/sentence-case.xml
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,5 @@ undoActionUndone()
<string name="sentence_change_deck" maxLength="28">Change deck</string>
<string name="sentence_toggle_mark">Toggle mark</string>
<string name="sentence_create_deck">Create deck</string>
<string name="sentence_copy_to_clipboard">Copy to clipboard</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
Loading