diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index eab896d..7384219 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1342,5 +1342,7 @@ "quickConnectPostFailed": "Failed to get quick connect code", "quickConnectLoginUsingCode": "Using quick connect", "quickConnectEnterCodeDescription": "Enter the code below to login", - "showMore": "Show more" + "showMore": "Show more", + "itemColorsTitle": "Item colors", + "itemColorsDesc": "Use item's primary color to theme the details page" } \ No newline at end of file diff --git a/lib/models/settings/client_settings_model.dart b/lib/models/settings/client_settings_model.dart index 98abd77..9814312 100644 --- a/lib/models/settings/client_settings_model.dart +++ b/lib/models/settings/client_settings_model.dart @@ -62,6 +62,7 @@ abstract class ClientSettingsModel with _$ClientSettingsModel { Duration? nextUpDateCutoff, @Default(ThemeMode.system) ThemeMode themeMode, ColorThemes? themeColor, + @Default(true) bool deriveColorsFromItem, @Default(false) bool amoledBlack, @Default(true) bool blurPlaceHolders, @Default(false) bool blurUpcomingEpisodes, diff --git a/lib/models/settings/client_settings_model.freezed.dart b/lib/models/settings/client_settings_model.freezed.dart index d3bb312..a097167 100644 --- a/lib/models/settings/client_settings_model.freezed.dart +++ b/lib/models/settings/client_settings_model.freezed.dart @@ -21,6 +21,7 @@ mixin _$ClientSettingsModel implements DiagnosticableTreeMixin { Duration? get nextUpDateCutoff; ThemeMode get themeMode; ColorThemes? get themeColor; + bool get deriveColorsFromItem; bool get amoledBlack; bool get blurPlaceHolders; bool get blurUpcomingEpisodes; @@ -63,6 +64,7 @@ mixin _$ClientSettingsModel implements DiagnosticableTreeMixin { ..add(DiagnosticsProperty('nextUpDateCutoff', nextUpDateCutoff)) ..add(DiagnosticsProperty('themeMode', themeMode)) ..add(DiagnosticsProperty('themeColor', themeColor)) + ..add(DiagnosticsProperty('deriveColorsFromItem', deriveColorsFromItem)) ..add(DiagnosticsProperty('amoledBlack', amoledBlack)) ..add(DiagnosticsProperty('blurPlaceHolders', blurPlaceHolders)) ..add(DiagnosticsProperty('blurUpcomingEpisodes', blurUpcomingEpisodes)) @@ -87,7 +89,7 @@ mixin _$ClientSettingsModel implements DiagnosticableTreeMixin { @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'ClientSettingsModel(syncPath: $syncPath, position: $position, size: $size, timeOut: $timeOut, nextUpDateCutoff: $nextUpDateCutoff, themeMode: $themeMode, themeColor: $themeColor, amoledBlack: $amoledBlack, blurPlaceHolders: $blurPlaceHolders, blurUpcomingEpisodes: $blurUpcomingEpisodes, selectedLocale: $selectedLocale, enableMediaKeys: $enableMediaKeys, posterSize: $posterSize, pinchPosterZoom: $pinchPosterZoom, mouseDragSupport: $mouseDragSupport, requireWifi: $requireWifi, showAllCollectionTypes: $showAllCollectionTypes, maxConcurrentDownloads: $maxConcurrentDownloads, schemeVariant: $schemeVariant, backgroundImage: $backgroundImage, checkForUpdates: $checkForUpdates, usePosterForLibrary: $usePosterForLibrary, lastViewedUpdate: $lastViewedUpdate, libraryPageSize: $libraryPageSize, shortcuts: $shortcuts)'; + return 'ClientSettingsModel(syncPath: $syncPath, position: $position, size: $size, timeOut: $timeOut, nextUpDateCutoff: $nextUpDateCutoff, themeMode: $themeMode, themeColor: $themeColor, deriveColorsFromItem: $deriveColorsFromItem, amoledBlack: $amoledBlack, blurPlaceHolders: $blurPlaceHolders, blurUpcomingEpisodes: $blurUpcomingEpisodes, selectedLocale: $selectedLocale, enableMediaKeys: $enableMediaKeys, posterSize: $posterSize, pinchPosterZoom: $pinchPosterZoom, mouseDragSupport: $mouseDragSupport, requireWifi: $requireWifi, showAllCollectionTypes: $showAllCollectionTypes, maxConcurrentDownloads: $maxConcurrentDownloads, schemeVariant: $schemeVariant, backgroundImage: $backgroundImage, checkForUpdates: $checkForUpdates, usePosterForLibrary: $usePosterForLibrary, lastViewedUpdate: $lastViewedUpdate, libraryPageSize: $libraryPageSize, shortcuts: $shortcuts)'; } } @@ -105,6 +107,7 @@ abstract mixin class $ClientSettingsModelCopyWith<$Res> { Duration? nextUpDateCutoff, ThemeMode themeMode, ColorThemes? themeColor, + bool deriveColorsFromItem, bool amoledBlack, bool blurPlaceHolders, bool blurUpcomingEpisodes, @@ -145,6 +148,7 @@ class _$ClientSettingsModelCopyWithImpl<$Res> Object? nextUpDateCutoff = freezed, Object? themeMode = null, Object? themeColor = freezed, + Object? deriveColorsFromItem = null, Object? amoledBlack = null, Object? blurPlaceHolders = null, Object? blurUpcomingEpisodes = null, @@ -193,6 +197,10 @@ class _$ClientSettingsModelCopyWithImpl<$Res> ? _self.themeColor : themeColor // ignore: cast_nullable_to_non_nullable as ColorThemes?, + deriveColorsFromItem: null == deriveColorsFromItem + ? _self.deriveColorsFromItem + : deriveColorsFromItem // ignore: cast_nullable_to_non_nullable + as bool, amoledBlack: null == amoledBlack ? _self.amoledBlack : amoledBlack // ignore: cast_nullable_to_non_nullable @@ -370,6 +378,7 @@ extension ClientSettingsModelPatterns on ClientSettingsModel { Duration? nextUpDateCutoff, ThemeMode themeMode, ColorThemes? themeColor, + bool deriveColorsFromItem, bool amoledBlack, bool blurPlaceHolders, bool blurUpcomingEpisodes, @@ -402,6 +411,7 @@ extension ClientSettingsModelPatterns on ClientSettingsModel { _that.nextUpDateCutoff, _that.themeMode, _that.themeColor, + _that.deriveColorsFromItem, _that.amoledBlack, _that.blurPlaceHolders, _that.blurUpcomingEpisodes, @@ -448,6 +458,7 @@ extension ClientSettingsModelPatterns on ClientSettingsModel { Duration? nextUpDateCutoff, ThemeMode themeMode, ColorThemes? themeColor, + bool deriveColorsFromItem, bool amoledBlack, bool blurPlaceHolders, bool blurUpcomingEpisodes, @@ -479,6 +490,7 @@ extension ClientSettingsModelPatterns on ClientSettingsModel { _that.nextUpDateCutoff, _that.themeMode, _that.themeColor, + _that.deriveColorsFromItem, _that.amoledBlack, _that.blurPlaceHolders, _that.blurUpcomingEpisodes, @@ -524,6 +536,7 @@ extension ClientSettingsModelPatterns on ClientSettingsModel { Duration? nextUpDateCutoff, ThemeMode themeMode, ColorThemes? themeColor, + bool deriveColorsFromItem, bool amoledBlack, bool blurPlaceHolders, bool blurUpcomingEpisodes, @@ -555,6 +568,7 @@ extension ClientSettingsModelPatterns on ClientSettingsModel { _that.nextUpDateCutoff, _that.themeMode, _that.themeColor, + _that.deriveColorsFromItem, _that.amoledBlack, _that.blurPlaceHolders, _that.blurUpcomingEpisodes, @@ -591,6 +605,7 @@ class _ClientSettingsModel extends ClientSettingsModel this.nextUpDateCutoff, this.themeMode = ThemeMode.system, this.themeColor, + this.deriveColorsFromItem = true, this.amoledBlack = false, this.blurPlaceHolders = true, this.blurUpcomingEpisodes = false, @@ -634,6 +649,9 @@ class _ClientSettingsModel extends ClientSettingsModel final ColorThemes? themeColor; @override @JsonKey() + final bool deriveColorsFromItem; + @override + @JsonKey() final bool amoledBlack; @override @JsonKey() @@ -717,6 +735,7 @@ class _ClientSettingsModel extends ClientSettingsModel ..add(DiagnosticsProperty('nextUpDateCutoff', nextUpDateCutoff)) ..add(DiagnosticsProperty('themeMode', themeMode)) ..add(DiagnosticsProperty('themeColor', themeColor)) + ..add(DiagnosticsProperty('deriveColorsFromItem', deriveColorsFromItem)) ..add(DiagnosticsProperty('amoledBlack', amoledBlack)) ..add(DiagnosticsProperty('blurPlaceHolders', blurPlaceHolders)) ..add(DiagnosticsProperty('blurUpcomingEpisodes', blurUpcomingEpisodes)) @@ -741,7 +760,7 @@ class _ClientSettingsModel extends ClientSettingsModel @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'ClientSettingsModel(syncPath: $syncPath, position: $position, size: $size, timeOut: $timeOut, nextUpDateCutoff: $nextUpDateCutoff, themeMode: $themeMode, themeColor: $themeColor, amoledBlack: $amoledBlack, blurPlaceHolders: $blurPlaceHolders, blurUpcomingEpisodes: $blurUpcomingEpisodes, selectedLocale: $selectedLocale, enableMediaKeys: $enableMediaKeys, posterSize: $posterSize, pinchPosterZoom: $pinchPosterZoom, mouseDragSupport: $mouseDragSupport, requireWifi: $requireWifi, showAllCollectionTypes: $showAllCollectionTypes, maxConcurrentDownloads: $maxConcurrentDownloads, schemeVariant: $schemeVariant, backgroundImage: $backgroundImage, checkForUpdates: $checkForUpdates, usePosterForLibrary: $usePosterForLibrary, lastViewedUpdate: $lastViewedUpdate, libraryPageSize: $libraryPageSize, shortcuts: $shortcuts)'; + return 'ClientSettingsModel(syncPath: $syncPath, position: $position, size: $size, timeOut: $timeOut, nextUpDateCutoff: $nextUpDateCutoff, themeMode: $themeMode, themeColor: $themeColor, deriveColorsFromItem: $deriveColorsFromItem, amoledBlack: $amoledBlack, blurPlaceHolders: $blurPlaceHolders, blurUpcomingEpisodes: $blurUpcomingEpisodes, selectedLocale: $selectedLocale, enableMediaKeys: $enableMediaKeys, posterSize: $posterSize, pinchPosterZoom: $pinchPosterZoom, mouseDragSupport: $mouseDragSupport, requireWifi: $requireWifi, showAllCollectionTypes: $showAllCollectionTypes, maxConcurrentDownloads: $maxConcurrentDownloads, schemeVariant: $schemeVariant, backgroundImage: $backgroundImage, checkForUpdates: $checkForUpdates, usePosterForLibrary: $usePosterForLibrary, lastViewedUpdate: $lastViewedUpdate, libraryPageSize: $libraryPageSize, shortcuts: $shortcuts)'; } } @@ -761,6 +780,7 @@ abstract mixin class _$ClientSettingsModelCopyWith<$Res> Duration? nextUpDateCutoff, ThemeMode themeMode, ColorThemes? themeColor, + bool deriveColorsFromItem, bool amoledBlack, bool blurPlaceHolders, bool blurUpcomingEpisodes, @@ -801,6 +821,7 @@ class __$ClientSettingsModelCopyWithImpl<$Res> Object? nextUpDateCutoff = freezed, Object? themeMode = null, Object? themeColor = freezed, + Object? deriveColorsFromItem = null, Object? amoledBlack = null, Object? blurPlaceHolders = null, Object? blurUpcomingEpisodes = null, @@ -849,6 +870,10 @@ class __$ClientSettingsModelCopyWithImpl<$Res> ? _self.themeColor : themeColor // ignore: cast_nullable_to_non_nullable as ColorThemes?, + deriveColorsFromItem: null == deriveColorsFromItem + ? _self.deriveColorsFromItem + : deriveColorsFromItem // ignore: cast_nullable_to_non_nullable + as bool, amoledBlack: null == amoledBlack ? _self.amoledBlack : amoledBlack // ignore: cast_nullable_to_non_nullable diff --git a/lib/models/settings/client_settings_model.g.dart b/lib/models/settings/client_settings_model.g.dart index 7c8cb4e..22d83ae 100644 --- a/lib/models/settings/client_settings_model.g.dart +++ b/lib/models/settings/client_settings_model.g.dart @@ -24,6 +24,7 @@ _ClientSettingsModel _$ClientSettingsModelFromJson(Map json) => themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ?? ThemeMode.system, themeColor: $enumDecodeNullable(_$ColorThemesEnumMap, json['themeColor']), + deriveColorsFromItem: json['deriveColorsFromItem'] as bool? ?? true, amoledBlack: json['amoledBlack'] as bool? ?? false, blurPlaceHolders: json['blurPlaceHolders'] as bool? ?? true, blurUpcomingEpisodes: json['blurUpcomingEpisodes'] as bool? ?? false, @@ -64,6 +65,7 @@ Map _$ClientSettingsModelToJson( 'nextUpDateCutoff': instance.nextUpDateCutoff?.inMicroseconds, 'themeMode': _$ThemeModeEnumMap[instance.themeMode]!, 'themeColor': _$ColorThemesEnumMap[instance.themeColor], + 'deriveColorsFromItem': instance.deriveColorsFromItem, 'amoledBlack': instance.amoledBlack, 'blurPlaceHolders': instance.blurPlaceHolders, 'blurUpcomingEpisodes': instance.blurUpcomingEpisodes, diff --git a/lib/providers/settings/client_settings_provider.dart b/lib/providers/settings/client_settings_provider.dart index 0c04bd2..1958233 100644 --- a/lib/providers/settings/client_settings_provider.dart +++ b/lib/providers/settings/client_settings_provider.dart @@ -39,6 +39,8 @@ class ClientSettingsNotifier extends StateNotifier { void setAmoledBlack(bool? value) => state = state.copyWith(amoledBlack: value ?? false); + void setDerivedColorsFromItem(bool? value) => state = state.copyWith(deriveColorsFromItem: value ?? false); + void setBlurPlaceholders(bool value) => state = state.copyWith(blurPlaceHolders: value); void setTimeOut(Duration? duration) => state = state.copyWith(timeOut: duration); diff --git a/lib/screens/settings/client_sections/client_settings_theme.dart b/lib/screens/settings/client_sections/client_settings_theme.dart index 5b69e27..9bbab5c 100644 --- a/lib/screens/settings/client_sections/client_settings_theme.dart +++ b/lib/screens/settings/client_sections/client_settings_theme.dart @@ -113,5 +113,15 @@ List buildClientSettingsTheme(BuildContext context, WidgetRef ref) { onChanged: (value) => ref.read(clientSettingsProvider.notifier).setAmoledBlack(value), ), ), + SettingsListTile( + label: Text(context.localized.itemColorsTitle), + subLabel: Text(context.localized.itemColorsDesc), + onTap: () => + ref.read(clientSettingsProvider.notifier).setDerivedColorsFromItem(!clientSettings.deriveColorsFromItem), + trailing: Switch( + value: clientSettings.deriveColorsFromItem, + onChanged: (value) => ref.read(clientSettingsProvider.notifier).setDerivedColorsFromItem(value), + ), + ), ]); } diff --git a/lib/screens/shared/detail_scaffold.dart b/lib/screens/shared/detail_scaffold.dart index 267ae84..fea60c9 100644 --- a/lib/screens/shared/detail_scaffold.dart +++ b/lib/screens/shared/detail_scaffold.dart @@ -3,9 +3,11 @@ import 'package:flutter/material.dart'; import 'package:auto_route/auto_route.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:iconsax_plus/iconsax_plus.dart'; +import 'package:palette_generator_master/palette_generator_master.dart'; import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/items/images_models.dart'; +import 'package:fladder/providers/settings/client_settings_provider.dart'; import 'package:fladder/providers/sync/sync_provider_helpers.dart'; import 'package:fladder/providers/sync_provider.dart'; import 'package:fladder/providers/user_provider.dart'; @@ -23,6 +25,16 @@ import 'package:fladder/widgets/shared/item_actions.dart'; import 'package:fladder/widgets/shared/modal_bottom_sheet.dart'; import 'package:fladder/widgets/shared/pull_to_refresh.dart'; +Future getDominantColor(ImageProvider imageProvider) async { + final paletteGenerator = await PaletteGeneratorMaster.fromImageProvider( + imageProvider, + size: const Size(200, 200), + maximumColorCount: 20, + ); + + return paletteGenerator.dominantColor?.color; +} + class DetailScaffold extends ConsumerStatefulWidget { final String label; final ItemBaseModel? item; @@ -49,18 +61,41 @@ class DetailScaffold extends ConsumerStatefulWidget { class _DetailScaffoldState extends ConsumerState { List? lastImages; ImageData? backgroundImage; + Color? dominantColor; + + ImageProvider? _lastRequestedImage; @override void didUpdateWidget(covariant DetailScaffold oldWidget) { super.didUpdateWidget(oldWidget); + updateImage(); + _updateDominantColor(); + } + + void updateImage() { if (lastImages == null) { lastImages = widget.backDrops?.backDrop; - setState(() { - backgroundImage = widget.backDrops?.randomBackDrop; - }); + backgroundImage = widget.backDrops?.randomBackDrop; } } + Future _updateDominantColor() async { + if (!ref.read(clientSettingsProvider.select((value) => value.deriveColorsFromItem))) return; + final newImage = widget.item?.getPosters?.logo; + if (newImage == null) return; + + final provider = newImage.imageProvider; + _lastRequestedImage = provider; + + final newColor = await getDominantColor(provider); + + if (!mounted || _lastRequestedImage != provider) return; + + setState(() { + dominantColor = newColor; + }); + } + @override Widget build(BuildContext context) { final size = MediaQuery.sizeOf(context); @@ -69,250 +104,263 @@ class _DetailScaffoldState extends ConsumerState { final minHeight = 450.0.clamp(0, size.height).toDouble(); final maxHeight = size.height - 10; final sideBarPadding = AdaptiveLayout.of(context).sideBarWidth; - return PullToRefresh( - onRefresh: () async { - await widget.onRefresh?.call(); - setState(() { - if (context.mounted) { - if (widget.backDrops?.backDrop?.contains(backgroundImage) == true) { - backgroundImage = widget.backDrops?.randomBackDrop; - } - } - }); - }, - refreshOnStart: true, - child: Scaffold( - backgroundColor: Theme.of(context).colorScheme.surface, - extendBodyBehindAppBar: true, - body: Stack( - children: [ - SingleChildScrollView( - physics: const AlwaysScrollableScrollPhysics(), - child: Stack( - children: [ - SizedBox( - height: maxHeight, - width: size.width, - child: FladderImage( - image: backgroundImage, - blurOnly: true, - ), - ), - if (backgroundImage != null) - Align( - alignment: Alignment.topCenter, - child: Padding( - padding: EdgeInsets.only(left: sideBarPadding), - child: ShaderMask( - shaderCallback: (bounds) => LinearGradient( + return Theme( + data: Theme.of(context).copyWith( + colorScheme: dominantColor != null + ? ColorScheme.fromSeed( + seedColor: dominantColor!, + brightness: Theme.brightnessOf(context), + dynamicSchemeVariant: ref.watch(clientSettingsProvider.select((value) => value.schemeVariant)), + ) + : null, + ), + child: Builder(builder: (context) { + return PullToRefresh( + onRefresh: () async { + await widget.onRefresh?.call(); + setState(() { + if (context.mounted) { + if (widget.backDrops?.backDrop?.contains(backgroundImage) == true) { + backgroundImage = widget.backDrops?.randomBackDrop; + } + } + }); + }, + refreshOnStart: true, + child: Scaffold( + backgroundColor: Theme.of(context).colorScheme.surface, + extendBodyBehindAppBar: true, + body: Stack( + children: [ + SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: Stack( + children: [ + SizedBox( + height: maxHeight, + width: size.width, + child: FladderImage( + image: backgroundImage, + blurOnly: true, + ), + ), + if (backgroundImage != null) + Align( + alignment: Alignment.topCenter, + child: Padding( + padding: EdgeInsets.only(left: sideBarPadding), + child: ShaderMask( + shaderCallback: (bounds) => LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.white, + Colors.white, + Colors.white, + Colors.white, + Colors.white, + Colors.white.withValues(alpha: 0), + ], + ).createShader(bounds), + child: ConstrainedBox( + constraints: BoxConstraints( + minWidth: double.infinity, + minHeight: minHeight - 20, + maxHeight: maxHeight.clamp(minHeight, 2500) - 20, + ), + child: FadeInImage( + placeholder: ResizeImage( + backgroundImage!.imageProvider, + height: maxHeight ~/ 1.5, + ), + placeholderColor: Colors.transparent, + fit: BoxFit.cover, + alignment: Alignment.topCenter, + placeholderFit: BoxFit.cover, + excludeFromSemantics: true, + image: ResizeImage( + backgroundImage!.imageProvider, + height: maxHeight ~/ 1.5, + ), + ), + ), + ), + ), + ), + Container( + width: double.infinity, + height: maxHeight + 10, + decoration: BoxDecoration( + gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ - Colors.white, - Colors.white, - Colors.white, - Colors.white, - Colors.white, - Colors.white.withValues(alpha: 0), + Theme.of(context).colorScheme.surface.withValues(alpha: 0), + Theme.of(context).colorScheme.surface.withValues(alpha: 0.10), + Theme.of(context).colorScheme.surface.withValues(alpha: 0.35), + Theme.of(context).colorScheme.surface.withValues(alpha: 0.85), + Theme.of(context).colorScheme.surface, ], - ).createShader(bounds), + ), + ), + ), + Container( + height: size.height, + width: size.width, + color: widget.backgroundColor, + ), + Padding( + padding: EdgeInsets.only( + bottom: 0, + top: MediaQuery.of(context).padding.top, + ), + child: FocusScope( + autofocus: true, child: ConstrainedBox( constraints: BoxConstraints( - minWidth: double.infinity, - minHeight: minHeight - 20, - maxHeight: maxHeight.clamp(minHeight, 2500) - 20, + minHeight: size.height, + maxWidth: size.width, ), - child: FadeInImage( - placeholder: ResizeImage( - backgroundImage!.imageProvider, - height: maxHeight ~/ 1.5, - ), - placeholderColor: Colors.transparent, - fit: BoxFit.cover, - alignment: Alignment.topCenter, - placeholderFit: BoxFit.cover, - excludeFromSemantics: true, - image: ResizeImage( - backgroundImage!.imageProvider, - height: maxHeight ~/ 1.5, + child: widget.content( + padding.copyWith( + left: sideBarPadding + 25 + MediaQuery.paddingOf(context).left, ), ), ), ), ), - ), - Container( - width: double.infinity, - height: maxHeight + 10, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Theme.of(context).colorScheme.surface.withValues(alpha: 0), - Theme.of(context).colorScheme.surface.withValues(alpha: 0.10), - Theme.of(context).colorScheme.surface.withValues(alpha: 0.35), - Theme.of(context).colorScheme.surface.withValues(alpha: 0.85), - Theme.of(context).colorScheme.surface, - ], - ), - ), - ), - Container( - height: size.height, - width: size.width, - color: widget.backgroundColor, - ), - Padding( - padding: EdgeInsets.only( - bottom: 0, - top: MediaQuery.of(context).padding.top, - ), - child: FocusScope( - autofocus: true, - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: size.height, - maxWidth: size.width, - ), - child: widget.content( - padding.copyWith( - left: sideBarPadding + 25 + MediaQuery.paddingOf(context).left, - ), - ), - ), - ), - ), - ], - ), - ), - //Top row buttons - if (AdaptiveLayout.of(context).viewSize < ViewSize.desktop) - IconTheme( - data: IconThemeData(color: Theme.of(context).colorScheme.onSurface), - child: Padding( - padding: MediaQuery.paddingOf(context) - .copyWith(left: sideBarPadding + MediaQuery.paddingOf(context).left) - .add( - const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - ), - child: Row( - children: [ - IconButton.filledTonal( - style: IconButton.styleFrom( - backgroundColor: backGroundColor, - ), - onPressed: () => context.router.popBack(), - icon: Padding( - padding: - EdgeInsets.all(AdaptiveLayout.of(context).inputDevice == InputDevice.pointer ? 0 : 4), - child: const BackButtonIcon(), - ), - ), - const Spacer(), - AnimatedSize( - duration: const Duration(milliseconds: 250), - child: Container( - decoration: BoxDecoration( - color: backGroundColor, borderRadius: FladderTheme.defaultShape.borderRadius), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.item != null) ...[ - ref.watch(syncedItemProvider(widget.item)).when( - error: (error, stackTrace) => const SizedBox.shrink(), - data: (syncedItem) { - if (syncedItem == null && - ref.read(userProvider.select( - (value) => value?.canDownload ?? false, - )) && - widget.item?.syncAble == true) { - return IconButton( - onPressed: () => - ref.read(syncProvider.notifier).addSyncItem(context, widget.item!), - icon: const Icon( - IconsaxPlusLinear.arrow_down_2, - ), - ); - } else if (syncedItem != null) { - return IconButton( - onPressed: () => showSyncItemDetails(context, syncedItem, ref), - icon: SyncButton(item: widget.item!, syncedItem: syncedItem), - ); - } - return const SizedBox.shrink(); - }, - loading: () => const SizedBox.shrink(), - ), - Builder( - builder: (context) { - final newActions = widget.actions?.call(context); - if (AdaptiveLayout.of(context).inputDevice == InputDevice.pointer) { - return PopupMenuButton( - tooltip: context.localized.moreOptions, - enabled: newActions?.isNotEmpty == true, - icon: Icon( - widget.item!.type.icon, - color: Theme.of(context).colorScheme.onSurface, - ), - itemBuilder: (context) => newActions?.popupMenuItems(useIcons: true) ?? [], - ); - } else { - return IconButton( - onPressed: () => showBottomSheetPill( - context: context, - content: (context, scrollController) => ListView( - controller: scrollController, - shrinkWrap: true, - children: newActions?.listTileItems(context, useIcons: true) ?? [], - ), - ), - icon: Icon( - widget.item!.type.icon, - ), - ); - } - }, - ), - ], - if (AdaptiveLayout.of(context).inputDevice == InputDevice.pointer) - Builder( - builder: (context) => Tooltip( - message: context.localized.refresh, - child: IconButton( - onPressed: () => context.refreshData(), - icon: const Icon(IconsaxPlusLinear.refresh), - ), - ), - ), - if (AdaptiveLayout.layoutModeOf(context) == LayoutMode.single || - AdaptiveLayout.viewSizeOf(context) == ViewSize.phone) - Container( - margin: const EdgeInsets.symmetric(horizontal: 6), - child: const SizedBox( - height: 30, - width: 30, - child: SettingsUserIcon(), - ), - ), - if (AdaptiveLayout.layoutModeOf(context) == LayoutMode.single) - Tooltip( - message: context.localized.home, - child: IconButton( - onPressed: () => context.navigateTo(const DashboardRoute()), - icon: const Icon(IconsaxPlusLinear.home), - )), - ], - ), - ), - ), ], ), ), - ), - ], - ), - ), + //Top row buttons + if (AdaptiveLayout.of(context).viewSize < ViewSize.desktop) + IconTheme( + data: IconThemeData(color: Theme.of(context).colorScheme.onSurface), + child: Padding( + padding: MediaQuery.paddingOf(context) + .copyWith(left: sideBarPadding + MediaQuery.paddingOf(context).left) + .add( + const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + ), + child: Row( + children: [ + IconButton.filledTonal( + style: IconButton.styleFrom( + backgroundColor: backGroundColor, + ), + onPressed: () => context.router.popBack(), + icon: Padding( + padding: + EdgeInsets.all(AdaptiveLayout.inputDeviceOf(context) == InputDevice.pointer ? 0 : 4), + child: const BackButtonIcon(), + ), + ), + const Spacer(), + AnimatedSize( + duration: const Duration(milliseconds: 250), + child: Container( + decoration: BoxDecoration( + color: backGroundColor, borderRadius: FladderTheme.defaultShape.borderRadius), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.item != null) ...[ + ref.watch(syncedItemProvider(widget.item)).when( + error: (error, stackTrace) => const SizedBox.shrink(), + data: (syncedItem) { + if (syncedItem == null && + ref.read(userProvider.select( + (value) => value?.canDownload ?? false, + )) && + widget.item?.syncAble == true) { + return IconButton( + onPressed: () => + ref.read(syncProvider.notifier).addSyncItem(context, widget.item!), + icon: const Icon( + IconsaxPlusLinear.arrow_down_2, + ), + ); + } else if (syncedItem != null) { + return IconButton( + onPressed: () => showSyncItemDetails(context, syncedItem, ref), + icon: SyncButton(item: widget.item!, syncedItem: syncedItem), + ); + } + return const SizedBox.shrink(); + }, + loading: () => const SizedBox.shrink(), + ), + Builder( + builder: (context) { + final newActions = widget.actions?.call(context); + if (AdaptiveLayout.inputDeviceOf(context) == InputDevice.pointer) { + return PopupMenuButton( + tooltip: context.localized.moreOptions, + enabled: newActions?.isNotEmpty == true, + icon: Icon( + widget.item!.type.icon, + color: Theme.of(context).colorScheme.onSurface, + ), + itemBuilder: (context) => newActions?.popupMenuItems(useIcons: true) ?? [], + ); + } else { + return IconButton( + onPressed: () => showBottomSheetPill( + context: context, + content: (context, scrollController) => ListView( + controller: scrollController, + shrinkWrap: true, + children: newActions?.listTileItems(context, useIcons: true) ?? [], + ), + ), + icon: Icon( + widget.item!.type.icon, + ), + ); + } + }, + ), + ], + if (AdaptiveLayout.inputDeviceOf(context) == InputDevice.pointer) + Builder( + builder: (context) => Tooltip( + message: context.localized.refresh, + child: IconButton( + onPressed: () => context.refreshData(), + icon: const Icon(IconsaxPlusLinear.refresh), + ), + ), + ), + if (AdaptiveLayout.layoutModeOf(context) == LayoutMode.single || + AdaptiveLayout.viewSizeOf(context) == ViewSize.phone) + Container( + margin: const EdgeInsets.symmetric(horizontal: 6), + child: const SizedBox( + height: 30, + width: 30, + child: SettingsUserIcon(), + ), + ), + if (AdaptiveLayout.layoutModeOf(context) == LayoutMode.single) + Tooltip( + message: context.localized.home, + child: IconButton( + onPressed: () => context.navigateTo(const DashboardRoute()), + icon: const Icon(IconsaxPlusLinear.home), + )), + ], + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + }), ); } } diff --git a/pubspec.lock b/pubspec.lock index 0114fb8..ef0557d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1373,6 +1373,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.1" + palette_generator_master: + dependency: "direct main" + description: + name: palette_generator_master + sha256: "2b27a3d9f773c5bc407ed828589488777f73fa23cd24abe6a9b90249a41a7df0" + url: "https://pub.dev" + source: hosted + version: "1.0.1" path: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 94dc592..1591858 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -91,6 +91,7 @@ dependencies: overflow_view: ^0.5.0 flutter_sticky_header: ^0.8.0 markdown_widget: ^2.3.2+8 + palette_generator_master: ^1.0.1 # Navigation auto_route: ^10.1.2 @@ -202,6 +203,7 @@ flutter: - assets/fonts/ - config/ - assets/mp-font.ttf + - assets/ fonts: - family: Rubik