Skip to content

Commit 54573bc

Browse files
Have Material widgets in a Cupertino App partially use Cupertino theme (#139253)
Fixes #138621 Fixes and issue where if a Material widget is used in a Cupertino App, then some parts of it's default Material 3 theming would show up as purplish. This could be fixed by wrapping your app with a Material theme, but that's a little awkward. Material and Cupertino themes interact with each other a little oddly. If a theme of either is searched for but does not exist, then a default one is generated. So if a Material widget is in a Cupertino app that does not already have a Material theme, then a fallback Material theme will be created. This PR makes it so that, in this case when that Material theme is created, it's default colors will be based on the Cupertino theme. Another oddity is that a Material theme always wraps itself with a Cupertino theme that's values are based on the Material theme. So before this change, a Material widget would theoretically add a new Material based Cupertino theme to the tree. So I added logic that would have the Material theme check to see if a Cupertino theme exists, before it overwrites it. Before: ![image](https://github.com/flutter/flutter/assets/58190796/95874076-e943-48fa-ba9d-1d7b0f5a2f38) After: <img width="386" alt="Screenshot 2023-11-29 at 10 37 09�AM" src="https://github.com/flutter/flutter/assets/58190796/959ccfd9-3439-438e-ad36-20597334837a"> Update: I changed it to not rely on the Material 3 flag. Instead, if a Material theme is searched for and there is already a Cupertino theme in the tree, then it will use that Cupertino theme instead of generating a new. Also, if a Material theme is searched for, and it does not find one already in the tree, but does find a Cupertino theme, then it will generate one with a color palette based off of that Cupertino theme's colors. After with this change: <img width="390" alt="Screenshot 2024-05-09 at 10 16 22�AM" src="https://github.com/flutter/flutter/assets/58190796/79765d04-a7a3-4eb5-9477-11668ed138e5"> We should still probably suggest for developers to include a Material theme in their Cupertino app if they wish to use widgets from both packages.
1 parent ee3557a commit 54573bc

File tree

4 files changed

+70
-14
lines changed

4 files changed

+70
-14
lines changed

packages/flutter/lib/src/cupertino/theme.dart

+12-7
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class CupertinoTheme extends StatelessWidget {
6363
/// Resolves all the colors defined in that [CupertinoThemeData] against the
6464
/// given [BuildContext] on a best-effort basis.
6565
static CupertinoThemeData of(BuildContext context) {
66-
final _InheritedCupertinoTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedCupertinoTheme>();
66+
final InheritedCupertinoTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<InheritedCupertinoTheme>();
6767
return (inheritedTheme?.theme.data ?? const CupertinoThemeData()).resolveFrom(context);
6868
}
6969

@@ -83,7 +83,7 @@ class CupertinoTheme extends StatelessWidget {
8383
/// * [CupertinoThemeData.brightness], the property takes precedence over
8484
/// [MediaQueryData.platformBrightness] for descendant Cupertino widgets.
8585
static Brightness brightnessOf(BuildContext context) {
86-
final _InheritedCupertinoTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedCupertinoTheme>();
86+
final InheritedCupertinoTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<InheritedCupertinoTheme>();
8787
return inheritedTheme?.theme.data.brightness ?? MediaQuery.platformBrightnessOf(context);
8888
}
8989

@@ -103,7 +103,7 @@ class CupertinoTheme extends StatelessWidget {
103103
/// * [brightnessOf], which throws if no valid [CupertinoTheme] or
104104
/// [MediaQuery] exists, instead of returning null.
105105
static Brightness? maybeBrightnessOf(BuildContext context) {
106-
final _InheritedCupertinoTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedCupertinoTheme>();
106+
final InheritedCupertinoTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<InheritedCupertinoTheme>();
107107
return inheritedTheme?.theme.data.brightness ?? MediaQuery.maybePlatformBrightnessOf(context);
108108
}
109109

@@ -114,7 +114,7 @@ class CupertinoTheme extends StatelessWidget {
114114

115115
@override
116116
Widget build(BuildContext context) {
117-
return _InheritedCupertinoTheme(
117+
return InheritedCupertinoTheme(
118118
theme: this,
119119
child: IconTheme(
120120
data: CupertinoIconThemeData(color: data.primaryColor),
@@ -130,12 +130,17 @@ class CupertinoTheme extends StatelessWidget {
130130
}
131131
}
132132

133-
class _InheritedCupertinoTheme extends InheritedTheme {
134-
const _InheritedCupertinoTheme({
133+
/// Provides a [CupertinoTheme] to all decendents.
134+
class InheritedCupertinoTheme extends InheritedTheme {
135+
/// Creates an [InheritedTheme] that provides a [CupertinoTheme] to all
136+
/// decendents.
137+
const InheritedCupertinoTheme({
138+
super.key,
135139
required this.theme,
136140
required super.child,
137141
});
138142

143+
/// The [CupertinoTheme] that is provided to widgets lower in the tree.
139144
final CupertinoTheme theme;
140145

141146
@override
@@ -144,7 +149,7 @@ class _InheritedCupertinoTheme extends InheritedTheme {
144149
}
145150

146151
@override
147-
bool updateShouldNotify(_InheritedCupertinoTheme old) => theme.data != old.theme.data;
152+
bool updateShouldNotify(InheritedCupertinoTheme oldWidget) => theme.data != oldWidget.theme.data;
148153
}
149154

150155
/// Styling specifications for a [CupertinoTheme].

packages/flutter/lib/src/material/theme.dart

+13-7
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,10 @@ class Theme extends StatelessWidget {
105105
final _InheritedTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedTheme>();
106106
final MaterialLocalizations? localizations = Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
107107
final ScriptCategory category = localizations?.scriptCategory ?? ScriptCategory.englishLike;
108-
final ThemeData theme = inheritedTheme?.theme.data ?? _kFallbackTheme;
108+
final InheritedCupertinoTheme? inheritedCupertinoTheme = context.dependOnInheritedWidgetOfExactType<InheritedCupertinoTheme>();
109+
final ThemeData theme = inheritedTheme?.theme.data ?? (
110+
inheritedCupertinoTheme != null ? CupertinoBasedMaterialThemeData(themeData: inheritedCupertinoTheme.theme.data).materialTheme : _kFallbackTheme
111+
);
109112
return ThemeData.localize(theme, theme.typography.geometryThemeFor(category));
110113
}
111114

@@ -124,17 +127,20 @@ class Theme extends StatelessWidget {
124127
);
125128
}
126129

130+
CupertinoThemeData _inheritedCupertinoThemeData(BuildContext context) {
131+
final InheritedCupertinoTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<InheritedCupertinoTheme>();
132+
return (inheritedTheme?.theme.data ?? MaterialBasedCupertinoThemeData(materialTheme: data)).resolveFrom(context);
133+
}
134+
127135
@override
128136
Widget build(BuildContext context) {
129137
return _InheritedTheme(
130138
theme: this,
131139
child: CupertinoTheme(
132-
// We're using a MaterialBasedCupertinoThemeData here instead of a
133-
// CupertinoThemeData because it defers some properties to the Material
134-
// ThemeData.
135-
data: MaterialBasedCupertinoThemeData(
136-
materialTheme: data,
137-
),
140+
// If a CupertinoThemeData doesn't exist, we're using a
141+
// MaterialBasedCupertinoThemeData here instead of a CupertinoThemeData
142+
// because it defers some properties to the Material ThemeData.
143+
data: _inheritedCupertinoThemeData(context),
138144
child: _wrapsWidgetThemes(context, child),
139145
),
140146
);

packages/flutter/lib/src/material/theme_data.dart

+27
Original file line numberDiff line numberDiff line change
@@ -2210,6 +2210,33 @@ class MaterialBasedCupertinoThemeData extends CupertinoThemeData {
22102210
}
22112211
}
22122212

2213+
/// A class for creating a Material theme with a color scheme based off of the
2214+
/// colors from a [CupertinoThemeData]. This is intended to be used only in the
2215+
/// case when a Material widget is unable to find a Material theme in the tree,
2216+
/// but is able to find a Cupertino theme. Most often this will occur when a
2217+
/// Material widget is used inside of a [CupertinoApp].
2218+
///
2219+
/// Besides the colors, this theme will use all the defaults from Material's
2220+
/// [ThemeData], so if further customization is needed, it is best to manually
2221+
/// add a Material [Theme] above the [CupertinoApp].
2222+
class CupertinoBasedMaterialThemeData {
2223+
/// Creates a Material theme with a color scheme based off of the colors from
2224+
/// a [CupertinoThemeData].
2225+
CupertinoBasedMaterialThemeData({
2226+
required CupertinoThemeData themeData,
2227+
}) : materialTheme = ThemeData(
2228+
colorScheme: ColorScheme.fromSeed(
2229+
seedColor: themeData.primaryColor,
2230+
brightness: themeData.brightness ?? Brightness.light,
2231+
primary: themeData.primaryColor,
2232+
onPrimary: themeData.primaryContrastingColor,
2233+
)
2234+
);
2235+
2236+
/// The Material theme data with colors based on an existing [CupertinoThemeData].
2237+
final ThemeData materialTheme;
2238+
}
2239+
22132240
@immutable
22142241
class _IdentityThemeDataCacheKey {
22152242
const _IdentityThemeDataCacheKey(this.baseTheme, this.localTextGeometry);

packages/flutter/test/cupertino/app_test.dart

+18
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'package:flutter/cupertino.dart';
66
import 'package:flutter/foundation.dart';
77
import 'package:flutter/gestures.dart';
8+
import 'package:flutter/material.dart';
89
import 'package:flutter/rendering.dart';
910
import 'package:flutter/services.dart';
1011
import 'package:flutter_test/flutter_test.dart';
@@ -448,6 +449,23 @@ void main() {
448449
debugBrightnessOverride = null;
449450
});
450451

452+
testWidgets('CupertinoApp creates a Material theme with colors based off of Cupertino theme', (WidgetTester tester) async {
453+
late ThemeData appliedTheme;
454+
await tester.pumpWidget(
455+
CupertinoApp(
456+
theme: const CupertinoThemeData(primaryColor: CupertinoColors.activeGreen),
457+
home: Builder(
458+
builder: (BuildContext context) {
459+
appliedTheme = Theme.of(context);
460+
return const SizedBox();
461+
},
462+
),
463+
),
464+
);
465+
466+
expect(appliedTheme.colorScheme.primary, CupertinoColors.activeGreen);
467+
});
468+
451469
testWidgets('Cursor color is resolved when CupertinoThemeData.brightness is null', (WidgetTester tester) async {
452470
debugBrightnessOverride = Brightness.dark;
453471

0 commit comments

Comments
 (0)