From d240c16c11e7a38826a52d993a24d06a2ca71486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Hosta=C4=8Dn=C3=BD?= Date: Wed, 8 Jul 2026 23:52:21 +0200 Subject: [PATCH 1/4] Fixing notifications content, making them clickable - leading to Logs page --- .gitignore | 3 + .../Activities/MainActivity.java | 10 ++ .../notifications/ReportNotifications.kt | 21 +++- .../notifications/SyncServiceNotifications.kt | 20 ++++ .../notifications/support/StatusObject.kt | 46 ++++++++- .../workmanager/SyncResultFormatter.kt | 61 ++++++++++++ .../rcloneexplorer/workmanager/SyncWorker.kt | 96 +++++++++++++------ app/src/main/res/values/strings.xml | 6 ++ 8 files changed, 229 insertions(+), 34 deletions(-) create mode 100644 app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncResultFormatter.kt diff --git a/.gitignore b/.gitignore index 8b4e176b3..76567067c 100644 --- a/.gitignore +++ b/.gitignore @@ -70,3 +70,6 @@ debug/ .idea/codeStyles/codeStyleConfig.xml .idea/inspectionProfiles/Project_Default.xml .idea/ + +# exported app backup from Android studio +**/application.backup diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Activities/MainActivity.java b/app/src/main/java/ca/pkay/rcloneexplorer/Activities/MainActivity.java index 981f26cf3..7ea902fba 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/Activities/MainActivity.java +++ b/app/src/main/java/ca/pkay/rcloneexplorer/Activities/MainActivity.java @@ -229,6 +229,16 @@ protected void onResume() { } } + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + setIntent(intent); + if(MAIN_ACTIVITY_START_LOG.equals(intent.getAction())){ + startLogFragment(); + navigationView.setCheckedItem(R.id.nav_logs); + } + } + @Override protected void onPostCreate(@Nullable Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/notifications/ReportNotifications.kt b/app/src/main/java/ca/pkay/rcloneexplorer/notifications/ReportNotifications.kt index c70984915..0a0678382 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/notifications/ReportNotifications.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/notifications/ReportNotifications.kt @@ -2,6 +2,7 @@ package ca.pkay.rcloneexplorer.notifications import android.app.PendingIntent import android.app.PendingIntent.FLAG_IMMUTABLE +import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.content.Context import android.content.Intent import androidx.core.app.NotificationCompat @@ -12,6 +13,7 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore +import ca.pkay.rcloneexplorer.Activities.MainActivity import ca.pkay.rcloneexplorer.BroadcastReceivers.ClearReportBroadcastReciever import ca.pkay.rcloneexplorer.R import ca.pkay.rcloneexplorer.util.NotificationUtils @@ -87,9 +89,11 @@ class ReportNotifications(var mContext: Context) { .setSmallIcon(R.drawable.ic_twotone_cloud_done_24) .setContentTitle(mContext.getString(R.string.operation_report_success_title)) .setContentText(mContext.getString(R.string.operation_report_success_short_content, notificationContent.lines().size-1)) + .setContentIntent(createLogsPendingIntent()) + .setAutoCancel(true) .setStyle( NotificationCompat.BigTextStyle().bigText( - notificationContent + content ) ) .setPriority(NotificationCompat.PRIORITY_LOW) @@ -142,9 +146,11 @@ class ReportNotifications(var mContext: Context) { .setSmallIcon(R.drawable.ic_twotone_cloud_error_24) .setContentTitle(mContext.getString(R.string.operation_report_fail_title)) .setContentText(mContext.getString(R.string.operation_report_fail_short_content, notificationContent.lines().size-1)) + .setContentIntent(createLogsPendingIntent()) + .setAutoCancel(true) .setStyle( NotificationCompat.BigTextStyle().bigText( - notificationContent + "$title: $line\n" ) ) .setPriority(NotificationCompat.PRIORITY_LOW) @@ -167,6 +173,17 @@ class ReportNotifications(var mContext: Context) { ) } + private fun createLogsPendingIntent(): PendingIntent { + val intent = Intent(mContext, MainActivity::class.java) + intent.action = MainActivity.MAIN_ACTIVITY_START_LOG + return PendingIntent.getActivity( + mContext, + 0, + intent, + FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE + ) + } + fun getFailures(): Int { val prefMap = runBlocking { mContext.dataStore.data.first().asMap() } val notificationContent = prefMap[NOTIFICATION_CACHE_FAIL_PREFERENCE].toString() diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/notifications/SyncServiceNotifications.kt b/app/src/main/java/ca/pkay/rcloneexplorer/notifications/SyncServiceNotifications.kt index dbfbc61ea..a3f7820b4 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/notifications/SyncServiceNotifications.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/notifications/SyncServiceNotifications.kt @@ -8,6 +8,7 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.preference.PreferenceManager import androidx.work.WorkManager +import ca.pkay.rcloneexplorer.Activities.MainActivity import ca.pkay.rcloneexplorer.BroadcastReceivers.SyncRestartAction import ca.pkay.rcloneexplorer.R import ca.pkay.rcloneexplorer.util.FLog @@ -29,6 +30,7 @@ class SyncServiceNotifications(var mContext: Context) { const val PERSISTENT_NOTIFICATION_ID_FOR_SYNC = 162 const val CANCEL_ID_NOTSET = "CANCEL_ID_NOTSET" const val TAG = "SyncServiceNotifications" + private const val LOGS_PENDING_INTENT_REQUEST = 163 } @@ -83,6 +85,8 @@ class SyncServiceNotifications(var mContext: Context) { .setSmallIcon(R.drawable.ic_twotone_cloud_error_24) .setContentTitle(mContext.getString(R.string.operation_failed)) .setContentText(content) + .setContentIntent(createLogsPendingIntent()) + .setAutoCancel(true) .setStyle( NotificationCompat.BigTextStyle().bigText(content) ) @@ -128,6 +132,8 @@ class SyncServiceNotifications(var mContext: Context) { .setSmallIcon(R.drawable.ic_twotone_cloud_error_24) .setContentTitle(mContext.getString(R.string.operation_failed_cancelled)) .setContentText(content) + .setContentIntent(createLogsPendingIntent()) + .setAutoCancel(true) .setStyle( NotificationCompat.BigTextStyle().bigText(content) ) @@ -166,6 +172,8 @@ class SyncServiceNotifications(var mContext: Context) { .setSmallIcon(R.drawable.ic_twotone_cloud_done_24) .setContentTitle(mContext.getString(R.string.operation_success, title)) .setContentText(content) + .setContentIntent(createLogsPendingIntent()) + .setAutoCancel(true) .setStyle( NotificationCompat.BigTextStyle().bigText( content @@ -227,6 +235,7 @@ class SyncServiceNotifications(var mContext: Context) { intent ) } + builder.setContentIntent(createLogsPendingIntent()) return builder.build() } @@ -235,4 +244,15 @@ class SyncServiceNotifications(var mContext: Context) { val notificationManagerCompat = NotificationManagerCompat.from(mContext) notificationManagerCompat.cancel(notificationId) } + + private fun createLogsPendingIntent(): PendingIntent { + val intent = Intent(mContext, MainActivity::class.java) + intent.action = MainActivity.MAIN_ACTIVITY_START_LOG + return PendingIntent.getActivity( + mContext, + LOGS_PENDING_INTENT_REQUEST, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + } } diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/notifications/support/StatusObject.kt b/app/src/main/java/ca/pkay/rcloneexplorer/notifications/support/StatusObject.kt index 9b59eec45..e5ea0431a 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/notifications/support/StatusObject.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/notifications/support/StatusObject.kt @@ -16,10 +16,16 @@ class StatusObject(var mContext: Context){ var mErrorList = ArrayList() var mStats = JSONObject() var mLogline = JSONObject() + private var retryAttemptErrorCount = 0 var estimatedAverageSpeed = 0L var lastItemAverageSpeed = 0L + companion object { + private val retryAttemptPattern = + Regex("""^Attempt (\d+)/(\d+) failed with (\d+) errors?.*""") + } + fun getSpeed(): String { return Formatter.formatFileSize(mContext, mStats.optLong("speed", 0)) + "/s" } @@ -63,6 +69,22 @@ class StatusObject(var mContext: Context){ return mStats.optInt("deletes", 0) + mStats.optInt("deletedDirs", 0) } + fun getRenames(): Int { + return mStats.optInt("renames", 0) + } + + fun getErrorCount(): Int { + return when { + retryAttemptErrorCount > 0 -> retryAttemptErrorCount + mErrorList.isNotEmpty() -> mErrorList.size + else -> mStats.optInt("errors", 0) + } + } + + fun hasErrors(): Boolean { + return getErrorCount() > 0 + } + fun getErrorMessage(): String { if(mLogline.has("msg") && mLogline.getString("level") == "error") { return mLogline.getString("msg") @@ -82,9 +104,23 @@ class StatusObject(var mContext: Context){ clearObject() mLogline = logLine - var error = ErrorObject(getErrorObject(), getErrorMessage()) + val retryAttempt = retryAttemptPattern.matchEntire(getErrorMessage()) + if (retryAttempt != null) { + val attempt = retryAttempt.groupValues[1].toInt() + val attempts = retryAttempt.groupValues[2].toInt() + retryAttemptErrorCount = retryAttempt.groupValues[3].toInt() + if (attempt < attempts) { + mErrorList.clear() + retryAttemptErrorCount = 0 + } + return + } + + val error = ErrorObject(getErrorObject(), getErrorMessage()) Log.e(TAG, error.mErrorObject + " - " + error.mErrorMessage) - mErrorList.add(error) + if (mErrorList.none { it.mErrorObject == error.mErrorObject && it.mErrorMessage == error.mErrorMessage }) { + mErrorList.add(error) + } } if(logLine.has("stats")) { @@ -231,7 +267,9 @@ class StatusObject(var mContext: Context){ var all = "" mErrorList.forEach { all += it.mErrorMessage + "\n" - all += mContext.getString(R.string.status_offendingfile) + it.mErrorObject + "\n" + if (it.mErrorObject.isNotEmpty() && !it.mErrorMessage.startsWith("not deleting ")) { + all += mContext.getString(R.string.status_offendingfile) + it.mErrorObject + "\n" + } } return all } @@ -305,4 +343,4 @@ class StatusObject(var mContext: Context){ "$daysText$hoursText$minutesText$secondsText" } } -} \ No newline at end of file +} diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncResultFormatter.kt b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncResultFormatter.kt new file mode 100644 index 000000000..c0b7ae76f --- /dev/null +++ b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncResultFormatter.kt @@ -0,0 +1,61 @@ +package ca.pkay.rcloneexplorer.workmanager + +object SyncResultFormatter { + data class Stats( + val totalTransfers: Int, + val totalSize: String, + val deletions: Int, + val renames: Int, + val errors: Int + ) { + fun changedItems(): Int = totalTransfers + deletions + renames + fun hasErrors(): Boolean = errors > 0 + fun hasChanges(): Boolean = changedItems() > 0 + } + + data class Labels( + val nothingToDo: String, + val completedWithErrors: (Int) -> String, + val completed: String, + val transferSummary: (String, Int) -> String, + val deletionSummary: (Int) -> String, + val renameSummary: (Int) -> String + ) + + fun successMessage(stats: Stats, labels: Labels): String { + if (!stats.hasChanges() && !stats.hasErrors()) { + return labels.nothingToDo + } + val lines = summaryLines(stats, labels) + return if (lines.isEmpty()) { + labels.completed + } else { + lines.joinToString("\n") + } + } + + fun completedWithErrorsMessage(stats: Stats, labels: Labels): String { + return (listOf(labels.completedWithErrors(stats.errors)) + summaryLines(stats, labels)) + .joinToString("\n") + } + + private fun summaryLines(stats: Stats, labels: Labels): List { + val lines = ArrayList() + + if (stats.totalTransfers > 0) { + lines.add(labels.transferSummary(stats.totalSize, stats.totalTransfers)) + } else if (stats.hasChanges()) { + lines.add(labels.completed) + } + + if (stats.deletions > 0) { + lines.add(labels.deletionSummary(stats.deletions)) + } + + if (stats.renames > 0) { + lines.add(labels.renameSummary(stats.renames)) + } + + return lines + } +} diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt index 7056e7bae..beffe9167 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt @@ -66,7 +66,7 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) private val mPreferences = PreferenceManager.getDefaultSharedPreferences(mContext) - private var log2File: Log2File? = null + private val log2File = Log2File(mContext) @@ -129,8 +129,11 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) return Result.failure() } - // Indicate whether the work finished successfully with the Result - return Result.success() + return if (failureReason == FAILURE_REASON.NO_FAILURE) { + Result.success() + } else { + Result.failure() + } } override fun onStopped() { @@ -183,13 +186,15 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) val line = iterator.next() try { val logline = JSONObject(line) + val level = logline.optString("level") + if (sIsLoggingEnabled && (level == "error" || level == "warning")) { + log2File.log(line) + } + //todo: migrate this to StatusObject, so that we can handle everything properly. - if (logline.getString("level") == "error") { - if (sIsLoggingEnabled) { - log2File?.log(line) - } + if (level == "error") { statusObject.parseLoglineToStatusObject(logline) - } else if (logline.getString("level") == "warning") { + } else if (level == "warning" || logline.has("stats")) { statusObject.parseLoglineToStatusObject(logline) } @@ -211,7 +216,10 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) FLog.e(TAG, "onHandleIntent: error reading stdout", e) } try { - localProcessReference.waitFor() + val exitCode = localProcessReference.waitFor() + if (exitCode != 0 || statusObject.hasErrors()) { + failureReason = FAILURE_REASON.RCLONE_ERROR + } } catch (e: InterruptedException) { FLog.e(TAG, "onHandleIntent: error waiting for process", e) } @@ -256,7 +264,11 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) content = mContext.getString(R.string.operation_failed_no_connection, mTitle) } FAILURE_REASON.RCLONE_ERROR -> { - content = mContext.getString(R.string.operation_failed_unknown_rclone_error, mTitle) + content = if (statusObject.hasErrors()) { + generateCompletedWithErrorsMessage(statusObject) + } else { + mContext.getString(R.string.operation_failed_unknown_rclone_error, mTitle) + } } } followupTask(mTask.onFailFollowup) @@ -295,28 +307,56 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) // this is currently only a useless mapper. It is supposed to keep this worker in sync with the ephemeral one. // when they are merged eventually, this can be easily extracted. private fun generateSuccessMessage(statusObject: StatusObject): String { - var message = mContext.resources.getQuantityString( - R.plurals.operation_success_description, - statusObject.getTotalTransfers(), - mTitle, - statusObject.getTotalSize(), - statusObject.getTotalTransfers() + return SyncResultFormatter.successMessage(statusObject.toResultStats(), resultLabels()) + } + + private fun generateCompletedWithErrorsMessage(statusObject: StatusObject): String { + return SyncResultFormatter.completedWithErrorsMessage(statusObject.toResultStats(), resultLabels()) + } + + private fun StatusObject.toResultStats(): SyncResultFormatter.Stats { + return SyncResultFormatter.Stats( + getTotalTransfers(), + getTotalSize(), + getDeletions(), + getRenames(), + getErrorCount() ) - if (statusObject.getTotalTransfers() == 0) { - message = mContext.resources.getString(R.string.operation_success_description_zero) - } - if (statusObject.getDeletions() > 0) { - message += """ - - ${ + } + + private fun resultLabels(): SyncResultFormatter.Labels { + return SyncResultFormatter.Labels( + nothingToDo = mContext.getString(R.string.operation_success_description_zero), + completedWithErrors = { errors -> + mContext.resources.getQuantityString( + R.plurals.operation_completed_with_errors, + errors, + errors + ) + }, + completed = mContext.getString(R.string.operation_success_description_completed), + transferSummary = { totalSize, transfers -> + mContext.resources.getQuantityString( + R.plurals.operation_success_description, + transfers, + mTitle, + totalSize, + transfers + ) + }, + deletionSummary = { deletions -> mContext.getString( - R.string.operation_success_description_deletions_prefix, - statusObject.getDeletions() + R.string.operation_success_description_deletions_prefix, + deletions + ) + }, + renameSummary = { renames -> + mContext.getString( + R.string.operation_success_description_renames_prefix, + renames ) } - """.trimIndent() - } - return message + ) } private fun showFailNotification(notificationId: Int, content: String, wasCancelled: Boolean = false) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 685fb6fe1..70f742618 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -337,11 +337,17 @@ Copy %s sync successful There was nothing to do. + Sync completed. + + Sync completed with %d error. + Sync completed with %d errors. + Successfully synced %2$s in %3$d file. Successfully synced %2$s in %3$d files. Also deleted %d files and folders. + Also renamed %d files and folders. Sync-Report Successes %d Tasks succeeded Sync-Report Failures From 18e8fbf2c4d374992901f98c00580d723d918b19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Hosta=C4=8Dn=C3=BD?= Date: Thu, 9 Jul 2026 00:30:21 +0200 Subject: [PATCH 2/4] Translations and plurals --- .../rcloneexplorer/workmanager/SyncWorker.kt | 10 ++++++---- app/src/main/res/values-ca/strings.xml | 14 +++++++++++++- app/src/main/res/values-de/strings.xml | 17 ++++++++++++++--- app/src/main/res/values-zh-rCN/strings.xml | 14 +++++++++++++- app/src/main/res/values/strings.xml | 10 ++++++++-- 5 files changed, 54 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt index beffe9167..a4fb92197 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt @@ -345,14 +345,16 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) ) }, deletionSummary = { deletions -> - mContext.getString( - R.string.operation_success_description_deletions_prefix, + mContext.resources.getQuantityString( + R.plurals.operation_success_description_deletions, + deletions, deletions ) }, renameSummary = { renames -> - mContext.getString( - R.string.operation_success_description_renames_prefix, + mContext.resources.getQuantityString( + R.plurals.operation_success_description_renames, + renames, renames ) } diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 41445b278..c23ab8e2d 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -312,11 +312,23 @@ Copia %s sincronitzat amb èxit No hi ha cap operació pendent. + Sincronització completada. + + Sincronització completada amb %d error. + Sincronització completada amb %d errors. + S\'ha sincronitzat amb èxit %2$s al fitxer %3$d. S\'han sincronitzat amb èxit %2$s als fitxers %3$d. - S\'han suprimit també %d fitxers i carpetes. + + S\'ha suprimit %d fitxer/carpeta. + S\'han suprimit %d fitxers/carpetes. + + + S\'ha canviat el nom de %d fitxer/carpeta. + S\'ha canviat el nom de %d fitxers/carpetes. + Informe de les sincronitzacions reeixides %d tasques reeixides Informe de les fallades en les sincronitzacions diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index e4f6f558c..70f7d27c1 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -299,13 +299,24 @@ Kopiere von entferntem Speicher nach lokalem Speicher Kopieren %s war erfolgreich + Es war nichts zu tun. + Synchronisierung abgeschlossen. + + Synchronisierung mit %d Fehler abgeschlossen. + Synchronisierung mit %d Fehlern abgeschlossen. + - Es gab nichts zu tun. Es wurden %2$s in %3$d Datei erfolgreich synchronisiert. Es wurden %2$s in %3$d Dateien erfolgreich synchronisiert. - Es war nichts zu tun. - Es wurden ebenfalls %d Dateien und Ordner gelöscht. + + %d Datei/Ordner gelöscht. + %d Dateien/Ordner gelöscht. + + + %d Datei/Ordner umbenannt. + %d Dateien/Ordner umbenannt. + Sync-Bericht Erfolge %d Aufgaben erfolgreich Sync-Bericht Fehlschläge diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 1cc7751dd..653c83cbc 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -306,11 +306,23 @@ 复制 %s 同步成功 没什么事可做。 + 同步已完成。 + + 同步已完成,但有 %d 个错误。 + 同步已完成,但有 %d 个错误。 + 已成功同步 %3$d 文件中的 %2$s。 已成功同步 %3$d 文件中的 %2$s。 - 还删除了 %d 个文件和文件夹。 + + 已删除 %d 个文件/文件夹。 + 已删除 %d 个文件/文件夹。 + + + 已重命名 %d 个文件/文件夹。 + 已重命名 %d 个文件/文件夹。 + Sync-Report 成功 %d 任务成功 同步报告 - 失败 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 70f742618..d671869ba 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -346,8 +346,14 @@ Successfully synced %2$s in %3$d file. Successfully synced %2$s in %3$d files. - Also deleted %d files and folders. - Also renamed %d files and folders. + + Deleted %d file/folder. + Deleted %d files/folders. + + + Renamed %d file/folder. + Renamed %d files/folders. + Sync-Report Successes %d Tasks succeeded Sync-Report Failures From 41c4be308dd198001ea1d1825bd729e427eec5d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Hosta=C4=8Dn=C3=BD?= Date: Thu, 9 Jul 2026 00:49:57 +0200 Subject: [PATCH 3/4] Adding comments --- .../Activities/MainActivity.java | 1 + .../notifications/support/StatusObject.kt | 1 + .../workmanager/SyncResultFormatter.kt | 18 ++++++++++++++++++ .../rcloneexplorer/workmanager/SyncWorker.kt | 4 ++++ 4 files changed, 24 insertions(+) diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/Activities/MainActivity.java b/app/src/main/java/ca/pkay/rcloneexplorer/Activities/MainActivity.java index 7ea902fba..493ca96e7 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/Activities/MainActivity.java +++ b/app/src/main/java/ca/pkay/rcloneexplorer/Activities/MainActivity.java @@ -233,6 +233,7 @@ protected void onResume() { protected void onNewIntent(Intent intent) { super.onNewIntent(intent); setIntent(intent); + // MainActivity is singleTop, so notification taps can arrive here instead of onCreate(). if(MAIN_ACTIVITY_START_LOG.equals(intent.getAction())){ startLogFragment(); navigationView.setCheckedItem(R.id.nav_logs); diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/notifications/support/StatusObject.kt b/app/src/main/java/ca/pkay/rcloneexplorer/notifications/support/StatusObject.kt index e5ea0431a..3097bc655 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/notifications/support/StatusObject.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/notifications/support/StatusObject.kt @@ -109,6 +109,7 @@ class StatusObject(var mContext: Context){ val attempt = retryAttempt.groupValues[1].toInt() val attempts = retryAttempt.groupValues[2].toInt() retryAttemptErrorCount = retryAttempt.groupValues[3].toInt() + // Rclone repeats the same underlying errors on retries; keep only the final attempt details. if (attempt < attempts) { mErrorList.clear() retryAttemptErrorCount = 0 diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncResultFormatter.kt b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncResultFormatter.kt index c0b7ae76f..fae3db3bf 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncResultFormatter.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncResultFormatter.kt @@ -1,6 +1,15 @@ package ca.pkay.rcloneexplorer.workmanager +/** + * Formats the final sync result from already-collected stats. + * + * Example: errors=1, transfers=2 -> "Sync completed with 1 error.\nSuccessfully synced 4 MB in 2 files." + */ object SyncResultFormatter { + /** + * Final rclone counters used for user-facing summaries. `totalTransfers` is copied or updated files, + * while deletions and renames are separate change types. + */ data class Stats( val totalTransfers: Int, val totalSize: String, @@ -13,6 +22,9 @@ object SyncResultFormatter { fun hasChanges(): Boolean = changedItems() > 0 } + /** + * Localized message builders supplied by Android resources. + */ data class Labels( val nothingToDo: String, val completedWithErrors: (Int) -> String, @@ -22,6 +34,9 @@ object SyncResultFormatter { val renameSummary: (Int) -> String ) + /** + * Clean success message. "Nothing to do" is only valid when there were no changes and no errors. + */ fun successMessage(stats: Stats, labels: Labels): String { if (!stats.hasChanges() && !stats.hasErrors()) { return labels.nothingToDo @@ -34,6 +49,9 @@ object SyncResultFormatter { } } + /** + * Partial result message: keep the clear error state, but still include any work rclone completed. + */ fun completedWithErrorsMessage(stats: Stats, labels: Labels): String { return (listOf(labels.completedWithErrors(stats.errors)) + summaryLines(stats, labels)) .joinToString("\n") diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt index a4fb92197..fb2e35283 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt @@ -324,6 +324,10 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) ) } + /** + * Android resource-backed labels for SyncResultFormatter. Keeping these here avoids putting Context + * or plural-resource knowledge into the formatter. + */ private fun resultLabels(): SyncResultFormatter.Labels { return SyncResultFormatter.Labels( nothingToDo = mContext.getString(R.string.operation_success_description_zero), From d400514ed35e8fde43856b2d9ebca87d269decca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Hosta=C4=8Dn=C3=BD?= Date: Fri, 10 Jul 2026 12:16:40 +0200 Subject: [PATCH 4/4] Improving strings for sync status --- .../workmanager/SyncResultFormatter.kt | 13 ++++++++----- .../rcloneexplorer/workmanager/SyncWorker.kt | 16 ++++++++++++---- app/src/main/res/values-ca/strings.xml | 8 ++++++++ app/src/main/res/values-de/strings.xml | 8 ++++++++ app/src/main/res/values-zh-rCN/strings.xml | 8 ++++++++ app/src/main/res/values/strings.xml | 8 ++++++++ 6 files changed, 52 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncResultFormatter.kt b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncResultFormatter.kt index fae3db3bf..d68fc9a62 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncResultFormatter.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncResultFormatter.kt @@ -3,7 +3,7 @@ package ca.pkay.rcloneexplorer.workmanager /** * Formats the final sync result from already-collected stats. * - * Example: errors=1, transfers=2 -> "Sync completed with 1 error.\nSuccessfully synced 4 MB in 2 files." + * Example: transfers=1, deletions=2 -> "Successfully synced 4 MB in 1 file.\nAlso deleted 2 files/folders." */ object SyncResultFormatter { /** @@ -30,8 +30,8 @@ object SyncResultFormatter { val completedWithErrors: (Int) -> String, val completed: String, val transferSummary: (String, Int) -> String, - val deletionSummary: (Int) -> String, - val renameSummary: (Int) -> String + val deletionSummary: (Int, Boolean) -> String, + val renameSummary: (Int, Boolean) -> String ) /** @@ -59,19 +59,22 @@ object SyncResultFormatter { private fun summaryLines(stats: Stats, labels: Labels): List { val lines = ArrayList() + var hasActionLine = false if (stats.totalTransfers > 0) { lines.add(labels.transferSummary(stats.totalSize, stats.totalTransfers)) + hasActionLine = true } else if (stats.hasChanges()) { lines.add(labels.completed) } if (stats.deletions > 0) { - lines.add(labels.deletionSummary(stats.deletions)) + lines.add(labels.deletionSummary(stats.deletions, hasActionLine)) + hasActionLine = true } if (stats.renames > 0) { - lines.add(labels.renameSummary(stats.renames)) + lines.add(labels.renameSummary(stats.renames, hasActionLine)) } return lines diff --git a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt index fb2e35283..3179d567f 100644 --- a/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt +++ b/app/src/main/java/ca/pkay/rcloneexplorer/workmanager/SyncWorker.kt @@ -348,16 +348,24 @@ class SyncWorker (private var mContext: Context, workerParams: WorkerParameters) transfers ) }, - deletionSummary = { deletions -> + deletionSummary = { deletions, isAdditional -> mContext.resources.getQuantityString( - R.plurals.operation_success_description_deletions, + if (isAdditional) { + R.plurals.operation_success_description_deletions_also + } else { + R.plurals.operation_success_description_deletions + }, deletions, deletions ) }, - renameSummary = { renames -> + renameSummary = { renames, isAdditional -> mContext.resources.getQuantityString( - R.plurals.operation_success_description_renames, + if (isAdditional) { + R.plurals.operation_success_description_renames_also + } else { + R.plurals.operation_success_description_renames + }, renames, renames ) diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index c23ab8e2d..c40b30c20 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -325,10 +325,18 @@ S\'ha suprimit %d fitxer/carpeta. S\'han suprimit %d fitxers/carpetes. + + També s\'ha suprimit %d fitxer/carpeta. + També s\'han suprimit %d fitxers/carpetes. + S\'ha canviat el nom de %d fitxer/carpeta. S\'ha canviat el nom de %d fitxers/carpetes. + + També s\'ha canviat el nom de %d fitxer/carpeta. + També s\'ha canviat el nom de %d fitxers/carpetes. + Informe de les sincronitzacions reeixides %d tasques reeixides Informe de les fallades en les sincronitzacions diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 70f7d27c1..4e56dc258 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -313,10 +313,18 @@ %d Datei/Ordner gelöscht. %d Dateien/Ordner gelöscht. + + Außerdem wurde %d Datei/Ordner gelöscht. + Außerdem wurden %d Dateien/Ordner gelöscht. + %d Datei/Ordner umbenannt. %d Dateien/Ordner umbenannt. + + Außerdem wurde %d Datei/Ordner umbenannt. + Außerdem wurden %d Dateien/Ordner umbenannt. + Sync-Bericht Erfolge %d Aufgaben erfolgreich Sync-Bericht Fehlschläge diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 653c83cbc..f3d1937d4 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -319,10 +319,18 @@ 已删除 %d 个文件/文件夹。 已删除 %d 个文件/文件夹。 + + 还删除了 %d 个文件/文件夹。 + 还删除了 %d 个文件/文件夹。 + 已重命名 %d 个文件/文件夹。 已重命名 %d 个文件/文件夹。 + + 还重命名了 %d 个文件/文件夹。 + 还重命名了 %d 个文件/文件夹。 + Sync-Report 成功 %d 任务成功 同步报告 - 失败 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d671869ba..0ad187572 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -350,10 +350,18 @@ Deleted %d file/folder. Deleted %d files/folders. + + Also deleted %d file/folder. + Also deleted %d files/folders. + Renamed %d file/folder. Renamed %d files/folders. + + Also renamed %d file/folder. + Also renamed %d files/folders. + Sync-Report Successes %d Tasks succeeded Sync-Report Failures