diff --git a/app/src/main/java/com/github/nullptroma/wallenc/app/tasks/TaskPipelineForegroundService.kt b/app/src/main/java/com/github/nullptroma/wallenc/app/tasks/TaskPipelineForegroundService.kt index 98bf535..39e94bd 100644 --- a/app/src/main/java/com/github/nullptroma/wallenc/app/tasks/TaskPipelineForegroundService.kt +++ b/app/src/main/java/com/github/nullptroma/wallenc/app/tasks/TaskPipelineForegroundService.kt @@ -26,8 +26,9 @@ import kotlin.math.roundToInt /** * Single foreground notification (one [NotificationManager.notify] id) that accumulates all task - * states. Multiple progress bars are implemented via [RemoteViews] — the standard - * [NotificationCompat.Builder.setProgress] API only supports one bar per notification. + * states. Each row: [task title], then a horizontal strip with a smaller **label** (ellipsis) + * beside a horizontal [ProgressBar] (fixed width). Indeterminate tasks hide the bar; the label + * line cycles suffix `""` → `.` → `..` → `...` by wall clock between [notify] calls. */ @AndroidEntryPoint class TaskPipelineForegroundService : Service() { @@ -153,6 +154,7 @@ class TaskPipelineForegroundService : Service() { val color = notificationTemplateTextColor() for (i in 0 until MAX_TASK_ROWS) { remoteViews.setTextColor(TASK_TITLE_IDS[i], color) + remoteViews.setTextColor(TASK_SUBTITLE_IDS[i], color) } } @@ -195,18 +197,34 @@ class TaskPipelineForegroundService : Service() { private fun hideRow(remoteViews: RemoteViews, index: Int) { remoteViews.setViewVisibility(TASK_ROW_IDS[index], View.GONE) + remoteViews.setViewVisibility(TASK_LABEL_BAR_ROW_IDS[index], View.GONE) + remoteViews.setTextViewText(TASK_SUBTITLE_IDS[index], "") } private fun showTaskRow(remoteViews: RemoteViews, index: Int, task: TaskForegroundItem) { remoteViews.setViewVisibility(TASK_ROW_IDS[index], View.VISIBLE) - remoteViews.setTextViewText(TASK_TITLE_IDS[index], taskTitleText(task)) - remoteViews.setViewVisibility(TASK_PROGRESS_IDS[index], View.VISIBLE) + remoteViews.setViewVisibility(TASK_LABEL_BAR_ROW_IDS[index], View.VISIBLE) + remoteViews.setTextViewText(TASK_TITLE_IDS[index], task.title) + val label = task.progress?.label?.trim().orEmpty() val fraction = task.progress?.fraction if (fraction != null) { + if (label.isNotEmpty()) { + remoteViews.setViewVisibility(TASK_SUBTITLE_IDS[index], View.VISIBLE) + remoteViews.setTextViewText(TASK_SUBTITLE_IDS[index], label) + } else { + remoteViews.setViewVisibility(TASK_SUBTITLE_IDS[index], View.GONE) + remoteViews.setTextViewText(TASK_SUBTITLE_IDS[index], "") + } + remoteViews.setViewVisibility(TASK_PROGRESS_IDS[index], View.VISIBLE) val pct = (fraction.coerceIn(0f, 1f) * 100).roundToInt() remoteViews.setProgressBar(TASK_PROGRESS_IDS[index], 100, pct, false) } else { - remoteViews.setProgressBar(TASK_PROGRESS_IDS[index], 0, 0, true) + remoteViews.setViewVisibility(TASK_SUBTITLE_IDS[index], View.VISIBLE) + remoteViews.setTextViewText( + TASK_SUBTITLE_IDS[index], + indeterminateSubtitleWithDots(label), + ) + remoteViews.setViewVisibility(TASK_PROGRESS_IDS[index], View.GONE) } } @@ -216,13 +234,22 @@ class TaskPipelineForegroundService : Service() { TASK_TITLE_IDS[index], getString(R.string.task_notification_more_tasks, remainingCount), ) - remoteViews.setViewVisibility(TASK_PROGRESS_IDS[index], View.GONE) + remoteViews.setViewVisibility(TASK_LABEL_BAR_ROW_IDS[index], View.GONE) + remoteViews.setTextViewText(TASK_SUBTITLE_IDS[index], "") } - private fun taskTitleText(task: TaskForegroundItem): String { - val label = task.progress?.label?.trim().orEmpty() - val title = task.title - return if (label.isNotEmpty()) "$title — $label" else title + /** + * Cycles `""` → `.` → `..` → `...` (wall clock) so each [notify] can advance the suffix after + * the optional base label. + */ + private fun indeterminateSubtitleWithDots(baseLabel: String): String { + val dots = when (((System.currentTimeMillis() / DOTS_ANIMATION_STEP_MS) % 4L).toInt()) { + 0 -> "" + 1 -> "." + 2 -> ".." + else -> "..." + } + return if (baseLabel.isEmpty()) dots else baseLabel + dots } companion object { @@ -230,6 +257,8 @@ class TaskPipelineForegroundService : Service() { private const val FOREGROUND_NOTIFICATION_ID = 1001 private const val MIN_NOTIFICATION_UPDATE_INTERVAL_MS = 500L + private const val DOTS_ANIMATION_STEP_MS = 400L + /** Must match [R.layout.notification_wallenc_tasks_big] row count. */ private const val MAX_TASK_ROWS = 8 @@ -265,5 +294,27 @@ class TaskPipelineForegroundService : Service() { R.id.task_progress_6, R.id.task_progress_7, ) + + private val TASK_SUBTITLE_IDS = intArrayOf( + R.id.task_subtitle_0, + R.id.task_subtitle_1, + R.id.task_subtitle_2, + R.id.task_subtitle_3, + R.id.task_subtitle_4, + R.id.task_subtitle_5, + R.id.task_subtitle_6, + R.id.task_subtitle_7, + ) + + private val TASK_LABEL_BAR_ROW_IDS = intArrayOf( + R.id.task_label_bar_row_0, + R.id.task_label_bar_row_1, + R.id.task_label_bar_row_2, + R.id.task_label_bar_row_3, + R.id.task_label_bar_row_4, + R.id.task_label_bar_row_5, + R.id.task_label_bar_row_6, + R.id.task_label_bar_row_7, + ) } } diff --git a/app/src/main/res/layout/notification_wallenc_tasks_big.xml b/app/src/main/res/layout/notification_wallenc_tasks_big.xml index 9d39717..e53d38c 100644 --- a/app/src/main/res/layout/notification_wallenc_tasks_big.xml +++ b/app/src/main/res/layout/notification_wallenc_tasks_big.xml @@ -23,12 +23,30 @@ android:maxLines="2" android:textSize="13sp" /> - + android:layout_height="wrap_content" + android:layout_marginTop="2dp" + android:gravity="center_vertical" + android:orientation="horizontal"> + + + + + - + android:layout_height="wrap_content" + android:layout_marginTop="2dp" + android:gravity="center_vertical" + android:orientation="horizontal"> + + + + + - + android:layout_height="wrap_content" + android:layout_marginTop="2dp" + android:gravity="center_vertical" + android:orientation="horizontal"> + + + + + - + android:layout_height="wrap_content" + android:layout_marginTop="2dp" + android:gravity="center_vertical" + android:orientation="horizontal"> + + + + + - + android:layout_height="wrap_content" + android:layout_marginTop="2dp" + android:gravity="center_vertical" + android:orientation="horizontal"> + + + + + - + android:layout_height="wrap_content" + android:layout_marginTop="2dp" + android:gravity="center_vertical" + android:orientation="horizontal"> + + + + + - + android:layout_height="wrap_content" + android:layout_marginTop="2dp" + android:gravity="center_vertical" + android:orientation="horizontal"> + + + + + - + android:layout_height="wrap_content" + android:layout_marginTop="2dp" + android:gravity="center_vertical" + android:orientation="horizontal"> + + + + +