diff --git a/assets/fonts/Inter-VariableFont_slnt,wght.ttf b/assets/fonts/Inter-VariableFont_slnt,wght.ttf new file mode 100644 index 00000000..e7247087 Binary files /dev/null and b/assets/fonts/Inter-VariableFont_slnt,wght.ttf differ diff --git a/assets/fonts/Lexend-VariableFont_wght.ttf b/assets/fonts/Lexend-VariableFont_wght.ttf new file mode 100644 index 00000000..b294dc84 Binary files /dev/null and b/assets/fonts/Lexend-VariableFont_wght.ttf differ diff --git a/lib/src/_conf/adapters.hive.dart b/lib/src/_conf/adapters.hive.dart new file mode 100644 index 00000000..1f69121a --- /dev/null +++ b/lib/src/_conf/adapters.hive.dart @@ -0,0 +1,37 @@ +import 'package:autojidelna/src/types/all.dart'; +import 'package:autojidelna/src/types/theme.dart'; +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; + +class ThemeModeAdapter extends TypeAdapter { + @override + final typeId = 0; // Put an ID you didn't use yet. + + @override + ThemeMode read(BinaryReader reader) => ThemeMode.values[reader.readInt()]; + + @override + void write(BinaryWriter writer, ThemeMode obj) => writer.writeInt(obj.index); +} + +class ThemeStyleAdapter extends TypeAdapter { + @override + final typeId = 1; // Put an ID you didn't use yet. + + @override + ThemeStyle read(BinaryReader reader) => ThemeStyle.values[reader.readInt()]; + + @override + void write(BinaryWriter writer, ThemeStyle obj) => writer.writeInt(obj.index); +} + +class DateFormatOptionsAdapter extends TypeAdapter { + @override + final typeId = 2; // Put an ID you didn't use yet. + + @override + DateFormatOptions read(BinaryReader reader) => DateFormatOptions.values[reader.readInt()]; + + @override + void write(BinaryWriter writer, DateFormatOptions obj) => writer.writeInt(obj.index); +} diff --git a/lib/src/_conf/hive.dart b/lib/src/_conf/hive.dart index d223043a..e04cb194 100644 --- a/lib/src/_conf/hive.dart +++ b/lib/src/_conf/hive.dart @@ -1,7 +1,8 @@ /// Hive boxes. Use these values to open a box. class Boxes { - static const String settings = 'settings'; static const String appState = 'appState'; + static const String theme = 'theme'; + static const String settings = 'settings'; static const String cache = 'cache'; static const String statistics = 'statistics'; static const String notifications = 'notifications'; @@ -18,14 +19,16 @@ class HiveKeys { static const String hideBurzaAlertDialog = 'hideBurzaAlertDialog'; static String location(String userName, String url) => 'location_${userName}_$url'; - //settings box - static const String shouldAskForNotificationPermission = 'shouldAskForNotificationPermission'; - static const String webNotificationsAccepted = 'webNotificationsAccepted'; + // theme box static const String themeMode = 'themeMode'; static const String themeStyle = 'themeStyle'; + static const String amoledMode = 'amoledMode'; + + // settings box + static const String shouldAskForNotificationPermission = 'shouldAskForNotificationPermission'; + static const String webNotificationsAccepted = 'webNotificationsAccepted'; static const String tabletUi = 'tabletUi'; static const String listUi = 'listUi'; - static const String pureBlack = 'amoledMode'; static const String bigCalendarMarkers = 'bigCalendarMarkers'; static const String dateFormat = 'dateFormat'; static const String relTimeStamps = 'relativeTimeStamps'; diff --git a/lib/src/_conf/notifications.dart b/lib/src/_conf/notifications.dart index dc724e0c..881cc2ef 100644 --- a/lib/src/_conf/notifications.dart +++ b/lib/src/_conf/notifications.dart @@ -1,11 +1,5 @@ // Purpose: stores constants used throughout the app. -import 'package:flutter/material.dart'; - -class Locales { - static Locale get csCZ => const Locale('cs', 'CZ'); -} - class NotificationIds { static String kreditChannel(String userName, String url) => 'kredit_channel_${userName}_$url'; static String objednanoChannel(String userName, String url) => 'objednano_channel_${userName}_$url'; diff --git a/lib/src/_global/app.dart b/lib/src/_global/app.dart index 1875e396..345c88df 100644 --- a/lib/src/_global/app.dart +++ b/lib/src/_global/app.dart @@ -1,6 +1,8 @@ +import 'package:autojidelna/src/_conf/adapters.hive.dart'; import 'package:autojidelna/src/_conf/hive.dart'; import 'package:autojidelna/src/_conf/notifications.dart'; import 'package:autojidelna/src/_global/providers/remote_config.dart'; +import 'package:autojidelna/src/lang/supported_locales.dart'; import 'package:autojidelna/src/logic/canteenwrapper.dart'; import 'package:autojidelna/src/logic/notifications.dart'; import 'package:autojidelna/src/types/all.dart'; @@ -147,6 +149,10 @@ class App { if (_initHiveExecuted) return; await Hive.initFlutter(); + Hive.registerAdapter(ThemeModeAdapter()); + Hive.registerAdapter(ThemeStyleAdapter()); + Hive.registerAdapter(DateFormatOptionsAdapter()); + await Hive.openBox(Boxes.theme); await Hive.openBox(Boxes.settings); await Hive.openBox(Boxes.cache); await Hive.openBox(Boxes.appState); @@ -174,7 +180,7 @@ class App { static final remoteConfigProvider = Rmc(); - static const defaultLocale = Locale('en'); + static final defaultLocale = Locales.cs; static const defaultRotations = [ DeviceOrientation.portraitUp, diff --git a/lib/src/_global/providers/theme.provider.dart b/lib/src/_global/providers/theme.provider.dart new file mode 100644 index 00000000..f12db21d --- /dev/null +++ b/lib/src/_global/providers/theme.provider.dart @@ -0,0 +1,66 @@ +import 'dart:async'; + +import 'package:autojidelna/src/_conf/hive.dart'; +import 'package:autojidelna/src/types/theme.dart'; +import 'package:autojidelna/src/ui/theme/app_themes.dart'; +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; + +class ThemeProvider extends ChangeNotifier { + static Box box = Hive.box(Boxes.settings); + + ThemeStyle _themeStyle = box.get(HiveKeys.themeStyle, defaultValue: ThemeStyle.defaultStyle); + ThemeMode _themeMode = box.get(HiveKeys.themeMode, defaultValue: ThemeMode.system); + bool _amoledMode = box.get(HiveKeys.amoledMode, defaultValue: false); + + /// Theme style getter + ThemeStyle get themeStyle => _themeStyle; + + /// Theme mode getter + ThemeMode get themeMode => _themeMode; + + /// Pure black getter + bool get amoledMode => _amoledMode; + + /// Setter for theme style + void setThemeStyle(ThemeStyle themeStyle) { + if (_themeStyle == themeStyle) return; + _themeStyle = themeStyle; + unawaited(box.put(HiveKeys.themeStyle, _themeStyle)); + notifyListeners(); + } + + /// Setter for theme mode + void setThemeMode(ThemeMode themeMode) { + _themeMode = themeMode; + unawaited(box.put(HiveKeys.themeMode, _themeMode)); + notifyListeners(); + } + + /// Setter for pure black + void setAmoledMode(bool isPureBlack) { + _amoledMode = isPureBlack; + unawaited(box.put(HiveKeys.amoledMode, _amoledMode)); + notifyListeners(); + } + + ColorScheme colorSchemeLight(ThemeStyle themeStyle) { + ColorStyle colorStyle = AppThemes.colorStyles[themeStyle]!; + + return AppThemes.colorSchemeLight.copyWith( + primary: colorStyle.primaryLight, + secondary: colorStyle.secondaryLight, + ); + } + + ColorScheme colorSchemeDark(ThemeStyle themeStyle) { + ColorStyle colorStyle = AppThemes.colorStyles[themeStyle]!; + + return AppThemes.colorSchemeDark.copyWith( + primary: colorStyle.primaryDark, + secondary: colorStyle.secondaryDark, + surface: amoledMode ? Colors.black : const Color(0xff121212), + scrim: amoledMode ? Colors.black87 : Colors.black54, + ); + } +} diff --git a/lib/src/lang/supported_locales.dart b/lib/src/lang/supported_locales.dart new file mode 100644 index 00000000..df10d098 --- /dev/null +++ b/lib/src/lang/supported_locales.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +/// Class for supported Locales +class Locales { + static Locale get cs => const Locale('cs', 'CZ'); + static Locale get en => const Locale('en', 'US'); +} diff --git a/lib/src/logic/notifications.dart b/lib/src/logic/notifications.dart index c0fd8fca..ce1a54b1 100644 --- a/lib/src/logic/notifications.dart +++ b/lib/src/logic/notifications.dart @@ -4,6 +4,7 @@ import 'package:autojidelna/src/_conf/dates.dart'; import 'package:autojidelna/src/_conf/hive.dart'; import 'package:autojidelna/src/_conf/notifications.dart'; import 'package:autojidelna/src/lang/l10n_context_extension.dart'; +import 'package:autojidelna/src/lang/supported_locales.dart'; import 'package:autojidelna/src/logic/canteenwrapper.dart'; import 'package:autojidelna/src/types/all.dart'; import 'package:awesome_notifications/awesome_notifications.dart'; @@ -152,7 +153,7 @@ Future initAwesome() async { Future doNotifications({bool force = false}) async { //TODO: add more langueages - final lang = lookupTexts(const Locale('cs')); + final lang = lookupTexts(Locales.cs); LoggedInCanteen loggedInCanteen = LoggedInCanteen(); LoginDataAutojidelna loginData = await loggedInCanteen.getLoginDataFromSecureStorage(); AwesomeNotifications().createNotification( diff --git a/lib/src/types/all.dart b/lib/src/types/all.dart index 834ff297..7154484e 100644 --- a/lib/src/types/all.dart +++ b/lib/src/types/all.dart @@ -199,16 +199,6 @@ enum TypStatistiky { burzaCatcher } -/// Describes what colors will be used by the app -enum ThemeStyle { - defaultStyle, - plumBrown, - blueMauve, - rustOlive, - evergreenSlate, - crimsonEarth, -} - /// Describes what time format will be used by the app enum DateFormatOptions { dMy, @@ -224,10 +214,3 @@ class Fonts { static const String body = 'Inter'; static const String headings = 'Lexend'; } - -enum TabletUi { - auto, - always, - landscape, - never, -} diff --git a/lib/src/types/theme.dart b/lib/src/types/theme.dart new file mode 100644 index 00000000..d00d56f6 --- /dev/null +++ b/lib/src/types/theme.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +class ColorStyle { + Color primaryLight; + Color secondaryLight; + Color primaryDark; + Color secondaryDark; + + ColorStyle({ + required this.primaryLight, + required this.secondaryLight, + required this.primaryDark, + required this.secondaryDark, + }); +} + +/// Describes what ColorStyle will be used by the app +enum ThemeStyle { + defaultStyle, + plumBrown, + blueMauve, + rustOlive, + evergreenSlate, + crimsonEarth, +} diff --git a/lib/src/ui/material_app.dart b/lib/src/ui/material_app.dart index a9af699c..72199b52 100644 --- a/lib/src/ui/material_app.dart +++ b/lib/src/ui/material_app.dart @@ -2,10 +2,12 @@ import 'dart:async'; import 'package:autojidelna/src/_conf/hive.dart'; import 'package:autojidelna/src/_global/app.dart'; +import 'package:autojidelna/src/_global/providers/theme.provider.dart'; import 'package:autojidelna/src/_sentry/sentry.dart'; import 'package:autojidelna/src/lang/l10n_context_extension.dart'; import 'package:autojidelna/src/_routing/app_router.dart'; import 'package:autojidelna/src/logic/deep_link_transformer_logic.dart'; +import 'package:autojidelna/src/ui/theme/app_themes.dart'; import 'package:flutter/material.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:provider/provider.dart'; @@ -51,20 +53,23 @@ class _MyAppState extends State { @override Widget build(BuildContext context) { - return MaterialApp.router( - supportedLocales: Texts.supportedLocales, - localizationsDelegates: Texts.localizationsDelegates, - locale: _locale, - debugShowCheckedModeBanner: false, - routerConfig: _appRouter.config( - navigatorObservers: () => [SentryNavigatorObserver(), SentryTabObserver()], - includePrefixMatches: true, - deepLinkTransformer: (uri) async => deepLinkTransformer(uri), - ), - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - useMaterial3: true, - ), + return Consumer( + builder: (context, theme, ___) { + return MaterialApp.router( + debugShowCheckedModeBanner: false, + themeMode: theme.themeMode, + theme: AppThemes.theme(theme.colorSchemeLight(theme.themeStyle), theme.amoledMode), + darkTheme: AppThemes.theme(theme.colorSchemeDark(theme.themeStyle), theme.amoledMode), + locale: _locale, + supportedLocales: Texts.supportedLocales, + localizationsDelegates: Texts.localizationsDelegates, + routerConfig: _appRouter.config( + includePrefixMatches: true, + navigatorObservers: () => [SentryNavigatorObserver(), SentryTabObserver()], + deepLinkTransformer: (uri) async => deepLinkTransformer(uri), + ), + ); + }, ); } } @@ -77,6 +82,7 @@ class MyAppWrapper extends StatelessWidget { return MultiProvider( providers: [ ChangeNotifierProvider.value(value: App.remoteConfigProvider), + ChangeNotifierProvider(create: (_) => ThemeProvider()), ], child: const MyApp(), ); diff --git a/lib/src/ui/pages/settings/localization/localizations_page.dart b/lib/src/ui/pages/settings/localization/localizations_page.dart index 32810071..891ff33d 100644 --- a/lib/src/ui/pages/settings/localization/localizations_page.dart +++ b/lib/src/ui/pages/settings/localization/localizations_page.dart @@ -1,6 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:autojidelna/src/_global/app.dart'; import 'package:autojidelna/src/lang/l10n_context_extension.dart'; +import 'package:autojidelna/src/lang/supported_locales.dart'; import 'package:flutter/material.dart'; @RoutePage() @@ -18,11 +19,11 @@ class LocalizationsPage extends StatelessWidget { child: Column( children: [ ElevatedButton( - onPressed: () => App.translate(const Locale('en')), + onPressed: () => App.translate(Locales.en), child: Text(lang.languageEnglish), ), ElevatedButton( - onPressed: () => App.translate(const Locale('cs')), + onPressed: () => App.translate(Locales.cs), child: Text(lang.languageCzech), ), ], diff --git a/lib/src/ui/theme/app_themes.dart b/lib/src/ui/theme/app_themes.dart new file mode 100644 index 00000000..5873174c --- /dev/null +++ b/lib/src/ui/theme/app_themes.dart @@ -0,0 +1,281 @@ +import 'package:autojidelna/src/types/all.dart'; +import 'package:autojidelna/src/types/theme.dart'; +import 'package:flutter/material.dart'; + +class AppThemes { + static TextTheme get textTheme => const TextTheme( + bodySmall: TextStyle(fontFamily: Fonts.body), + bodyMedium: TextStyle(fontFamily: Fonts.body), + bodyLarge: TextStyle(fontFamily: Fonts.body), + labelSmall: TextStyle(fontFamily: Fonts.headings), + labelMedium: TextStyle(fontFamily: Fonts.headings), + labelLarge: TextStyle(fontFamily: Fonts.headings), + titleSmall: TextStyle(fontFamily: Fonts.headings), + titleMedium: TextStyle(fontFamily: Fonts.headings), + titleLarge: TextStyle(fontFamily: Fonts.headings), + headlineSmall: TextStyle(fontFamily: Fonts.headings), + headlineMedium: TextStyle(fontFamily: Fonts.headings), + headlineLarge: TextStyle(fontFamily: Fonts.headings), + displaySmall: TextStyle(fontFamily: Fonts.headings), + displayMedium: TextStyle(fontFamily: Fonts.headings), + displayLarge: TextStyle(fontFamily: Fonts.headings), + ); + + static Map get colorStyles => { + ThemeStyle.defaultStyle: ColorStyle( + primaryLight: const Color(0xFFE040FB), + secondaryLight: const Color(0x7B009687), + primaryDark: const Color(0xffbb86fc), + secondaryDark: const Color(0xff018786), + ), + ThemeStyle.plumBrown: ColorStyle( + primaryLight: const Color(0xFFAC009E), + secondaryLight: const Color(0xFF815342), + primaryDark: const Color(0xFFA03998), + secondaryDark: const Color(0xFF7F2A0B), + ), + ThemeStyle.blueMauve: ColorStyle( + primaryLight: const Color(0xFF3741F7), + secondaryLight: const Color(0xFFA3385F), + primaryDark: const Color(0xFF6264D7), + secondaryDark: const Color(0xFF6F354E), + ), + ThemeStyle.rustOlive: ColorStyle( + primaryLight: const Color(0xFFAB4D00), + secondaryLight: const Color(0xFF6D692B), + primaryDark: const Color(0xFFC54F00), + secondaryDark: const Color(0xFF53500C), + ), + ThemeStyle.evergreenSlate: ColorStyle( + primaryLight: const Color(0xFF306b1e), + secondaryLight: const Color(0xFF54624d), + primaryDark: const Color(0xBC19A400), + secondaryDark: const Color(0xFF273421), + ), + ThemeStyle.crimsonEarth: ColorStyle( + primaryLight: const Color(0xFFbe0f00), + secondaryLight: const Color(0xFF775651), + primaryDark: const Color(0xFFC8423D), + secondaryDark: const Color(0xFF442925), + ), + }; + + static ColorScheme get colorSchemeLight => const ColorScheme( + brightness: Brightness.light, + primary: Colors.purpleAccent, + onPrimary: Colors.white, + secondary: Color(0x7B009687), + onSecondary: Colors.white, + error: Colors.red, + onError: Colors.black, + surface: Colors.white, + onSurface: Colors.black, + surfaceContainerHighest: Colors.black12, + onSurfaceVariant: Colors.black54, + scrim: Colors.black54, + surfaceTint: Colors.black, + inverseSurface: Color(0xFF121212), + onInverseSurface: Colors.white, + ); + + static ColorScheme get colorSchemeDark => const ColorScheme( + brightness: Brightness.dark, + primary: Color(0xffbb86fc), + onPrimary: Colors.white, + secondary: Color(0xff018786), + onSecondary: Colors.white, + error: Color(0xFFCF6679), + onError: Colors.white, + surface: Color(0xff121212), + onSurface: Colors.white, + surfaceContainerHighest: Colors.white12, + onSurfaceVariant: Colors.white54, + scrim: Colors.black54, + surfaceTint: Colors.white, + inverseSurface: Color(0xFFdddddd), + onInverseSurface: Colors.black, + ); + + static ThemeData theme(ColorScheme colorScheme, bool amoledMode) => ThemeData( + useMaterial3: true, + applyElevationOverlayColor: true, + materialTapTargetSize: MaterialTapTargetSize.padded, + visualDensity: VisualDensity.adaptivePlatformDensity, + + // COLOR + colorScheme: colorScheme, + canvasColor: colorScheme.surface, + disabledColor: colorScheme.surfaceContainerHighest, + scaffoldBackgroundColor: colorScheme.surface, + shadowColor: Colors.transparent, + splashColor: Colors.transparent, + splashFactory: NoSplash.splashFactory, + + // TYPOGRAPHY & ICONOGRAPHY + textTheme: textTheme, + typography: Typography.material2021(), + + // COMPONENT THEMES + iconTheme: IconThemeData(size: 30, color: colorScheme.onSurface), + appBarTheme: AppBarTheme( + scrolledUnderElevation: amoledMode ? 0 : 2, + elevation: amoledMode ? 0 : 2, + actionsIconTheme: IconThemeData(color: colorScheme.onSurfaceVariant), + ), + cardTheme: CardTheme( + elevation: 2, + clipBehavior: Clip.hardEdge, + surfaceTintColor: colorScheme.surfaceTint, + ), + dividerTheme: DividerThemeData(color: colorScheme.surfaceContainerHighest), + drawerTheme: DrawerThemeData( + surfaceTintColor: colorScheme.surfaceTint, + backgroundColor: colorScheme.surface, + scrimColor: colorScheme.scrim, + elevation: 2, + width: 275, + ), + navigationBarTheme: NavigationBarThemeData( + indicatorColor: colorScheme.secondary, + surfaceTintColor: colorScheme.surfaceTint, + elevation: amoledMode ? 0 : null, + ), + + // Popups + snackBarTheme: SnackBarThemeData( + backgroundColor: colorScheme.inverseSurface, + elevation: amoledMode ? 0 : 2, + contentTextStyle: TextStyle(fontFamily: Fonts.body, color: colorScheme.onInverseSurface), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + ), + behavior: SnackBarBehavior.floating, + insetPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8), + showCloseIcon: true, + closeIconColor: colorScheme.onInverseSurface, + ), + dialogTheme: DialogTheme( + backgroundColor: colorScheme.surface, + elevation: 3, + surfaceTintColor: colorScheme.surfaceTint, + alignment: Alignment.center, + iconColor: colorScheme.onSurface, + titleTextStyle: TextStyle( + fontFamily: Fonts.headings, + color: colorScheme.onSurface, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + contentTextStyle: const TextStyle(fontFamily: Fonts.body, fontSize: 15), + actionsPadding: const EdgeInsets.fromLTRB(12, 0, 16, 7), + ), + + // Inputs + inputDecorationTheme: const InputDecorationTheme( + alignLabelWithHint: true, + isDense: true, + errorMaxLines: 1, + labelStyle: TextStyle(fontFamily: Fonts.body), + floatingLabelAlignment: FloatingLabelAlignment.start, + hintStyle: TextStyle(fontFamily: Fonts.body), + helperStyle: TextStyle(fontFamily: Fonts.body), + border: OutlineInputBorder(), + ), + + // List tiles + listTileTheme: ListTileThemeData( + //shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + dense: false, + selectedColor: colorScheme.primary, + iconColor: colorScheme.primary.withOpacity(.75), + titleTextStyle: TextStyle( + fontSize: 19, + fontFamily: Fonts.body, + fontWeight: FontWeight.w400, + color: colorScheme.onSurface, + ), + subtitleTextStyle: TextStyle( + fontSize: 13, + fontFamily: Fonts.body, + color: colorScheme.onSurfaceVariant, + ), + visualDensity: VisualDensity.comfortable, + ), + expansionTileTheme: ExpansionTileThemeData( + collapsedTextColor: colorScheme.primary, + textColor: colorScheme.onSurface, + childrenPadding: const EdgeInsets.only(bottom: 8), + ), + bottomSheetTheme: BottomSheetThemeData( + modalElevation: 1, + clipBehavior: Clip.hardEdge, + surfaceTintColor: colorScheme.surfaceTint, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + side: BorderSide(color: amoledMode ? colorScheme.surfaceContainerHighest : Colors.transparent, strokeAlign: 1), + ), + ), + + // Buttons + switchTheme: const SwitchThemeData(splashRadius: 0), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ButtonStyle( + textStyle: const WidgetStatePropertyAll( + TextStyle( + fontFamily: Fonts.headings, + fontSize: 18, + fontWeight: FontWeight.w500, + height: 1.25, + ), + ), + backgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) return colorScheme.surfaceContainerHighest; // Disabled color + return colorScheme.surface; // Regular color + }), + foregroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) return colorScheme.onSurfaceVariant; + return colorScheme.onSurface; + }), + fixedSize: const WidgetStatePropertyAll(Size.fromHeight(50)), + splashFactory: InkRipple.splashFactory, + alignment: Alignment.center, + shadowColor: const WidgetStatePropertyAll(Colors.transparent), + elevation: const WidgetStatePropertyAll(4), + ), + ), + iconButtonTheme: const IconButtonThemeData( + style: ButtonStyle(splashFactory: NoSplash.splashFactory), + ), + textButtonTheme: TextButtonThemeData( + style: ButtonStyle( + foregroundColor: WidgetStatePropertyAll(colorScheme.onSurface), + textStyle: const WidgetStatePropertyAll( + TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + height: 1.25, + ), + ), + splashFactory: NoSplash.splashFactory, + overlayColor: const WidgetStatePropertyAll(Colors.transparent), + ), + ), + outlinedButtonTheme: OutlinedButtonThemeData( + style: ButtonStyle( + side: WidgetStatePropertyAll(BorderSide(width: 1.75, color: colorScheme.onSurfaceVariant)), + ), + ), + segmentedButtonTheme: SegmentedButtonThemeData( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.resolveWith( + (Set states) { + if (states.contains(WidgetState.selected)) { + return colorScheme.primary; + } + return Colors.transparent; + }, + ), + ), + ), + ); +} diff --git a/lib/src/ui/widgets/theme_mode_picker.dart b/lib/src/ui/widgets/theme_mode_picker.dart new file mode 100644 index 00000000..ec90eaff --- /dev/null +++ b/lib/src/ui/widgets/theme_mode_picker.dart @@ -0,0 +1,29 @@ +import 'package:autojidelna/src/_global/providers/theme.provider.dart'; +import 'package:autojidelna/src/lang/l10n_context_extension.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class ThemeModePicker extends StatelessWidget { + const ThemeModePicker({super.key}); + + @override + Widget build(BuildContext context) { + final lang = context.l10n; + return SizedBox( + width: MediaQuery.sizeOf(context).width * .9, + child: Selector( + selector: (_, p1) => (read: p1.themeMode, set: p1.setThemeMode), + builder: (_, themeMode, __) => SegmentedButton( + showSelectedIcon: false, + selected: {themeMode.read}, + onSelectionChanged: (Set selected) => themeMode.set(selected.first), + segments: [ + ButtonSegment(value: ThemeMode.system, label: Text(lang.systemThemeMode)), + ButtonSegment(value: ThemeMode.light, label: Text(lang.lightThemeMode)), + ButtonSegment(value: ThemeMode.dark, label: Text(lang.darkThemeMode)), + ], + ), + ), + ); + } +} diff --git a/lib/src/ui/widgets/theme_style_picker.dart b/lib/src/ui/widgets/theme_style_picker.dart new file mode 100644 index 00000000..7a03aa6e --- /dev/null +++ b/lib/src/ui/widgets/theme_style_picker.dart @@ -0,0 +1,74 @@ +import 'package:autojidelna/src/_global/providers/theme.provider.dart'; +import 'package:autojidelna/src/types/theme.dart'; +import 'package:autojidelna/src/ui/theme/app_themes.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class ThemeStylePicker extends StatelessWidget { + const ThemeStylePicker({super.key}); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 225, + child: Consumer( + builder: (context, prov, _) => ListView.builder( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 16), + itemCount: ThemeStyle.values.length, + itemBuilder: (context, index) { + ThemeStyle themeStyle = ThemeStyle.values[index]; + final bool isBright = MediaQuery.platformBrightnessOf(context) == Brightness.light || prov.themeMode == ThemeMode.light; + + ThemeData theme = AppThemes.theme(isBright ? prov.colorSchemeLight(themeStyle) : prov.colorSchemeDark(themeStyle), prov.amoledMode); + + ButtonStyle style = OutlinedButton.styleFrom( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + fixedSize: const Size.fromWidth(125), + padding: EdgeInsets.zero, + side: BorderSide( + width: 3, + strokeAlign: BorderSide.strokeAlignInside, + color: ThemeStyle.values[index] == prov.themeStyle ? theme.colorScheme.primary : Colors.grey, + ), + ); + + return Theme( + data: theme, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 5), + child: OutlinedButton( + style: style, + onPressed: () => prov.setThemeStyle(ThemeStyle.values[index]), + child: Column( + children: [ + SizedBox(height: 35, child: AppBar(automaticallyImplyLeading: false)), + const Divider(color: Colors.transparent), + foodTileColorSchemePreview(context, theme.colorScheme.primary), + foodTileColorSchemePreview(context, theme.colorScheme.secondary), + ], + ), + ), + ), + ); + }, + ), + ), + ); + } + + Widget foodTileColorSchemePreview(BuildContext context, Color color) { + return Card.filled( + margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: Container( + height: 20, + width: 100, + margin: const EdgeInsets.fromLTRB(5, 30, 5, 5), + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(12.5), + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 67201afc..e25f89da 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,10 +34,6 @@ dependencies: sdk: flutter intl: any - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.6 - auto_route: ^9.2.0 hive: ^2.2.3 hive_flutter: ^1.1.0 @@ -107,25 +103,15 @@ flutter: # For details regarding adding assets from package dependencies, see # https://flutter.dev/assets-and-images/#from-packages - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages + fonts: + - family: Lexend + fonts: + - asset: assets/fonts/Lexend-VariableFont_wght.ttf + - family: Inter + fonts: + - asset: assets/fonts/Inter-VariableFont_slnt,wght.ttf sentry: upload_debug_symbols: true