chore: don't use theme extension

This commit is contained in:
Richard Shiue 2025-04-17 17:56:35 +08:00
parent 74caa39655
commit 8b303b7e9d
6 changed files with 199 additions and 88 deletions

View file

@ -19,6 +19,7 @@ import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/command_palette/command_palette.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -233,22 +234,34 @@ class _ApplicationWidgetState extends State<ApplicationWidget> {
supportedLocales: context.supportedLocales,
locale: state.locale,
routerConfig: routerConfig,
builder: (context, child) => MediaQuery(
// use the 1.0 as the textScaleFactor to avoid the text size
// affected by the system setting.
data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.linear(state.textScaleFactor),
),
child: overlayManagerBuilder(
context,
!UniversalPlatform.isMobile && FeatureFlag.search.isOn
? CommandPalette(
notifier: _commandPaletteNotifier,
child: child,
)
: child,
),
),
builder: (context, child) {
final themeBuilder = AppFlowyDefaultTheme();
final brightness = Theme.of(context).brightness;
return AppFlowyTheme(
data: brightness == Brightness.light
? themeBuilder.light()
: themeBuilder.dark(),
child: MediaQuery(
// use the 1.0 as the textScaleFactor to avoid the text size
// affected by the system setting.
data: MediaQuery.of(context).copyWith(
textScaler:
TextScaler.linear(state.textScaleFactor),
),
child: overlayManagerBuilder(
context,
!UniversalPlatform.isMobile &&
FeatureFlag.search.isOn
? CommandPalette(
notifier: _commandPaletteNotifier,
child: child,
)
: child,
),
),
);
},
),
),
),

View file

@ -1,5 +1,4 @@
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra/theme_extension.dart';
@ -46,8 +45,6 @@ class DesktopAppearance extends BaseAppearance {
shadow: theme.shadow,
);
final newThemeBuilder = AppFlowyDefaultTheme();
// Due to Desktop version has multiple themes, it relies on the current theme to build the ThemeData
return ThemeData(
visualDensity: VisualDensity.standard,
@ -154,9 +151,6 @@ class DesktopAppearance extends BaseAppearance {
lightIconColor: theme.lightIconColor,
toolbarHoverColor: theme.toolbarHoverColor,
),
AppFlowyTheme(
themeData: isLight ? newThemeBuilder.light() : newThemeBuilder.dark(),
),
],
);
}

View file

@ -1,7 +1,6 @@
// ThemeData in mobile
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_toolbar_theme.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra/theme_extension.dart';
@ -75,8 +74,6 @@ class MobileAppearance extends BaseAppearance {
final onBackground = isLight ? _onBackgroundColor : Colors.white;
final background = isLight ? Colors.white : const Color(0xff121212);
final newThemeBuilder = AppFlowyDefaultTheme();
return ThemeData(
useMaterial3: false,
primaryColor: colorTheme.primary, //primary 100
@ -281,9 +278,6 @@ class MobileAppearance extends BaseAppearance {
toolbarHoverColor: theme.toolbarHoverColor,
),
ToolbarColorExtension.fromBrightness(brightness),
AppFlowyTheme(
themeData: isLight ? newThemeBuilder.light() : newThemeBuilder.dark(),
),
],
);
}

View file

@ -27,25 +27,22 @@ class MyApp extends StatelessWidget {
valueListenable: themeMode,
builder: (context, themeMode, child) {
final themeBuilder = AppFlowyDefaultTheme();
ThemeData themeData =
final themeData =
themeMode == ThemeMode.light ? ThemeData.light() : ThemeData.dark();
themeData = themeData.copyWith(
visualDensity: VisualDensity.standard,
extensions: [
AppFlowyTheme(
themeData: themeMode == ThemeMode.light
? themeBuilder.light()
: themeBuilder.dark(),
),
],
);
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'AppFlowy UI Example',
theme: themeData,
home: const MyHomePage(
title: 'AppFlowy UI',
return AppFlowyTheme(
data: themeMode == ThemeMode.light
? themeBuilder.light()
: themeBuilder.dark(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'AppFlowy UI Example',
theme: themeData.copyWith(
visualDensity: VisualDensity.standard,
),
home: const MyHomePage(
title: 'AppFlowy UI',
),
),
);
},

View file

@ -1,42 +1,153 @@
import 'package:appflowy_ui/src/theme/theme.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
/// [AppFlowyTheme] relies on the Material library's [ThemeData] extensions to
/// handle dependency injection into the widget tree.
///
/// See also:
///
/// - [AppFlowyThemeData], which contains the actual theme data
class AppFlowyTheme extends ThemeExtension<AppFlowyTheme> {
const AppFlowyTheme({required this.themeData});
class AppFlowyTheme extends StatelessWidget {
const AppFlowyTheme({
super.key,
required this.data,
required this.child,
});
static AppFlowyThemeData of(BuildContext context) =>
Theme.of(context).extension<AppFlowyTheme>()!.themeData;
final AppFlowyThemeData data;
final Widget child;
static AppFlowyThemeData? maybeOf(BuildContext context) =>
Theme.of(context).extension<AppFlowyTheme>()?.themeData;
static AppFlowyThemeData of(BuildContext context, {bool listen = true}) {
final provider = maybeOf(context, listen: listen);
if (provider == null) {
throw FlutterError(
'''
AppFlowyTheme.of() called with a context that does not contain a AppFlowyTheme.\n
No AppFlowyTheme ancestor could be found starting from the context that was passed to AppFlowyTheme.of().
This can happen because you do not have a AppFlowyTheme widget (which introduces a AppFlowyTheme),
or it can happen if the context you use comes from a widget above this widget.\n
The context used was: $context''',
);
}
return provider;
}
final AppFlowyThemeData themeData;
@override
ThemeExtension<AppFlowyTheme> copyWith({
AppFlowyThemeData? themeData,
static AppFlowyThemeData? maybeOf(
BuildContext context, {
bool listen = true,
}) {
return AppFlowyTheme(
themeData: themeData ?? this.themeData,
);
if (listen) {
return context
.dependOnInheritedWidgetOfExactType<AppFlowyInheritedTheme>()
?.theme
.data;
}
final provider = context
.getElementForInheritedWidgetOfExactType<AppFlowyInheritedTheme>()
?.widget;
return (provider as AppFlowyInheritedTheme?)?.theme.data;
}
@override
ThemeExtension<AppFlowyTheme> lerp(
covariant ThemeExtension<AppFlowyTheme>? other,
double t,
) {
if (other is! AppFlowyTheme) {
return this;
}
return AppFlowyTheme(
themeData: themeData.lerp(other.themeData, t),
Widget build(BuildContext context) {
return AppFlowyInheritedTheme(
theme: this,
child: child,
);
}
}
class AppFlowyInheritedTheme extends InheritedTheme {
const AppFlowyInheritedTheme({
super.key,
required this.theme,
required super.child,
});
final AppFlowyTheme theme;
@override
Widget wrap(BuildContext context, Widget child) {
return AppFlowyTheme(data: theme.data, child: child);
}
@override
bool updateShouldNotify(AppFlowyInheritedTheme oldWidget) =>
theme.data != oldWidget.theme.data;
}
/// An interpolation between two [ThemeData]s.
///
/// This class specializes the interpolation of [Tween<ThemeData>] to call the
/// [ThemeData.lerp] method.
///
/// See [Tween] for a discussion on how to use interpolation objects.
class AppFlowyThemeDataTween extends Tween<AppFlowyThemeData> {
/// Creates a [AppFlowyThemeData] tween.
///
/// The [begin] and [end] properties must be non-null before the tween is
/// first used, but the arguments can be null if the values are going to be
/// filled in later.
AppFlowyThemeDataTween({super.begin, super.end});
@override
AppFlowyThemeData lerp(double t) => AppFlowyThemeData.lerp(begin!, end!, t);
}
class AnimatedAppFlowyTheme extends ImplicitlyAnimatedWidget {
/// Creates an animated theme.
///
/// By default, the theme transition uses a linear curve.
const AnimatedAppFlowyTheme({
super.key,
required this.data,
super.curve,
super.duration = kThemeAnimationDuration,
super.onEnd,
required this.child,
});
/// Specifies the color and typography values for descendant widgets.
final AppFlowyThemeData data;
/// The widget below this widget in the tree.
///
/// {@macro flutter.widgets.ProxyWidget.child}
final Widget child;
@override
AnimatedWidgetBaseState<AnimatedAppFlowyTheme> createState() =>
_AnimatedThemeState();
}
class _AnimatedThemeState
extends AnimatedWidgetBaseState<AnimatedAppFlowyTheme> {
AppFlowyThemeDataTween? data;
@override
void forEachTween(TweenVisitor<dynamic> visitor) {
data = visitor(
data,
widget.data,
(dynamic value) =>
AppFlowyThemeDataTween(begin: value as AppFlowyThemeData),
)! as AppFlowyThemeDataTween;
}
@override
Widget build(BuildContext context) {
return AppFlowyTheme(
data: data!.evaluate(animation),
child: widget.child,
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(
DiagnosticsProperty<AppFlowyThemeDataTween>(
'data',
data,
showName: false,
defaultValue: null,
),
);
}
}

View file

@ -46,25 +46,27 @@ class AppFlowyThemeData {
final AppFlowyOtherColorsColorScheme otherColorsColorScheme;
AppFlowyThemeData lerp(
AppFlowyThemeData other,
static AppFlowyThemeData lerp(
AppFlowyThemeData begin,
AppFlowyThemeData end,
double t,
) {
return AppFlowyThemeData(
textColorScheme: textColorScheme.lerp(other.textColorScheme, t),
textStyle: other.textStyle,
iconColorScheme: iconColorScheme.lerp(other.iconColorScheme, t),
borderColorScheme: borderColorScheme.lerp(other.borderColorScheme, t),
textColorScheme: begin.textColorScheme.lerp(end.textColorScheme, t),
textStyle: end.textStyle,
iconColorScheme: begin.iconColorScheme.lerp(end.iconColorScheme, t),
borderColorScheme: begin.borderColorScheme.lerp(end.borderColorScheme, t),
backgroundColorScheme:
backgroundColorScheme.lerp(other.backgroundColorScheme, t),
fillColorScheme: fillColorScheme.lerp(other.fillColorScheme, t),
surfaceColorScheme: surfaceColorScheme.lerp(other.surfaceColorScheme, t),
borderRadius: other.borderRadius,
spacing: other.spacing,
shadow: other.shadow,
brandColorScheme: brandColorScheme.lerp(other.brandColorScheme, t),
begin.backgroundColorScheme.lerp(end.backgroundColorScheme, t),
fillColorScheme: begin.fillColorScheme.lerp(end.fillColorScheme, t),
surfaceColorScheme:
begin.surfaceColorScheme.lerp(end.surfaceColorScheme, t),
borderRadius: end.borderRadius,
spacing: end.spacing,
shadow: end.shadow,
brandColorScheme: begin.brandColorScheme.lerp(end.brandColorScheme, t),
otherColorsColorScheme:
otherColorsColorScheme.lerp(other.otherColorsColorScheme, t),
begin.otherColorsColorScheme.lerp(end.otherColorsColorScheme, t),
);
}
}