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