From 56947a888a9cf94f2a5cc319589bce268a9bcb67 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Thu, 24 Sep 2026 15:43:19 +0200 Subject: [PATCH 1/2] fix(ui): let dialogs below StreamChat follow the app's brightness Co-Authored-By: Claude Opus 5.5 --- packages/stream_chat_flutter/CHANGELOG.md | 1 + .../lib/src/stream_chat.dart | 10 ++++- .../test/src/stream_chat_test.dart | 44 +++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index f9dc4c2fa4..ee9469167e 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -24,6 +24,7 @@ - Fixed `StreamMessageComposer` polling the audio recorder ten times a second while no recording is in progress, including when voice recording is disabled. - Fixed `StreamMessageComposer`'s send button looking enabled on an empty message when voice recording is off, and showing the microphone instead of send for a staged poll. - Fixed the attachment picker showing an empty commands tab in channels without commands. +- Fixed dialogs and bottom sheets opened below `StreamChat` keeping the old colors when the app switched between light and dark mode. ## 10.5.0 diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index 75162c2155..54e7709f71 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -217,8 +217,14 @@ class StreamChatState extends State { final theme = widget.themeData ?? StreamChatThemeData(); child = StreamChatTheme(data: theme, child: child); - final streamTheme = StreamTheme.of(context); - child = Theme(data: Theme.of(context).withExtension(streamTheme), child: child); + // Only when the app has not registered a StreamTheme itself: dialogs and + // sheets opened below this point copy the Theme once and would stop + // following the app's brightness. + final materialTheme = Theme.of(context); + if (materialTheme.extension() == null) { + final streamTheme = StreamTheme.of(context); + child = Theme(data: materialTheme.withExtension(streamTheme), child: child); + } if (widget.componentBuilders case final builders?) { child = StreamComponentFactory(builders: builders, child: child); diff --git a/packages/stream_chat_flutter/test/src/stream_chat_test.dart b/packages/stream_chat_flutter/test/src/stream_chat_test.dart index 9e8db8ae5e..927d66e401 100644 --- a/packages/stream_chat_flutter/test/src/stream_chat_test.dart +++ b/packages/stream_chat_flutter/test/src/stream_chat_test.dart @@ -218,6 +218,50 @@ void main() { expect(captured, same(sibling)); }, ); + + testWidgets( + 'a dialog opened below StreamChat follows the app theme when the ' + 'brightness changes', + (tester) async { + tester.platformDispatcher.platformBrightnessTestValue = Brightness.light; + addTearDown(tester.platformDispatcher.clearPlatformBrightnessTestValue); + + final mockClient = MockClient(); + final light = StreamTheme.light(); + final dark = StreamTheme.dark(); + StreamTheme? captured; + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(extensions: [light]), + darkTheme: ThemeData(brightness: Brightness.dark, extensions: [dark]), + home: StreamChat( + client: mockClient, + child: Builder( + builder: (context) => TextButton( + onPressed: () => showDialog( + context: context, + builder: (context) { + captured = Theme.of(context).extension(); + return const Text('Dialog'); + }, + ), + child: const Text('Open'), + ), + ), + ), + ), + ); + + await tester.tap(find.text('Open')); + await tester.pumpAndSettle(); + expect(captured, same(light)); + + tester.platformDispatcher.platformBrightnessTestValue = Brightness.dark; + await tester.pumpAndSettle(); + expect(captured, same(dark)); + }, + ); }); } From c8d468861592d8b833a78821d046749961aefb8a Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Thu, 1 Oct 2026 13:27:07 +0200 Subject: [PATCH 2/2] fix(ui): keep the state below StreamChat when its Theme comes or goes Key the subtree so switching between a light and dark theme where only one registers a StreamTheme no longer unmounts everything below StreamChat. Co-Authored-By: Claude Opus 5.5 --- .../lib/src/stream_chat.dart | 6 +++- .../test/src/stream_chat_test.dart | 29 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index 54e7709f71..2a2584a3cb 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -219,7 +219,11 @@ class StreamChatState extends State { // Only when the app has not registered a StreamTheme itself: dialogs and // sheets opened below this point copy the Theme once and would stop - // following the app's brightness. + // following the app's brightness (flutter/flutter#106642). + // + // The key keeps the subtree's state when the Theme comes or goes, such as + // when only one of the app's light and dark themes registers a StreamTheme. + child = KeyedSubtree(key: GlobalObjectKey(this), child: child); final materialTheme = Theme.of(context); if (materialTheme.extension() == null) { final streamTheme = StreamTheme.of(context); diff --git a/packages/stream_chat_flutter/test/src/stream_chat_test.dart b/packages/stream_chat_flutter/test/src/stream_chat_test.dart index 927d66e401..6e998c1bd9 100644 --- a/packages/stream_chat_flutter/test/src/stream_chat_test.dart +++ b/packages/stream_chat_flutter/test/src/stream_chat_test.dart @@ -262,6 +262,35 @@ void main() { expect(captured, same(dark)); }, ); + + testWidgets( + 'keeps the state below StreamChat when only one of the app themes ' + 'registers a StreamTheme', + (tester) async { + tester.platformDispatcher.platformBrightnessTestValue = Brightness.light; + addTearDown(tester.platformDispatcher.clearPlatformBrightnessTestValue); + + final mockClient = MockClient(); + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(extensions: [StreamTheme.light()]), + darkTheme: ThemeData(brightness: Brightness.dark), + home: StreamChat( + client: mockClient, + child: const Material(child: TextField()), + ), + ), + ); + + await tester.enterText(find.byType(TextField), 'draft'); + + tester.platformDispatcher.platformBrightnessTestValue = Brightness.dark; + await tester.pumpAndSettle(); + + expect(find.text('draft'), findsOneWidget); + }, + ); }); }