From 082a24cdb2ec83945c02e62bd5b2b51776c6eeb7 Mon Sep 17 00:00:00 2001 From: Kishan Rathore <34465683+rkishan516@users.noreply.github.com> Date: Tue, 1 Apr 2025 00:16:44 +0530 Subject: [PATCH] Feat: Add brightnessOf method for theme (#163733) Feat: Add brightnessOf method for theme fixes: #163393 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --- packages/flutter/lib/src/material/theme.dart | 36 +++++++ .../flutter/test/material/theme_test.dart | 94 +++++++++++++++++++ 2 files changed, 130 insertions(+) diff --git a/packages/flutter/lib/src/material/theme.dart b/packages/flutter/lib/src/material/theme.dart index 4667b2680d..436a4dc133 100644 --- a/packages/flutter/lib/src/material/theme.dart +++ b/packages/flutter/lib/src/material/theme.dart @@ -161,6 +161,42 @@ class Theme extends StatelessWidget { .resolveFrom(context); } + /// Retrieves the [Brightness] to use for descendant Material widgets, based + /// on the value of [ThemeData.brightness] in the given [context]. + /// + /// If no [InheritedTheme] can be found in the given [context], or its `brightness` + /// is null, it will fall back to [MediaQueryData.platformBrightness]. + /// + /// See also: + /// + /// * [maybeBrightnessOf], which returns null if no valid [InheritedTheme] or + /// [MediaQuery] exists. + /// * [ThemeData.brightness], the property that takes precedence over + /// [MediaQueryData.platformBrightness] for descendant Material widgets. + static Brightness brightnessOf(BuildContext context) { + final _InheritedTheme? inheritedTheme = + context.dependOnInheritedWidgetOfExactType<_InheritedTheme>(); + return inheritedTheme?.theme.data.brightness ?? MediaQuery.platformBrightnessOf(context); + } + + /// Retrieves the [Brightness] to use for descendant Material widgets, based + /// on the value of [ThemeData.brightness] in the given [context]. + /// + /// If no [InheritedTheme] or [MediaQuery] can be found in the given [context], it will + /// return null. + /// + /// See also: + /// + /// * [ThemeData.brightness], the property that takes precedence over + /// [MediaQueryData.platformBrightness] for descendant Material widgets. + /// * [brightnessOf], which return a default value if no valid [InheritedTheme] or + /// [MediaQuery] exists, instead of returning null. + static Brightness? maybeBrightnessOf(BuildContext context) { + final _InheritedTheme? inheritedTheme = + context.dependOnInheritedWidgetOfExactType<_InheritedTheme>(); + return inheritedTheme?.theme.data.brightness ?? MediaQuery.maybePlatformBrightnessOf(context); + } + @override Widget build(BuildContext context) { return _InheritedTheme( diff --git a/packages/flutter/test/material/theme_test.dart b/packages/flutter/test/material/theme_test.dart index 9d71b8c5dc..ee9b9aeedd 100644 --- a/packages/flutter/test/material/theme_test.dart +++ b/packages/flutter/test/material/theme_test.dart @@ -47,6 +47,92 @@ void main() { expect(Theme.of(tester.element(find.text('menuItem'))).brightness, equals(Brightness.dark)); }); + group('Theme.brightnessOf', () { + testWidgets('return correct brightness when just media query is given', ( + WidgetTester tester, + ) async { + await tester.pumpWidget( + const MediaQuery( + data: MediaQueryData(platformBrightness: Brightness.dark), + child: SizedBox(), + ), + ); + + expect(Theme.brightnessOf(tester.element(find.byType(SizedBox))), equals(Brightness.dark)); + }); + + testWidgets('return correct brightness with overriding theme brightness over media query', ( + WidgetTester tester, + ) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(platformBrightness: Brightness.dark), + child: Theme(data: ThemeData(brightness: Brightness.light), child: const SizedBox()), + ), + ); + + expect(Theme.brightnessOf(tester.element(find.byType(SizedBox))), equals(Brightness.light)); + }); + + testWidgets('returns Brightness.light when no theme or media query is present', ( + WidgetTester tester, + ) async { + // Prevent the implicitly added View from adding a MediaQuery + await tester.pumpWidget( + RawView(view: FakeFlutterView(tester.view, viewId: 77), child: const SizedBox()), + wrapWithView: false, + ); + + expect(Theme.brightnessOf(tester.element(find.byType(SizedBox))), equals(Brightness.light)); + }); + }); + + group('Theme.maybeBrightnessOf', () { + testWidgets('return correct brightness when just media query is given', ( + WidgetTester tester, + ) async { + await tester.pumpWidget( + const MediaQuery( + data: MediaQueryData(platformBrightness: Brightness.dark), + child: SizedBox(), + ), + ); + + expect( + Theme.maybeBrightnessOf(tester.element(find.byType(SizedBox))), + equals(Brightness.dark), + ); + }); + + testWidgets('return correct brightness with overriding theme brightness over media query', ( + WidgetTester tester, + ) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(platformBrightness: Brightness.dark), + child: Theme(data: ThemeData(brightness: Brightness.light), child: const SizedBox()), + ), + ); + + expect( + Theme.maybeBrightnessOf(tester.element(find.byType(SizedBox))), + equals(Brightness.light), + ); + }); + + testWidgets('returns null when no theme or media query is present', ( + WidgetTester tester, + ) async { + // Prevent the implicitly added View from adding a MediaQuery + await tester.pumpWidget( + RawView(view: FakeFlutterView(tester.view, viewId: 77), child: const SizedBox()), + wrapWithView: false, + ); + + expect(Theme.maybeBrightnessOf(tester.element(find.byType(SizedBox))), isNull); + }); + }); + testWidgets('Theme overrides selection style', (WidgetTester tester) async { final Key key = UniqueKey(); const Color defaultSelectionColor = Color(0x11111111); @@ -1242,3 +1328,11 @@ class _TextStyleProxy implements TextStyle { throw UnimplementedError(); } } + +class FakeFlutterView extends TestFlutterView { + FakeFlutterView(TestFlutterView view, {required this.viewId}) + : super(view: view, display: view.display, platformDispatcher: view.platformDispatcher); + + @override + final int viewId; +}