diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d554425..9a39bb59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [0.1.1+1] - 2023-12-01 + +- feature: Refactor theme declaration and introduce theme service + ## [0.1.0+1] - 2023-11-28 - chore: Tidy, reorganise and prepare repo diff --git a/example/lib/main.dart b/example/lib/main.dart index 19c69098..86099838 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,15 +1,47 @@ import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:zeta_example/theme_service.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; import 'home.dart'; -void main() => runApp(const ZetaExample()); +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + final preferences = await SharedPreferences.getInstance(); + final themeService = SharedPrefsThemeService(preferences); + final themPreferences = await themeService.loadTheme(); + + runApp( + ZetaExample( + themeService: themeService, + initialThemeData: themPreferences.$1 ?? ZetaThemeData(), + initialThemeMode: themPreferences.$2 ?? ThemeMode.system, + initialContrast: themPreferences.$3 ?? ZetaContrast.aa, + ), + ); +} class ZetaExample extends StatelessWidget { - const ZetaExample({super.key}); + const ZetaExample({ + super.key, + required this.themeService, + required this.initialContrast, + required this.initialThemeMode, + required this.initialThemeData, + }); + + final ZetaThemeService themeService; + final ZetaContrast initialContrast; + final ThemeMode initialThemeMode; + final ZetaThemeData initialThemeData; @override Widget build(BuildContext context) { return ZetaProvider( + themeService: themeService, + initialContrast: initialContrast, + initialThemeData: initialThemeData, + initialThemeMode: initialThemeMode, builder: (context, themeData, themeMode) { final dark = themeData.colorsDark.toScheme(); final light = themeData.colorsLight.toScheme(); diff --git a/example/lib/pages/theme_color_switch.dart b/example/lib/pages/theme_color_switch.dart index a2ca12fc..98ebbeec 100644 --- a/example/lib/pages/theme_color_switch.dart +++ b/example/lib/pages/theme_color_switch.dart @@ -1,29 +1,29 @@ import 'package:flutter/material.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; +late final appThemes = { + "default": ZetaThemeData(), + "teal": ZetaThemeData( + identifier: 'teal', + primary: ZetaColorBase.teal, + ), + "yellow": ZetaThemeData( + identifier: 'yellow', + primary: ZetaColorBase.yellow, + ), + "red": ZetaThemeData( + identifier: 'red', + primary: ZetaColorBase.red, + ), + "purple": ZetaThemeData( + identifier: 'purple', + primary: ZetaColorBase.purple, + ), +}; + class ZetaThemeColorSwitch extends StatelessWidget { ZetaThemeColorSwitch({super.key}); - late final _themes = { - "default": ZetaThemeData(), - "teal": ZetaThemeData( - identifier: 'teal', - primary: ZetaColorBase.teal, - ), - "yellow": ZetaThemeData( - identifier: 'yellow', - primary: ZetaColorBase.yellow, - ), - "red": ZetaThemeData( - identifier: 'red', - primary: ZetaColorBase.red, - ), - "purple": ZetaThemeData( - identifier: 'purple', - primary: ZetaColorBase.purple, - ), - }; - @override Widget build(BuildContext context) { var zeta = Zeta.of(context); @@ -44,8 +44,8 @@ class ZetaThemeColorSwitch extends StatelessWidget { alignment: Alignment.center, icon: SizedBox(width: 8), dropdownColor: zeta.colors.borderDisabled, - items: _themes.entries.map((e) { - var zetaColors = primary(_themes[e.key]!); + items: appThemes.entries.map((e) { + var zetaColors = primary(appThemes[e.key]!); var color = zetaColors.primary; return DropdownMenuItem( value: e.value.identifier, @@ -58,7 +58,7 @@ class ZetaThemeColorSwitch extends StatelessWidget { ); }).toList(), onChanged: (value) { - final theme = _themes[value]; + final theme = appThemes[value]; if (theme != null) { ZetaProvider.of(context).updateThemeData(theme); } diff --git a/example/lib/theme_service.dart b/example/lib/theme_service.dart new file mode 100644 index 00000000..b25c0a45 --- /dev/null +++ b/example/lib/theme_service.dart @@ -0,0 +1,47 @@ +import 'package:flutter/src/material/app.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:zeta_example/pages/theme_color_switch.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +class SharedPrefsThemeService extends ZetaThemeService { + SharedPrefsThemeService(this.preferences); + + final SharedPreferences preferences; + + @override + Future<(ZetaThemeData?, ThemeMode?, ZetaContrast?)> loadTheme() async { + final theme = preferences.getString('theme') ?? 'default'; + final themeData = appThemes[theme]; + + final modeString = preferences.getString('themeMode'); + final themeMode = modeString == 'light' + ? ThemeMode.light + : modeString == 'dark' + ? ThemeMode.dark + : ThemeMode.system; + + final contrastString = preferences.getString('contrast'); + final contrast = contrastString == 'aaa' ? ZetaContrast.aaa : ZetaContrast.aa; + + return (themeData, themeMode, contrast); + } + + @override + Future saveTheme({ + required ZetaThemeData themeData, + required ThemeMode themeMode, + required ZetaContrast contrast, + }) async { + await preferences.setString('theme', themeData.identifier); + + final modeString = themeMode == ThemeMode.light + ? 'light' + : themeMode == ThemeMode.dark + ? 'dark' + : 'system'; + await preferences.setString('themeMode', modeString); + + final contrastString = contrast == ZetaContrast.aaa ? 'aaa' : 'aa'; + await preferences.setString('contrast', contrastString); + } +} diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift deleted file mode 100644 index e777c67d..00000000 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Generated file. Do not edit. -// - -import FlutterMacOS -import Foundation - -import path_provider_foundation - -func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) -} diff --git a/example/pubspec.yaml b/example/pubspec.yaml index f6c712a9..74d5c998 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -4,13 +4,14 @@ version: 0.0.1 publish_to: "none" environment: - sdk: ">=2.19.1 <3.0.0" + sdk: ">=3.0.1 <4.0.0" dependencies: flutter: sdk: flutter go_router: ^11.1.2 google_fonts: ^6.1.0 + shared_preferences: ^2.2.2 zeta_flutter: path: ../ diff --git a/lib/src/theme/color_swatch.dart b/lib/src/theme/color_swatch.dart index 5b8f8acd..405a0440 100644 --- a/lib/src/theme/color_swatch.dart +++ b/lib/src/theme/color_swatch.dart @@ -27,6 +27,7 @@ class ZetaColorSwatch extends ColorSwatch { /// /// It ensures that the 60th and 80th shades from swatch are abide by the AA and AAA accessibility standards on [background], respectively. /// [background] color defaults to [ZetaColorBase.greyWarm] shade10. + /// {@endtemplate} factory ZetaColorSwatch.fromColor( Color primary, { Brightness brightness = Brightness.light, diff --git a/lib/src/theme/colors.dart b/lib/src/theme/colors.dart index c54f270f..9abdc4f3 100644 --- a/lib/src/theme/colors.dart +++ b/lib/src/theme/colors.dart @@ -218,13 +218,13 @@ class ZetaColors { // Text / icons. + /// {@template zeta-color-dark} /// Default text /icon color. /// /// Defaults to `ZetaColors.cool.90`. /// - /// {@template zeta-color-dark} /// Color swatches are inverted if [ZetaColors.brightness] is Dark. - /// {@endTemplate} + /// {@endtemplate} Color get textDefault => cool.shade90; /// Subtle text /icon color. @@ -252,13 +252,13 @@ class ZetaColors { /// {@macro zeta-color-dark} Color get textInverse => cool.shade20; + /// {@template zeta-color-dark} /// Default icon color. /// /// Defaults to `ZetaColors.cool.90`. /// - /// {@template zeta-color-dark} /// Color swatches are inverted if [ZetaColors.brightness] is Dark. - /// {@endTemplate} + /// {@endtemplate} Color get iconDefault => textDefault; /// Subtle icon color. diff --git a/lib/src/theme/theme_service.dart b/lib/src/theme/theme_service.dart index bbc63ee5..84b243ad 100644 --- a/lib/src/theme/theme_service.dart +++ b/lib/src/theme/theme_service.dart @@ -1,17 +1,25 @@ +import 'package:flutter/material.dart'; + +import 'contrast.dart'; import 'theme_data.dart'; /// `ZetaThemeService` is an abstract class. /// It provides the structure for loading and saving themes in Zeta application. abstract class ZetaThemeService { - /// Loads the application's theme. + /// Asynchronously load the theme data. /// - /// `loadTheme` is a method to retrieve the current theme data. + /// This method returns a `Future` that when complete will produce a + /// tuple of `ZetaThemeData`, `ThemeMode`, and `ZetaContrast`. /// - /// Returns a `Future` that completes with the `ZetaThemeData` object - /// that represents the current theme. - - Future loadTheme(); + /// `ZetaThemeData` describes the colors that are used by a theme. + /// + /// `ThemeMode` determines the brightness of the system. + /// + /// `ZetaContrast` defines different contrast styles to use across the application. + /// + /// Returns a Future `(ZetaThemeData?, ThemeMode?, ZetaContrast?)`. + Future<(ZetaThemeData?, ThemeMode?, ZetaContrast?)> loadTheme(); /// Saves the provided theme data as the application's theme. /// @@ -21,5 +29,9 @@ abstract class ZetaThemeService { /// /// Returns a `Future` that completes when the theme data has been successfully saved. - Future saveTheme(ZetaThemeData themeData); + Future saveTheme({ + required ZetaThemeData themeData, + required ThemeMode themeMode, + required ZetaContrast contrast, + }); } diff --git a/lib/src/zeta.dart b/lib/src/zeta.dart index cf8a7c42..e28de7e3 100644 --- a/lib/src/zeta.dart +++ b/lib/src/zeta.dart @@ -248,17 +248,6 @@ class ZetaProviderState extends State with Diagnosticable, Widgets // Apply the initial contrast to the theme data. _themeData = widget.initialThemeData.apply(contrast: _contrast); - - // Load theme data from themeService if available. - unawaited( - widget.themeService?.loadTheme().then((value) { - if (value != null) { - setState(() { - _themeData = value; - }); - } - }), - ); } /// Clean up function to be called when this object is removed from the tree. @@ -332,6 +321,7 @@ class ZetaProviderState extends State with Diagnosticable, Widgets void updateThemeMode(ThemeMode themeMode) { setState(() { _themeMode = themeMode; + _saveThemeChange(); }); } @@ -339,7 +329,7 @@ class ZetaProviderState extends State with Diagnosticable, Widgets void updateThemeData(ZetaThemeData data) { setState(() { _themeData = data.apply(contrast: _contrast); - unawaited(widget.themeService?.saveTheme(data)); + _saveThemeChange(); }); } @@ -348,9 +338,20 @@ class ZetaProviderState extends State with Diagnosticable, Widgets setState(() { _contrast = contrast; _themeData = _themeData.apply(contrast: contrast); + _saveThemeChange(); }); } + void _saveThemeChange() { + unawaited( + widget.themeService?.saveTheme( + themeData: _themeData, + themeMode: _themeMode, + contrast: _contrast, + ), + ); + } + @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); diff --git a/lib/zeta_flutter.dart b/lib/zeta_flutter.dart index eecb1b7a..576d2887 100644 --- a/lib/zeta_flutter.dart +++ b/lib/zeta_flutter.dart @@ -10,6 +10,7 @@ export 'src/theme/color_swatch.dart'; export 'src/theme/constants.dart'; export 'src/theme/contrast.dart'; export 'src/theme/theme_data.dart'; +export 'src/theme/theme_service.dart'; export 'src/tokens.dart'; export 'src/utils/extensions.dart'; export 'src/zeta.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 564d0e43..4a8e8fba 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: zeta_flutter -version: 0.1.0+1 +version: 0.1.1+1 description: Zeta is the new, formal, standardized Zebra Design System based off the successes of ZDS (Zebra Design System). This package is in pre-release, and so many aspects are incomplete. homepage: https://github.com/zebradevs/zeta_flutter repository: https://github.com/zebradevs/zeta_flutter