From 72327e0fd3ae41c1137b8c7d3f2c83c25bc579ce Mon Sep 17 00:00:00 2001 From: Hitanshu Paruthi Date: Tue, 10 Nov 2026 13:43:28 -0500 Subject: [PATCH] fix(mobile): use pink dots for unread activity Signed-off-by: Hitanshu Paruthi --- .../activity/activity_page/inbox_row.dart | 2 +- .../channels/channels_page/channel_tile.dart | 2 +- mobile/lib/shared/theme/app_colors.dart | 7 ++++ mobile/lib/shared/theme/app_theme.dart | 2 ++ .../features/activity/activity_page_test.dart | 36 +++++++++++++------ .../features/channels/channels_page_test.dart | 15 +++++++- mobile/test/shared/theme/app_theme_test.dart | 11 ++++++ 7 files changed, 61 insertions(+), 14 deletions(-) diff --git a/mobile/lib/features/activity/activity_page/inbox_row.dart b/mobile/lib/features/activity/activity_page/inbox_row.dart index 083fff454cd..ff9537a6363 100644 --- a/mobile/lib/features/activity/activity_page/inbox_row.dart +++ b/mobile/lib/features/activity/activity_page/inbox_row.dart @@ -258,7 +258,7 @@ class _InboxRow extends HookConsumerWidget { height: 6, decoration: BoxDecoration( shape: BoxShape.circle, - color: context.colors.primary, + color: context.appColors.unread, ), ), ], diff --git a/mobile/lib/features/channels/channels_page/channel_tile.dart b/mobile/lib/features/channels/channels_page/channel_tile.dart index 54117a35464..ca18dcf26d3 100644 --- a/mobile/lib/features/channels/channels_page/channel_tile.dart +++ b/mobile/lib/features/channels/channels_page/channel_tile.dart @@ -118,7 +118,7 @@ class _ChannelTile extends ConsumerWidget { height: 6, decoration: BoxDecoration( shape: BoxShape.circle, - color: context.colors.primary, + color: context.appColors.unread, ), ), ), diff --git a/mobile/lib/shared/theme/app_colors.dart b/mobile/lib/shared/theme/app_colors.dart index 78ff1abad31..53534e0af12 100644 --- a/mobile/lib/shared/theme/app_colors.dart +++ b/mobile/lib/shared/theme/app_colors.dart @@ -5,6 +5,9 @@ class AppColors extends ThemeExtension { final Color success; final Color warning; final Color accent; + + /// Unread activity dots, independent of the selected accent. + final Color unread; final Color huddleDrawerSurface; final Color huddleControlSurface; final Color onHuddleDrawer; @@ -18,6 +21,7 @@ class AppColors extends ThemeExtension { required this.success, required this.warning, required this.accent, + required this.unread, required this.huddleDrawerSurface, required this.huddleControlSurface, required this.onHuddleDrawer, @@ -29,6 +33,7 @@ class AppColors extends ThemeExtension { Color? success, Color? warning, Color? accent, + Color? unread, Color? huddleDrawerSurface, Color? huddleControlSurface, Color? onHuddleDrawer, @@ -37,6 +42,7 @@ class AppColors extends ThemeExtension { success: success ?? this.success, warning: warning ?? this.warning, accent: accent ?? this.accent, + unread: unread ?? this.unread, huddleDrawerSurface: huddleDrawerSurface ?? this.huddleDrawerSurface, huddleControlSurface: huddleControlSurface ?? this.huddleControlSurface, onHuddleDrawer: onHuddleDrawer ?? this.onHuddleDrawer, @@ -50,6 +56,7 @@ class AppColors extends ThemeExtension { success: Color.lerp(success, other.success, t)!, warning: Color.lerp(warning, other.warning, t)!, accent: Color.lerp(accent, other.accent, t)!, + unread: Color.lerp(unread, other.unread, t)!, huddleDrawerSurface: Color.lerp( huddleDrawerSurface, other.huddleDrawerSurface, diff --git a/mobile/lib/shared/theme/app_theme.dart b/mobile/lib/shared/theme/app_theme.dart index bad5e0a5f9e..3b8538c8cd1 100644 --- a/mobile/lib/shared/theme/app_theme.dart +++ b/mobile/lib/shared/theme/app_theme.dart @@ -40,6 +40,7 @@ class AppTheme { success: const Color(0xFF218358), // Desktop green-11 warning: const Color(0xFFAB6400), // Desktop amber-11 accent: scheme.tertiary, + unread: const Color(0xFFDF1E85), // Desktop status-unread (magenta-9) huddleDrawerSurface: const Color(0xFF000000), huddleControlSurface: const Color(0xFF333333), onHuddleDrawer: const Color(0xFFFAFAFA), @@ -64,6 +65,7 @@ class AppTheme { success: const Color(0xFF3DD68C), // Desktop green-11 warning: const Color(0xFFFFCA16), // Desktop amber-11 accent: scheme.tertiary, + unread: const Color(0xFFF4BCCF), // Desktop status-unread (magenta-11) huddleDrawerSurface: scheme.primaryContainer, huddleControlSurface: Color.alphaBlend( scheme.onPrimaryContainer.withValues(alpha: 0.18), diff --git a/mobile/test/features/activity/activity_page_test.dart b/mobile/test/features/activity/activity_page_test.dart index 96974e90cc0..eac192fdc28 100644 --- a/mobile/test/features/activity/activity_page_test.dart +++ b/mobile/test/features/activity/activity_page_test.dart @@ -129,6 +129,7 @@ void main() { }; Future buildTestable({ + Brightness brightness = Brightness.light, HomeFeedResponse? feed, ActivityNotifier Function()? activityNotifier, Map? users, @@ -171,7 +172,9 @@ void main() { ...overrides, ], child: MaterialApp( - theme: AppTheme.light(), + theme: brightness == Brightness.dark + ? AppTheme.dark() + : AppTheme.light(), builder: (context, child) => MediaQuery( data: MediaQuery.of( context, @@ -1002,17 +1005,28 @@ void main() { }); testWidgets('unread rows show a dot; read rows do not', (tester) async { - await tester.pumpWidget( - await buildTestable( - // ch1 fully read; ch2 (thread) unread. - readContexts: {'ch1': now}, - ), - ); - await tester.pumpAndSettle(); + for (final brightness in Brightness.values) { + await tester.pumpWidget( + await buildTestable( + brightness: brightness, + // ch1 fully read; ch2 (thread) unread. + readContexts: {'ch1': now}, + ), + ); + await tester.pumpAndSettle(); - expect(find.byKey(const ValueKey('inbox-unread-dot-m1')), findsNothing); - expect(find.byKey(const ValueKey('inbox-unread-dot-ag1')), findsNothing); - expect(find.byKey(const ValueKey('inbox-unread-dot-a1')), findsOneWidget); + expect(find.byKey(const ValueKey('inbox-unread-dot-m1')), findsNothing); + expect(find.byKey(const ValueKey('inbox-unread-dot-ag1')), findsNothing); + final dot = tester.widget( + find.byKey(const ValueKey('inbox-unread-dot-a1')), + ); + expect( + (dot.decoration as BoxDecoration).color, + brightness == Brightness.dark + ? const Color(0xFFF4BCCF) + : const Color(0xFFDF1E85), + ); + } }); testWidgets( diff --git a/mobile/test/features/channels/channels_page_test.dart b/mobile/test/features/channels/channels_page_test.dart index 5c3a04e7f08..f9381a5e9d3 100644 --- a/mobile/test/features/channels/channels_page_test.dart +++ b/mobile/test/features/channels/channels_page_test.dart @@ -3627,7 +3627,10 @@ void main() { ); }); - for (final viaMenu in [false, true]) { + for (final (viaMenu, brightness) in [ + (false, Brightness.light), + (true, Brightness.dark), + ]) { testWidgets('dots, not bolds, channels with only unread thread replies ' '(cleared ${viaMenu ? 'by Mark read' : 'by reading the thread'})', ( tester, @@ -3661,6 +3664,7 @@ void main() { await tester.pumpWidget( buildTestable( + brightness: brightness, overrides: [ channelsProvider.overrideWith( () => _FakeNotifier( @@ -3697,6 +3701,15 @@ void main() { find.byKey(const ValueKey('channel-attention-dot-1')), findsOneWidget, ); + final dot = tester.widget( + find.byKey(const ValueKey('channel-attention-dot-1')), + ); + expect( + (dot.decoration as BoxDecoration).color, + brightness == Brightness.dark + ? const Color(0xFFF4BCCF) + : const Color(0xFFDF1E85), + ); expect( find.bySemanticsLabel(RegExp(r'general[\s\S]*Needs your attention')), findsOneWidget, diff --git a/mobile/test/shared/theme/app_theme_test.dart b/mobile/test/shared/theme/app_theme_test.dart index 161d2cd8006..68d349cde9e 100644 --- a/mobile/test/shared/theme/app_theme_test.dart +++ b/mobile/test/shared/theme/app_theme_test.dart @@ -4,6 +4,17 @@ import 'package:buzz/shared/theme/app_colors.dart'; import 'package:buzz/shared/theme/app_theme.dart'; void main() { + test('unread dots contrast with the list surface in both modes', () { + for (final theme in [AppTheme.light(), AppTheme.dark()]) { + final dot = theme.extension()!.unread.computeLuminance(); + final surface = theme.colorScheme.surface.computeLuminance(); + final contrast = dot > surface + ? (dot + .05) / (surface + .05) + : (surface + .05) / (dot + .05); + expect(contrast, greaterThanOrEqualTo(3), reason: '${theme.brightness}'); + } + }); + test('disables Material touch ripples in every app theme', () { expect(AppTheme.light().splashFactory, NoSplash.splashFactory); expect(AppTheme.dark().splashFactory, NoSplash.splashFactory);