diff --git a/lib/platform_ui.dart b/lib/platform_ui.dart index 58685ed..19e83e3 100644 --- a/lib/platform_ui.dart +++ b/lib/platform_ui.dart @@ -16,3 +16,4 @@ export 'src/platform_tab_view.dart'; export 'src/platform_property.dart'; export 'src/platform_app_bar.dart'; export 'src/platform_sidebar.dart'; +export 'src/platform_theme.dart'; diff --git a/lib/src/platform_app.dart b/lib/src/platform_app.dart index 2ad96e5..f83e157 100644 --- a/lib/src/platform_app.dart +++ b/lib/src/platform_app.dart @@ -9,6 +9,7 @@ import 'package:macos_ui/macos_ui.dart'; import 'package:platform_ui/src/platform_app_router.dart'; import 'package:platform_ui/src/platform_mixin.dart'; import 'package:platform_ui/src/platform_property.dart'; +import 'package:platform_ui/src/platform_theme.dart'; // TODO: Implement [PlatformTheme] @@ -139,7 +140,7 @@ class PlatformApp extends StatelessWidget with PlatformMixin { routeInformationParser: routeInformationParser, routerDelegate: routerDelegate, backButtonDispatcher: backButtonDispatcher, - builder: builder, + builder: buildPlatformTheme, title: title, onGenerateTitle: onGenerateTitle, androidTheme: androidTheme, @@ -178,6 +179,13 @@ class PlatformApp extends StatelessWidget with PlatformMixin { ); } + static Widget buildPlatformTheme(BuildContext context, Widget? child) { + return PlatformTheme( + theme: PlatformThemeData.fromContext(context), + child: child ?? Container(), + ); + } + @override Widget android(context) { return MaterialApp( @@ -190,7 +198,7 @@ class PlatformApp extends StatelessWidget with PlatformMixin { onGenerateInitialRoutes: onGenerateInitialRoutes, onUnknownRoute: onUnknownRoute, navigatorObservers: navigatorObservers!, - builder: builder, + builder: buildPlatformTheme, title: title, onGenerateTitle: onGenerateTitle, theme: androidTheme, @@ -235,7 +243,7 @@ class PlatformApp extends StatelessWidget with PlatformMixin { onGenerateInitialRoutes: onGenerateInitialRoutes, onUnknownRoute: onUnknownRoute, navigatorObservers: navigatorObservers!, - builder: builder, + builder: buildPlatformTheme, title: title, onGenerateTitle: onGenerateTitle, theme: iosTheme, @@ -280,7 +288,7 @@ class PlatformApp extends StatelessWidget with PlatformMixin { onGenerateInitialRoutes: onGenerateInitialRoutes, onUnknownRoute: onUnknownRoute, navigatorObservers: navigatorObservers!, - builder: builder, + builder: buildPlatformTheme, title: title, onGenerateTitle: onGenerateTitle, theme: macosTheme, @@ -321,7 +329,7 @@ class PlatformApp extends StatelessWidget with PlatformMixin { onGenerateInitialRoutes: onGenerateInitialRoutes, onUnknownRoute: onUnknownRoute, navigatorObservers: navigatorObservers!, - builder: builder, + builder: buildPlatformTheme, title: title, onGenerateTitle: onGenerateTitle, theme: windowsTheme, diff --git a/lib/src/platform_filled_button.dart b/lib/src/platform_filled_button.dart index f990f64..dd3d5fc 100644 --- a/lib/src/platform_filled_button.dart +++ b/lib/src/platform_filled_button.dart @@ -116,20 +116,36 @@ class PlatformFilledButton extends StatelessWidget with PlatformMixin { cursor: mouseCursor, child: GestureDetector( onLongPress: onLongPress, - child: PushButton( - onPressed: onPressed, - pressedOpacity: macOSiOSPressedOpacity, - borderRadius: - borderRadius ?? const BorderRadius.all(Radius.circular(4.0)), - buttonSize: macOSButtonSize, - isSecondary: macOSIsSecondary, - mouseCursor: mouseCursor, - color: style?.backgroundColor?.resolve(allStates), - disabledColor: - style?.backgroundColor?.resolve({MaterialState.disabled}) ?? - CupertinoColors.quaternarySystemFill, - padding: style?.padding?.resolve(allStates), - child: child, + child: IconTheme( + data: IconTheme.of(context).copyWith( + color: + style?.foregroundColor?.resolve(Utils.allMaterialStates) ?? + MacosTheme.of(context).pushButtonTheme.secondaryColor, + ), + child: PushButton( + onPressed: onPressed, + pressedOpacity: macOSiOSPressedOpacity, + borderRadius: borderRadius ?? + const BorderRadius.all(Radius.circular(4.0)), + buttonSize: macOSButtonSize, + isSecondary: macOSIsSecondary, + mouseCursor: mouseCursor, + color: style?.backgroundColor?.resolve(allStates), + disabledColor: + style?.backgroundColor?.resolve({MaterialState.disabled}) ?? + CupertinoColors.quaternarySystemFill, + padding: style?.padding?.resolve(allStates), + child: DefaultTextStyle( + style: MacosTheme.of(context).typography.body.copyWith( + color: style?.foregroundColor + ?.resolve(Utils.allMaterialStates) ?? + MacosTheme.of(context) + .pushButtonTheme + .secondaryColor, + ), + child: child, + ), + ), ), ), ), diff --git a/lib/src/platform_property.dart b/lib/src/platform_property.dart index 496da7b..f3026fd 100644 --- a/lib/src/platform_property.dart +++ b/lib/src/platform_property.dart @@ -32,6 +32,23 @@ class PlatformProperty { } } + factory PlatformProperty.only({ + T? android, + T? ios, + T? macos, + T? linux, + T? windows, + required T other, + }) { + return PlatformProperty( + android: android ?? other, + ios: ios ?? other, + macos: macos ?? other, + linux: linux ?? other, + windows: windows ?? other, + ); + } + factory PlatformProperty.all(T value) { return PlatformProperty( android: value, @@ -59,7 +76,8 @@ class PlatformProperty { Map> groups) { assert(groups.isNotEmpty); - final platforms = TargetPlatform.values..remove(TargetPlatform.fuchsia); + final platforms = List.from(TargetPlatform.values) + ..remove(TargetPlatform.fuchsia); assert(groups.values.expand((element) => element).length == platforms.length); diff --git a/lib/src/platform_sidebar.dart b/lib/src/platform_sidebar.dart index 01627ce..1ad0e34 100644 --- a/lib/src/platform_sidebar.dart +++ b/lib/src/platform_sidebar.dart @@ -3,6 +3,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:platform_ui/platform_ui.dart'; +import 'package:collection/collection.dart'; class PlatformSidebarItem { final Widget title; @@ -25,10 +26,18 @@ class PlatformSidebarItem { ); } - SidebarItem macos() { + SidebarItem macos(BuildContext context, bool isActive) { return SidebarItem( label: title, - leading: icon, + leading: IconTheme( + data: IconTheme.of(context).copyWith( + color: isActive + ? MacosTheme.of(context).canvasColor + : MacosTheme.of(context).primaryColor, + size: 16, + ), + child: icon, + ), ); } @@ -207,7 +216,9 @@ class _PlatformSidebarState extends State return SidebarItems( currentIndex: currentIndex, onChanged: onIndexChanged, - items: widget.body.keys.map((e) => e.macos()).toList(), + items: widget.body.keys + .mapIndexed((i, e) => e.macos(context, i == currentIndex)) + .toList(), ); }, ), diff --git a/lib/src/platform_text_button.dart b/lib/src/platform_text_button.dart index 6d9479a..02d9310 100644 --- a/lib/src/platform_text_button.dart +++ b/lib/src/platform_text_button.dart @@ -4,6 +4,7 @@ import 'package:fluent_ui/fluent_ui.dart' as FluentUI; import 'package:flutter/cupertino.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:macos_ui/macos_ui.dart'; import 'package:platform_ui/platform_ui.dart'; import "package:platform_ui/src/utils.dart"; @@ -108,7 +109,40 @@ class PlatformTextButton extends StatelessWidget with PlatformMixin { @override Widget macos(context) { - return ios(context); + return ClipRect( + clipBehavior: clipBehavior, + child: Focus( + autofocus: autofocus, + focusNode: focusNode, + child: MouseRegion( + onHover: onHover, + cursor: mouseCursor, + child: GestureDetector( + onLongPress: onLongPress, + child: CupertinoButton( + color: style?.foregroundColor?.resolve(allStates), + onPressed: onPressed, + pressedOpacity: macOSiOSPressedOpacity, + borderRadius: + borderRadius ?? const BorderRadius.all(Radius.circular(8.0)), + minSize: style?.minimumSize?.resolve(allStates)?.width, + disabledColor: + style?.backgroundColor?.resolve({MaterialState.disabled}) ?? + CupertinoColors.quaternarySystemFill, + padding: style?.padding?.resolve(allStates), + child: DefaultTextStyle( + style: MacosTheme.of(context).typography.body.copyWith( + color: style?.foregroundColor + ?.resolve(Utils.allMaterialStates) ?? + MacosTheme.of(context).primaryColor, + ), + child: child, + ), + ), + ), + ), + ), + ); } @override diff --git a/lib/src/platform_theme.dart b/lib/src/platform_theme.dart new file mode 100644 index 0000000..a9ee464 --- /dev/null +++ b/lib/src/platform_theme.dart @@ -0,0 +1,210 @@ +import 'package:fluent_ui/fluent_ui.dart' hide Colors; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:macos_ui/macos_ui.dart'; +import 'package:platform_ui/platform_ui.dart'; + +class PlatformTextTheme { + TextStyle? body; + TextStyle? headline; + TextStyle? caption; + TextStyle? label; + TextStyle? subheading; + + PlatformTextTheme({ + this.body, + this.headline, + this.caption, + this.label, + this.subheading, + }); + + @override + operator ==(other) { + if (other is PlatformTextTheme) { + return body == other.body && + headline == other.headline && + caption == other.caption && + label == other.label && + subheading == other.subheading; + } + return false; + } + + @override + int get hashCode { + return Object.hash( + body, + headline, + caption, + label, + subheading, + ); + } +} + +class PlatformThemeData { + Color? secondaryBackgroundColor; + Color? scaffoldBackgroundColor; + Color? primaryColor; + Color? shadowColor; + Color? borderColor; + IconThemeData? iconTheme; + PlatformTextTheme? textTheme; + + PlatformThemeData({ + this.secondaryBackgroundColor, + this.scaffoldBackgroundColor, + this.primaryColor, + this.shadowColor, + this.borderColor, + this.iconTheme, + this.textTheme, + }); + + factory PlatformThemeData.fromContext(BuildContext context) { + final androidTheme = Theme.of(context); + final macosTheme = MacosTheme.of(context); + final iosTheme = CupertinoTheme.of(context); + final windowsTheme = FluentTheme.maybeOf(context); + + final currentPlatform = platform ?? androidTheme.platform; + + return PlatformThemeData( + iconTheme: PlatformProperty( + android: androidTheme.iconTheme, + linux: androidTheme.iconTheme, + ios: androidTheme.iconTheme, + macos: IconThemeData( + color: macosTheme.iconTheme.color, + opacity: macosTheme.iconTheme.opacity, + size: macosTheme.iconTheme.size, + ), + windows: windowsTheme?.iconTheme, + ).resolve(currentPlatform), + primaryColor: PlatformProperty( + android: androidTheme.primaryColor, + linux: androidTheme.primaryColor, + ios: iosTheme.primaryColor, + macos: macosTheme.primaryColor, + windows: windowsTheme?.accentColor, + ).resolve(currentPlatform), + scaffoldBackgroundColor: PlatformProperty( + android: androidTheme.scaffoldBackgroundColor, + linux: androidTheme.scaffoldBackgroundColor, + ios: iosTheme.scaffoldBackgroundColor, + macos: macosTheme.canvasColor, + windows: windowsTheme?.scaffoldBackgroundColor, + ).resolve(currentPlatform), + secondaryBackgroundColor: PlatformProperty( + android: androidTheme.cardColor, + linux: androidTheme.cardColor, + ios: iosTheme.barBackgroundColor, + macos: macosTheme.pulldownButtonTheme.backgroundColor, + windows: windowsTheme?.acrylicBackgroundColor, + ).resolve(currentPlatform), + textTheme: PlatformTextTheme( + body: PlatformProperty( + android: androidTheme.textTheme.bodyText1, + linux: androidTheme.textTheme.bodyText1, + ios: iosTheme.textTheme.textStyle, + macos: macosTheme.typography.body, + windows: windowsTheme?.typography.body, + ).resolve(currentPlatform), + headline: PlatformProperty( + android: androidTheme.textTheme.headline6, + linux: androidTheme.textTheme.headline6, + ios: iosTheme.textTheme.navLargeTitleTextStyle, + macos: macosTheme.typography.headline, + windows: windowsTheme?.typography.title, + ).resolve(currentPlatform), + caption: PlatformProperty( + android: androidTheme.textTheme.caption, + linux: androidTheme.textTheme.caption, + ios: iosTheme.textTheme.tabLabelTextStyle, + macos: macosTheme.typography.caption1, + windows: windowsTheme?.typography.caption, + ).resolve(currentPlatform), + label: PlatformProperty( + android: androidTheme.textTheme.labelMedium, + linux: androidTheme.textTheme.labelMedium, + ios: iosTheme.textTheme.navActionTextStyle, + macos: macosTheme.typography.caption2, + windows: windowsTheme?.typography.subtitle, + ).resolve(currentPlatform), + subheading: PlatformProperty( + android: androidTheme.textTheme.headline4, + linux: androidTheme.textTheme.headline4, + ios: iosTheme.textTheme.navTitleTextStyle, + macos: macosTheme.typography.subheadline, + windows: windowsTheme?.typography.bodyLarge + ?.merge(windowsTheme.typography.bodyStrong), + ).resolve(currentPlatform), + ), + borderColor: PlatformProperty( + android: androidTheme.dividerColor, + linux: androidTheme.dividerColor, + ios: iosTheme.primaryContrastingColor, + macos: macosTheme.dividerColor, + windows: + (windowsTheme?.dividerTheme.decoration as BoxDecoration?)?.color ?? + windowsTheme?.resources.dividerStrokeColorDefault, + ).resolve(currentPlatform), + shadowColor: PlatformProperty( + android: androidTheme.shadowColor, + linux: androidTheme.shadowColor, + ios: Colors.transparent, + macos: Colors.transparent, + windows: windowsTheme?.shadowColor, + ).resolve(currentPlatform), + ); + } + + @override + operator ==(other) { + return other is PlatformThemeData && + other.secondaryBackgroundColor == secondaryBackgroundColor && + other.scaffoldBackgroundColor == scaffoldBackgroundColor && + other.primaryColor == primaryColor && + other.shadowColor == shadowColor && + other.borderColor == borderColor && + other.iconTheme == iconTheme && + other.textTheme == textTheme; + } + + @override + int get hashCode { + return Object.hash( + secondaryBackgroundColor, + scaffoldBackgroundColor, + primaryColor, + shadowColor, + borderColor, + iconTheme, + textTheme, + ); + } +} + +class PlatformTheme extends InheritedWidget { + final PlatformThemeData theme; + + const PlatformTheme({ + required this.theme, + required super.child, + super.key, + }); + + static PlatformTheme of(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType()!; + } + + static PlatformTheme? maybeOf(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType(); + } + + @override + bool updateShouldNotify(covariant PlatformTheme oldWidget) { + return oldWidget.theme != theme; + } +}