Skip to content

Commit 8df954f

Browse files
committed
fix(android): tint the widget header with a low-alpha accent wash over the card and show the list title with its count on non-Focus widgets (#1173)
1 parent d404f30 commit 8df954f

9 files changed

Lines changed: 45 additions & 14 deletions

File tree

apps/mobile/lib/widget-data.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ describe('widget-data', () => {
138138
expect(payload.sections[2].detail).toBeNull();
139139
expect(payload.dateLabel).toMatch(/\d/);
140140
expect(payload.palette.warning).toMatch(/^#/);
141+
expect(payload.palette.headerWash).toMatch(/^#[0-9a-f]{8}$/i);
141142
expect(scheduleByTitle.get('Due today')?.contextLabel).toBe('Home');
142143
expect(payload.sections[2].items[0]).toMatchObject({ priorityColor: null, contextLabel: null });
143144
// Priorities off: the colour is gated with the feature.

apps/mobile/lib/widget-data.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
computeTodayFocusTasks,
3+
getAccentTint,
34
getTaskAccentColor,
45
getUpcomingDeferredTasks,
56
hasTimeComponent,
@@ -103,6 +104,8 @@ export interface WidgetPalette {
103104
accent: WidgetColor;
104105
onAccent: WidgetColor;
105106
warning: WidgetColor;
107+
// Accent wash for the widget header band over the card (core getAccentTint).
108+
headerWash: WidgetColor;
106109
}
107110

108111
// One list a placed Tasks widget can show (#1173); `focus` mirrors the
@@ -300,6 +303,7 @@ const resolveWidgetPalette = (
300303
accent: preset.tint,
301304
onAccent: preset.onTint,
302305
warning: preset.warning,
306+
headerWash: (getAccentTint(preset.tint, isDarkPreset(preset) ? 0.18 : 0.12) ?? preset.tint) as WidgetColor,
303307
};
304308
}
305309

@@ -318,6 +322,7 @@ const resolveWidgetPalette = (
318322
accent: '#2563EB',
319323
onAccent: '#FFFFFF',
320324
warning: '#F59E0B',
325+
headerWash: getAccentTint('#2563EB', 0.18) as WidgetColor,
321326
};
322327
}
323328

@@ -330,9 +335,20 @@ const resolveWidgetPalette = (
330335
accent: '#2563EB',
331336
onAccent: '#FFFFFF',
332337
warning: '#D97706',
338+
headerWash: getAccentTint('#2563EB', 0.12) as WidgetColor,
333339
};
334340
};
335341

342+
// A preset counts as dark when its background is darker than mid-grey.
343+
const isDarkPreset = (preset: { bg: string }): boolean => {
344+
const hex = preset.bg.replace('#', '');
345+
if (hex.length !== 6) return false;
346+
const r = parseInt(hex.slice(0, 2), 16);
347+
const g = parseInt(hex.slice(2, 4), 16);
348+
const b = parseInt(hex.slice(4, 6), 16);
349+
return (r * 299 + g * 587 + b * 114) / 1000 < 128;
350+
};
351+
336352
export function buildWidgetPayload(
337353
data: AppData,
338354
language: Language,

apps/mobile/modules/android-widget/android/src/main/java/tech/dongdongbh/mindwtr/androidwidget/WidgetPayload.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ data class WidgetPayload(
5757
val onAccent: Int,
5858
val border: Int,
5959
val warning: Int,
60+
val headerWash: Int,
6061
)
6162

6263
data class QuickCaptureLabels(
@@ -215,17 +216,22 @@ data class WidgetPayload(
215216
onAccent = parseHexColor(json.optString("onAccent")) ?: background,
216217
border = parseHexColor(json.optString("border")) ?: (parseHexColor(json.optString("mutedText")) ?: text),
217218
warning = parseHexColor(json.optString("warning")) ?: (parseHexColor(json.optString("accent")) ?: text),
219+
headerWash = parseHexColor(json.optString("headerWash"))
220+
?: WidgetRenderer.withAlpha(parseHexColor(json.optString("accent")) ?: text, 0x2E),
218221
)
219222
}
220223

221224
fun appUriOrNull(value: String?): String? = value?.takeIf { it.startsWith("mindwtr:") }
222225

223-
/** `#RRGGBB` or `#AARRGGBB` to an ARGB int; android.graphics.Color is a stub on the JVM. */
226+
/**
227+
* `#RRGGBB` or `#RRGGBBAA` (CSS order, what core's getAccentTint writes) to
228+
* an ARGB int; android.graphics.Color is a stub on the JVM.
229+
*/
224230
fun parseHexColor(value: String?): Int? {
225231
val hex = value?.trim()?.removePrefix("#") ?: return null
226232
val digits = when (hex.length) {
227233
6 -> "FF$hex"
228-
8 -> hex
234+
8 -> hex.substring(6, 8) + hex.substring(0, 6)
229235
else -> return null
230236
}
231237
return digits.toLongOrNull(16)?.toInt()

apps/mobile/modules/android-widget/android/src/main/java/tech/dongdongbh/mindwtr/androidwidget/WidgetRenderer.kt

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,15 @@ object WidgetRenderer {
6868
payload: WidgetPayload,
6969
palette: WidgetPayload.Palette?,
7070
) {
71-
// Header band: the list's title (the date for Focus), the Inbox chip and "+".
72-
val list = payload.listFor(WidgetListStore.read(context, appWidgetId))
73-
views.setTextViewText(R.id.mindwtr_widget_title, list.dateLabel?.ifEmpty { null } ?: list.title)
71+
// Header: Focus shows the date plus the Inbox chip; any other list shows its
72+
// full title with a small count, so the header never reads as two lists.
73+
val listId = WidgetListStore.read(context, appWidgetId)
74+
val list = payload.listFor(listId)
75+
val isFocus = listId == WidgetListStore.DEFAULT_LIST || payload.lists[listId] == null
76+
val rowCount = if (list.sections.isEmpty()) list.items.size else list.sections.sumOf { it.items.size }
77+
views.setTextViewText(R.id.mindwtr_widget_title, if (isFocus) list.dateLabel?.ifEmpty { null } ?: list.title else "${list.title} · $rowCount")
7478
views.setTextViewText(R.id.mindwtr_widget_subtitle, "${payload.inboxLabel} ${payload.inboxCount}")
79+
views.setViewVisibility(R.id.mindwtr_widget_subtitle, if (isFocus) View.VISIBLE else View.GONE)
7580
views.setTextViewText(R.id.mindwtr_widget_empty, payload.emptyMessage)
7681
views.setViewVisibility(R.id.mindwtr_widget_empty, if (list.items.isEmpty() && list.sections.isEmpty()) View.VISIBLE else View.GONE)
7782

@@ -100,11 +105,12 @@ object WidgetRenderer {
100105
PendingIntent.getActivity(context, REQUEST_ROW, rowTemplate, mutable),
101106
)
102107

103-
// The header shares the body surface (dd: a solid accent band contrasted
104-
// too hard); the accent sits only on the date and the "+".
108+
// Header = a low-alpha accent wash over the card with a hairline under it
109+
// (dd: some contrast, not the solid band); the accent itself only on "+".
105110
palette?.let {
106111
views.setInt(R.id.mindwtr_widget_surface, "setColorFilter", it.background)
107-
views.setTextColor(R.id.mindwtr_widget_title, it.accent)
112+
views.setInt(R.id.mindwtr_widget_band, "setColorFilter", it.headerWash)
113+
views.setTextColor(R.id.mindwtr_widget_title, it.text)
108114
views.setTextColor(R.id.mindwtr_widget_subtitle, it.mutedText)
109115
views.setTextColor(R.id.mindwtr_widget_capture, it.accent)
110116
views.setInt(R.id.mindwtr_widget_header_divider, "setBackgroundColor", it.border)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
33
<corners android:topLeftRadius="16dp" android:topRightRadius="16dp" />
4-
<solid android:color="@color/mindwtr_widget_accent" />
4+
<solid android:color="@color/mindwtr_widget_header_wash" />
55
</shape>

apps/mobile/modules/android-widget/android/src/main/res/layout/mindwtr_widget.xml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@
3131
android:layout_height="match_parent"
3232
android:importantForAccessibility="no"
3333
android:scaleType="fitXY"
34-
android:src="@drawable/mindwtr_widget_band"
35-
android:visibility="gone" />
34+
android:src="@drawable/mindwtr_widget_band" />
3635

3736
<LinearLayout
3837
android:layout_width="match_parent"
@@ -49,7 +48,7 @@
4948
android:layout_weight="1"
5049
android:ellipsize="end"
5150
android:singleLine="true"
52-
android:textColor="@color/mindwtr_widget_accent"
51+
android:textColor="@color/mindwtr_widget_text"
5352
android:textSize="15sp"
5453
android:textStyle="bold" />
5554

apps/mobile/modules/android-widget/android/src/main/res/values-night/colors.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
<color name="mindwtr_widget_accent">#2563EB</color>
99
<color name="mindwtr_widget_border">#374151</color>
1010
<color name="mindwtr_widget_warning">#F59E0B</color>
11+
<color name="mindwtr_widget_header_wash">#2E2563EB</color>
1112
<color name="mindwtr_widget_on_accent">#FFFFFF</color>
1213
</resources>

apps/mobile/modules/android-widget/android/src/main/res/values/colors.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010
<color name="mindwtr_widget_accent">#2563EB</color>
1111
<color name="mindwtr_widget_border">#CBD5E1</color>
1212
<color name="mindwtr_widget_warning">#D97706</color>
13+
<color name="mindwtr_widget_header_wash">#1F2563EB</color>
1314
<color name="mindwtr_widget_on_accent">#FFFFFF</color>
1415
</resources>

apps/mobile/modules/android-widget/android/src/test/java/tech/dongdongbh/mindwtr/androidwidget/WidgetPayloadTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class WidgetPayloadTest {
3636
"emptyMessage": "All clear",
3737
"focusUri": "mindwtr:///focus",
3838
"themeMode": "dark",
39-
"palette": {"background": "#111827", "card": "#1F2937", "text": "#F9FAFB", "mutedText": "#CBD5E1", "accent": "#2563EB", "onAccent": "#FFFFFF", "border": "#374151", "warning": "#F59E0B"},
39+
"palette": {"background": "#111827", "card": "#1F2937", "text": "#F9FAFB", "mutedText": "#CBD5E1", "accent": "#2563EB", "onAccent": "#FFFFFF", "border": "#374151", "warning": "#F59E0B", "headerWash": "#2563EB2E"},
4040
"quickCapture": {"title": "Quick capture", "placeholder": "Add task to inbox...", "save": "Save", "cancel": "Cancel", "added": "Task added to Mindwtr."}
4141
}
4242
""".trimIndent()
@@ -67,6 +67,7 @@ class WidgetPayloadTest {
6767
assertEquals("Saturday, Sep 6", payload.dateLabel)
6868
assertEquals(0xFF374151.toInt(), payload.palette!!.border)
6969
assertEquals(0xFFF59E0B.toInt(), payload.palette!!.warning)
70+
assertEquals(0x2E2563EB, payload.palette!!.headerWash)
7071
assertNull(payload.sections[1].items[0].contextLabel)
7172
val rows = TasksWidgetFactory.buildRows(WidgetPayload.ListPayload("", null, payload.sections, payload.items))
7273
assertEquals(4, rows.size)
@@ -131,7 +132,7 @@ class WidgetPayloadTest {
131132
@Test
132133
fun parsesHexColorsWithAndWithoutAlpha() {
133134
assertEquals(0xFF2563EB.toInt(), WidgetPayload.parseHexColor("#2563EB"))
134-
assertEquals(0x802563EB.toInt(), WidgetPayload.parseHexColor("#802563EB"))
135+
assertEquals(0x802563EB.toInt(), WidgetPayload.parseHexColor("#2563EB80"))
135136
assertNull(WidgetPayload.parseHexColor("blue"))
136137
assertNull(WidgetPayload.parseHexColor(null))
137138
}

0 commit comments

Comments
 (0)