diff --git a/api/lib/src/models/colors.dart b/api/lib/src/models/colors.dart index c40da7bcda4b..402a0b78f3a8 100644 --- a/api/lib/src/models/colors.dart +++ b/api/lib/src/models/colors.dart @@ -1,8 +1,8 @@ import 'package:dart_leap/dart_leap.dart'; class BasicColors { - static const light = SRGBColor(0xFFD9D9D9); - static const dark = SRGBColor(0xFF1A1A1A); + static const light = SRGBColor(0xFF9E9E9E); + static const dark = SRGBColor(0xFF3A3A3A); static const red = SRGBColor(0xFFF44336); static const blue = SRGBColor(0xFF2196F3); static const whiteTransparent = SRGBColor(0x00FFFFFF); diff --git a/api/lib/src/models/texture.dart b/api/lib/src/models/texture.dart index 57aa481d3673..c721c6d3920d 100644 --- a/api/lib/src/models/texture.dart +++ b/api/lib/src/models/texture.dart @@ -7,67 +7,142 @@ import 'colors.dart'; part 'texture.g.dart'; part 'texture.freezed.dart'; +enum PatternBackground { light, dark } + enum PatternTemplate { plain, - ruled, - quad, - music, plainDark, + ruled, ruledDark, + ruledSimple, + ruledSimpleDark, + quad, quadDark, + quadSimple, + quadSimpleDark, + music, musicDark, + dotted, + dottedDark, } +const templateDirectory = 'templates/'; + extension PatternTemplateExtension on PatternTemplate { // camelCase to snake_case - String get asset => - 'templates/${name.replaceAllMapped(RegExp(r'([A-Z])'), (match) => '_${match.group(1)?.toLowerCase()}')}.png'; + String get asset => '$templateDirectory$assetName'; + String get assetName => + '${name.replaceAllMapped(RegExp(r'([A-Z])'), (match) => '_${match.group(1)?.toLowerCase()}')}.png'; - bool get dark => [ - PatternTemplate.plainDark, - PatternTemplate.ruledDark, - PatternTemplate.quadDark, - PatternTemplate.musicDark, - ].contains(this); + PatternBackground get background => switch (this) { + PatternTemplate.plain || + PatternTemplate.ruled || + PatternTemplate.ruledSimple || + PatternTemplate.quad || + PatternTemplate.quadSimple || + PatternTemplate.music || + PatternTemplate.dotted => PatternBackground.light, + PatternTemplate.plainDark || + PatternTemplate.ruledDark || + PatternTemplate.ruledSimpleDark || + PatternTemplate.quadDark || + PatternTemplate.quadSimpleDark || + PatternTemplate.musicDark || + PatternTemplate.dottedDark => PatternBackground.dark, + }; PatternTexture create() { switch (this) { case PatternTemplate.plain: - return const PatternTexture(boxColor: BasicColors.light); + return const PatternTexture(boxColor: SRGBColor.white); case PatternTemplate.ruled: - return const PatternTexture(boxColor: BasicColors.light, boxHeight: 40); + return const PatternTexture(boxColor: SRGBColor.white, boxHeight: 40); + case PatternTemplate.ruledSimple: + return const PatternTexture( + boxColor: SRGBColor.white, + boxHeight: 40, + boxXColor: BasicColors.dark, + boxYColor: BasicColors.dark, + ); case PatternTemplate.quad: return const PatternTexture( - boxColor: BasicColors.light, + boxColor: SRGBColor.white, + boxHeight: 40, + boxWidth: 40, + ); + case PatternTemplate.quadSimple: + return const PatternTexture( + boxColor: SRGBColor.white, boxHeight: 40, boxWidth: 40, + boxXColor: BasicColors.dark, + boxYColor: BasicColors.dark, ); case PatternTemplate.music: return const PatternTexture( - boxColor: BasicColors.light, + boxColor: SRGBColor.white, boxHeight: 40, boxYColor: SRGBColor.black, boxYSpace: 80, boxYCount: 5, ); + case PatternTemplate.dotted: + return const PatternTexture( + boxColor: SRGBColor.white, + boxWidth: 40, + boxHeight: 40, + boxXCount: 1, + boxYCount: 2, + boxXStroke: 5, + boxYStroke: 35, + boxXColor: BasicColors.light, + boxYColor: SRGBColor.white, + ); case PatternTemplate.plainDark: - return const PatternTexture(boxColor: BasicColors.dark); + return const PatternTexture(boxColor: SRGBColor.black); case PatternTemplate.ruledDark: - return const PatternTexture(boxColor: BasicColors.dark, boxHeight: 40); + return const PatternTexture(boxColor: SRGBColor.black, boxHeight: 40); + case PatternTemplate.ruledSimpleDark: + return const PatternTexture( + boxColor: SRGBColor.black, + boxHeight: 40, + boxXColor: BasicColors.dark, + boxYColor: BasicColors.dark, + ); case PatternTemplate.quadDark: return const PatternTexture( - boxColor: BasicColors.dark, + boxColor: SRGBColor.black, + boxWidth: 40, + boxHeight: 40, + ); + case PatternTemplate.quadSimpleDark: + return const PatternTexture( + boxColor: SRGBColor.black, boxWidth: 40, boxHeight: 40, + boxXColor: BasicColors.dark, + boxYColor: BasicColors.dark, ); case PatternTemplate.musicDark: return const PatternTexture( - boxColor: BasicColors.dark, + boxColor: SRGBColor.black, boxYColor: SRGBColor.white, boxHeight: 40, boxYSpace: 80, boxYCount: 5, ); + case PatternTemplate.dottedDark: + return const PatternTexture( + boxColor: SRGBColor.black, + boxWidth: 40, + boxHeight: 40, + boxXCount: 1, + boxYCount: 2, + boxXStroke: 5, + boxYStroke: 35, + boxXColor: BasicColors.dark, + boxYColor: SRGBColor.black, + ); } } } diff --git a/app/lib/api/file_system.dart b/app/lib/api/file_system.dart index 1b3d81e54e06..05f30cf7766b 100644 --- a/app/lib/api/file_system.dart +++ b/app/lib/api/file_system.dart @@ -192,13 +192,6 @@ class ButterflyFileSystem { } } - Future _createDefaultTemplates(TemplateFileSystem fs) async => - Future.wait( - (await DocumentDefaults.getDefaults( - context, - )).map((e) => fs.createFile('${e.name}.bfly', e)), - ); - Future _createDefaultPacks(PackFileSystem fs) async { final pack = await DocumentDefaults.getCorePack(); await fs.createFile('${pack.name}.bfly', pack); @@ -240,7 +233,6 @@ class ButterflyFileSystem { onEncode: _encode, onDecode: _decode, storage: storage, - createDefault: _createDefaultTemplates, ); _templateCache[key] = system; return system; diff --git a/app/lib/cubits/settings.dart b/app/lib/cubits/settings.dart index c8baa38f98a4..b969c0b6d575 100644 --- a/app/lib/cubits/settings.dart +++ b/app/lib/cubits/settings.dart @@ -98,6 +98,25 @@ enum StartupBehavior { openHomeScreen, openLastNote, openNewNote } enum InputMappingCategory { activeTool, handTool, toolOnToolbar } +@freezed +sealed class FavoriteLocation with _$FavoriteLocation { + const FavoriteLocation._(); + + const factory FavoriteLocation({String? remote, required String path}) = + _FavoriteLocation; + + factory FavoriteLocation.fromJson(Map json) => + _$FavoriteLocationFromJson(json); + + factory FavoriteLocation.fromLocation(AssetLocation location) { + return FavoriteLocation(remote: location.remote, path: location.path); + } + + AssetLocation toLocation([String defaultRemote = '']) { + return AssetLocation(path: path, remote: remote ?? defaultRemote); + } +} + class InputMappingDefault { static const InputMapping leftMouse = InputMapping( InputMapping.activeToolValue, @@ -311,6 +330,9 @@ sealed class ButterflySettings with _$ButterflySettings, LeapSettings { @Default(InputConfiguration()) InputConfiguration inputConfiguration, @Default('') String fallbackPack, @Default([]) List starred, + @Default([]) + @JsonKey(includeFromJson: false, includeToJson: false) + List favoriteTemplates, @Default('') String defaultTemplate, @Default(NavigatorPosition.left) NavigatorPosition navigatorPosition, @Default(ToolbarPosition.inline) ToolbarPosition toolbarPosition, @@ -386,7 +408,7 @@ sealed class ButterflySettings with _$ButterflySettings, LeapSettings { return null; } }) - .whereType() + .nonNulls .toList() ?? [], zoomEnabled: prefs.getBool('zoom_enabled') ?? true, @@ -403,6 +425,19 @@ sealed class ButterflySettings with _$ButterflySettings, LeapSettings { ), fallbackPack: prefs.getString('fallback_pack') ?? '', starred: prefs.getStringList('starred') ?? [], + favoriteTemplates: + prefs + .getStringList('favorite_templates') + ?.map((e) { + try { + return FavoriteLocation.fromJson(json.decode(e)); + } catch (e) { + return null; + } + }) + .nonNulls + .toList() ?? + [], defaultTemplate: prefs.getString('default_template') ?? '', toolbarPosition: prefs.containsKey('toolbar_position') ? ToolbarPosition.values.byName(prefs.getString('toolbar_position')!) @@ -536,6 +571,10 @@ sealed class ButterflySettings with _$ButterflySettings, LeapSettings { ); await prefs.setString('fallback_pack', fallbackPack); await prefs.setStringList('starred', starred); + await prefs.setStringList( + 'favorite_templates', + favoriteTemplates.map((e) => json.encode(e.toJson())).toList(), + ); await prefs.setInt('version', 0); await prefs.setString('default_template', defaultTemplate); await prefs.setString('toolbar_position', toolbarPosition.name); @@ -984,6 +1023,17 @@ class SettingsCubit extends Cubit return save(); } + Future toggleFavoriteTemplate(FavoriteLocation template) { + final favorites = state.favoriteTemplates.toList(); + if (favorites.contains(template)) { + favorites.remove(template); + } else { + favorites.add(template); + } + emit(state.copyWith(favoriteTemplates: favorites)); + return save(); + } + Future changeNavigationRail(bool value) { emit(state.copyWith(navigationRail: value)); return save(); diff --git a/app/lib/cubits/settings.freezed.dart b/app/lib/cubits/settings.freezed.dart index 5328d2751492..482e8a1361c7 100644 --- a/app/lib/cubits/settings.freezed.dart +++ b/app/lib/cubits/settings.freezed.dart @@ -12,6 +12,155 @@ part of 'settings.dart'; // dart format off T _$identity(T value) => value; +/// @nodoc +mixin _$FavoriteLocation implements DiagnosticableTreeMixin { + + String? get remote; String get path; +/// Create a copy of FavoriteLocation +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$FavoriteLocationCopyWith get copyWith => _$FavoriteLocationCopyWithImpl(this as FavoriteLocation, _$identity); + + /// Serializes this FavoriteLocation to a JSON map. + Map toJson(); + +@override +void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'FavoriteLocation')) + ..add(DiagnosticsProperty('remote', remote))..add(DiagnosticsProperty('path', path)); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is FavoriteLocation&&(identical(other.remote, remote) || other.remote == remote)&&(identical(other.path, path) || other.path == path)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,remote,path); + +@override +String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { + return 'FavoriteLocation(remote: $remote, path: $path)'; +} + + +} + +/// @nodoc +abstract mixin class $FavoriteLocationCopyWith<$Res> { + factory $FavoriteLocationCopyWith(FavoriteLocation value, $Res Function(FavoriteLocation) _then) = _$FavoriteLocationCopyWithImpl; +@useResult +$Res call({ + String? remote, String path +}); + + + + +} +/// @nodoc +class _$FavoriteLocationCopyWithImpl<$Res> + implements $FavoriteLocationCopyWith<$Res> { + _$FavoriteLocationCopyWithImpl(this._self, this._then); + + final FavoriteLocation _self; + final $Res Function(FavoriteLocation) _then; + +/// Create a copy of FavoriteLocation +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? remote = freezed,Object? path = null,}) { + return _then(_self.copyWith( +remote: freezed == remote ? _self.remote : remote // ignore: cast_nullable_to_non_nullable +as String?,path: null == path ? _self.path : path // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + + +/// @nodoc +@JsonSerializable() + +class _FavoriteLocation extends FavoriteLocation with DiagnosticableTreeMixin { + const _FavoriteLocation({this.remote, required this.path}): super._(); + factory _FavoriteLocation.fromJson(Map json) => _$FavoriteLocationFromJson(json); + +@override final String? remote; +@override final String path; + +/// Create a copy of FavoriteLocation +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$FavoriteLocationCopyWith<_FavoriteLocation> get copyWith => __$FavoriteLocationCopyWithImpl<_FavoriteLocation>(this, _$identity); + +@override +Map toJson() { + return _$FavoriteLocationToJson(this, ); +} +@override +void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'FavoriteLocation')) + ..add(DiagnosticsProperty('remote', remote))..add(DiagnosticsProperty('path', path)); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _FavoriteLocation&&(identical(other.remote, remote) || other.remote == remote)&&(identical(other.path, path) || other.path == path)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,remote,path); + +@override +String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { + return 'FavoriteLocation(remote: $remote, path: $path)'; +} + + +} + +/// @nodoc +abstract mixin class _$FavoriteLocationCopyWith<$Res> implements $FavoriteLocationCopyWith<$Res> { + factory _$FavoriteLocationCopyWith(_FavoriteLocation value, $Res Function(_FavoriteLocation) _then) = __$FavoriteLocationCopyWithImpl; +@override @useResult +$Res call({ + String? remote, String path +}); + + + + +} +/// @nodoc +class __$FavoriteLocationCopyWithImpl<$Res> + implements _$FavoriteLocationCopyWith<$Res> { + __$FavoriteLocationCopyWithImpl(this._self, this._then); + + final _FavoriteLocation _self; + final $Res Function(_FavoriteLocation) _then; + +/// Create a copy of FavoriteLocation +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? remote = freezed,Object? path = null,}) { + return _then(_FavoriteLocation( +remote: freezed == remote ? _self.remote : remote // ignore: cast_nullable_to_non_nullable +as String?,path: null == path ? _self.path : path // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + /// @nodoc mixin _$InputConfiguration implements DiagnosticableTreeMixin { @@ -182,7 +331,7 @@ as InputMapping, /// @nodoc mixin _$ButterflySettings implements DiagnosticableTreeMixin { - ThemeMode get theme; ThemeDensity get density; String get localeTag; String get documentPath; double get gestureSensitivity; double get touchSensitivity; double get selectSensitivity; double get scrollSensitivity; bool get penOnlyInput; bool get inputGestures; String get design; BannerVisibility get bannerVisibility;@JsonKey(includeFromJson: false, includeToJson: false) List get history; bool get zoomEnabled; String? get lastVersion;@JsonKey(includeFromJson: false, includeToJson: false) List get connections; String get defaultRemote; bool get nativeTitleBar; bool get startInFullScreen; bool get navigationRail; IgnorePressure get ignorePressure; SyncMode get syncMode; InputConfiguration get inputConfiguration; String get fallbackPack; List get starred; String get defaultTemplate; NavigatorPosition get navigatorPosition; ToolbarPosition get toolbarPosition; ToolbarSize get toolbarSize; SortBy get sortBy; SortOrder get sortOrder; double get imageScale; double get pdfQuality; PlatformTheme get platformTheme;@SRGBConverter() List get recentColors; List get flags; bool get spreadPages; bool get highContrast; bool get gridView; bool get autosave; bool get showSaveButton; int get toolbarRows; bool get delayedAutosave; int get autosaveDelaySeconds; bool get hideCursorWhileDrawing; UtilitiesState get utilities; StartupBehavior get onStartup; SimpleToolbarVisibility get simpleToolbarVisibility; OptionsPanelPosition get optionsPanelPosition; RenderResolution get renderResolution; bool get moveOnGesture; List get swamps; PackAssetLocation? get selectedPalette; bool get showVerboseLogs; + ThemeMode get theme; ThemeDensity get density; String get localeTag; String get documentPath; double get gestureSensitivity; double get touchSensitivity; double get selectSensitivity; double get scrollSensitivity; bool get penOnlyInput; bool get inputGestures; String get design; BannerVisibility get bannerVisibility;@JsonKey(includeFromJson: false, includeToJson: false) List get history; bool get zoomEnabled; String? get lastVersion;@JsonKey(includeFromJson: false, includeToJson: false) List get connections; String get defaultRemote; bool get nativeTitleBar; bool get startInFullScreen; bool get navigationRail; IgnorePressure get ignorePressure; SyncMode get syncMode; InputConfiguration get inputConfiguration; String get fallbackPack; List get starred;@JsonKey(includeFromJson: false, includeToJson: false) List get favoriteTemplates; String get defaultTemplate; NavigatorPosition get navigatorPosition; ToolbarPosition get toolbarPosition; ToolbarSize get toolbarSize; SortBy get sortBy; SortOrder get sortOrder; double get imageScale; double get pdfQuality; PlatformTheme get platformTheme;@SRGBConverter() List get recentColors; List get flags; bool get spreadPages; bool get highContrast; bool get gridView; bool get autosave; bool get showSaveButton; int get toolbarRows; bool get delayedAutosave; int get autosaveDelaySeconds; bool get hideCursorWhileDrawing; UtilitiesState get utilities; StartupBehavior get onStartup; SimpleToolbarVisibility get simpleToolbarVisibility; OptionsPanelPosition get optionsPanelPosition; RenderResolution get renderResolution; bool get moveOnGesture; List get swamps; PackAssetLocation? get selectedPalette; bool get showVerboseLogs; /// Create a copy of ButterflySettings /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -196,21 +345,21 @@ $ButterflySettingsCopyWith get copyWith => _$ButterflySetting void debugFillProperties(DiagnosticPropertiesBuilder properties) { properties ..add(DiagnosticsProperty('type', 'ButterflySettings')) - ..add(DiagnosticsProperty('theme', theme))..add(DiagnosticsProperty('density', density))..add(DiagnosticsProperty('localeTag', localeTag))..add(DiagnosticsProperty('documentPath', documentPath))..add(DiagnosticsProperty('gestureSensitivity', gestureSensitivity))..add(DiagnosticsProperty('touchSensitivity', touchSensitivity))..add(DiagnosticsProperty('selectSensitivity', selectSensitivity))..add(DiagnosticsProperty('scrollSensitivity', scrollSensitivity))..add(DiagnosticsProperty('penOnlyInput', penOnlyInput))..add(DiagnosticsProperty('inputGestures', inputGestures))..add(DiagnosticsProperty('design', design))..add(DiagnosticsProperty('bannerVisibility', bannerVisibility))..add(DiagnosticsProperty('history', history))..add(DiagnosticsProperty('zoomEnabled', zoomEnabled))..add(DiagnosticsProperty('lastVersion', lastVersion))..add(DiagnosticsProperty('connections', connections))..add(DiagnosticsProperty('defaultRemote', defaultRemote))..add(DiagnosticsProperty('nativeTitleBar', nativeTitleBar))..add(DiagnosticsProperty('startInFullScreen', startInFullScreen))..add(DiagnosticsProperty('navigationRail', navigationRail))..add(DiagnosticsProperty('ignorePressure', ignorePressure))..add(DiagnosticsProperty('syncMode', syncMode))..add(DiagnosticsProperty('inputConfiguration', inputConfiguration))..add(DiagnosticsProperty('fallbackPack', fallbackPack))..add(DiagnosticsProperty('starred', starred))..add(DiagnosticsProperty('defaultTemplate', defaultTemplate))..add(DiagnosticsProperty('navigatorPosition', navigatorPosition))..add(DiagnosticsProperty('toolbarPosition', toolbarPosition))..add(DiagnosticsProperty('toolbarSize', toolbarSize))..add(DiagnosticsProperty('sortBy', sortBy))..add(DiagnosticsProperty('sortOrder', sortOrder))..add(DiagnosticsProperty('imageScale', imageScale))..add(DiagnosticsProperty('pdfQuality', pdfQuality))..add(DiagnosticsProperty('platformTheme', platformTheme))..add(DiagnosticsProperty('recentColors', recentColors))..add(DiagnosticsProperty('flags', flags))..add(DiagnosticsProperty('spreadPages', spreadPages))..add(DiagnosticsProperty('highContrast', highContrast))..add(DiagnosticsProperty('gridView', gridView))..add(DiagnosticsProperty('autosave', autosave))..add(DiagnosticsProperty('showSaveButton', showSaveButton))..add(DiagnosticsProperty('toolbarRows', toolbarRows))..add(DiagnosticsProperty('delayedAutosave', delayedAutosave))..add(DiagnosticsProperty('autosaveDelaySeconds', autosaveDelaySeconds))..add(DiagnosticsProperty('hideCursorWhileDrawing', hideCursorWhileDrawing))..add(DiagnosticsProperty('utilities', utilities))..add(DiagnosticsProperty('onStartup', onStartup))..add(DiagnosticsProperty('simpleToolbarVisibility', simpleToolbarVisibility))..add(DiagnosticsProperty('optionsPanelPosition', optionsPanelPosition))..add(DiagnosticsProperty('renderResolution', renderResolution))..add(DiagnosticsProperty('moveOnGesture', moveOnGesture))..add(DiagnosticsProperty('swamps', swamps))..add(DiagnosticsProperty('selectedPalette', selectedPalette))..add(DiagnosticsProperty('showVerboseLogs', showVerboseLogs)); + ..add(DiagnosticsProperty('theme', theme))..add(DiagnosticsProperty('density', density))..add(DiagnosticsProperty('localeTag', localeTag))..add(DiagnosticsProperty('documentPath', documentPath))..add(DiagnosticsProperty('gestureSensitivity', gestureSensitivity))..add(DiagnosticsProperty('touchSensitivity', touchSensitivity))..add(DiagnosticsProperty('selectSensitivity', selectSensitivity))..add(DiagnosticsProperty('scrollSensitivity', scrollSensitivity))..add(DiagnosticsProperty('penOnlyInput', penOnlyInput))..add(DiagnosticsProperty('inputGestures', inputGestures))..add(DiagnosticsProperty('design', design))..add(DiagnosticsProperty('bannerVisibility', bannerVisibility))..add(DiagnosticsProperty('history', history))..add(DiagnosticsProperty('zoomEnabled', zoomEnabled))..add(DiagnosticsProperty('lastVersion', lastVersion))..add(DiagnosticsProperty('connections', connections))..add(DiagnosticsProperty('defaultRemote', defaultRemote))..add(DiagnosticsProperty('nativeTitleBar', nativeTitleBar))..add(DiagnosticsProperty('startInFullScreen', startInFullScreen))..add(DiagnosticsProperty('navigationRail', navigationRail))..add(DiagnosticsProperty('ignorePressure', ignorePressure))..add(DiagnosticsProperty('syncMode', syncMode))..add(DiagnosticsProperty('inputConfiguration', inputConfiguration))..add(DiagnosticsProperty('fallbackPack', fallbackPack))..add(DiagnosticsProperty('starred', starred))..add(DiagnosticsProperty('favoriteTemplates', favoriteTemplates))..add(DiagnosticsProperty('defaultTemplate', defaultTemplate))..add(DiagnosticsProperty('navigatorPosition', navigatorPosition))..add(DiagnosticsProperty('toolbarPosition', toolbarPosition))..add(DiagnosticsProperty('toolbarSize', toolbarSize))..add(DiagnosticsProperty('sortBy', sortBy))..add(DiagnosticsProperty('sortOrder', sortOrder))..add(DiagnosticsProperty('imageScale', imageScale))..add(DiagnosticsProperty('pdfQuality', pdfQuality))..add(DiagnosticsProperty('platformTheme', platformTheme))..add(DiagnosticsProperty('recentColors', recentColors))..add(DiagnosticsProperty('flags', flags))..add(DiagnosticsProperty('spreadPages', spreadPages))..add(DiagnosticsProperty('highContrast', highContrast))..add(DiagnosticsProperty('gridView', gridView))..add(DiagnosticsProperty('autosave', autosave))..add(DiagnosticsProperty('showSaveButton', showSaveButton))..add(DiagnosticsProperty('toolbarRows', toolbarRows))..add(DiagnosticsProperty('delayedAutosave', delayedAutosave))..add(DiagnosticsProperty('autosaveDelaySeconds', autosaveDelaySeconds))..add(DiagnosticsProperty('hideCursorWhileDrawing', hideCursorWhileDrawing))..add(DiagnosticsProperty('utilities', utilities))..add(DiagnosticsProperty('onStartup', onStartup))..add(DiagnosticsProperty('simpleToolbarVisibility', simpleToolbarVisibility))..add(DiagnosticsProperty('optionsPanelPosition', optionsPanelPosition))..add(DiagnosticsProperty('renderResolution', renderResolution))..add(DiagnosticsProperty('moveOnGesture', moveOnGesture))..add(DiagnosticsProperty('swamps', swamps))..add(DiagnosticsProperty('selectedPalette', selectedPalette))..add(DiagnosticsProperty('showVerboseLogs', showVerboseLogs)); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is ButterflySettings&&(identical(other.theme, theme) || other.theme == theme)&&(identical(other.density, density) || other.density == density)&&(identical(other.localeTag, localeTag) || other.localeTag == localeTag)&&(identical(other.documentPath, documentPath) || other.documentPath == documentPath)&&(identical(other.gestureSensitivity, gestureSensitivity) || other.gestureSensitivity == gestureSensitivity)&&(identical(other.touchSensitivity, touchSensitivity) || other.touchSensitivity == touchSensitivity)&&(identical(other.selectSensitivity, selectSensitivity) || other.selectSensitivity == selectSensitivity)&&(identical(other.scrollSensitivity, scrollSensitivity) || other.scrollSensitivity == scrollSensitivity)&&(identical(other.penOnlyInput, penOnlyInput) || other.penOnlyInput == penOnlyInput)&&(identical(other.inputGestures, inputGestures) || other.inputGestures == inputGestures)&&(identical(other.design, design) || other.design == design)&&(identical(other.bannerVisibility, bannerVisibility) || other.bannerVisibility == bannerVisibility)&&const DeepCollectionEquality().equals(other.history, history)&&(identical(other.zoomEnabled, zoomEnabled) || other.zoomEnabled == zoomEnabled)&&(identical(other.lastVersion, lastVersion) || other.lastVersion == lastVersion)&&const DeepCollectionEquality().equals(other.connections, connections)&&(identical(other.defaultRemote, defaultRemote) || other.defaultRemote == defaultRemote)&&(identical(other.nativeTitleBar, nativeTitleBar) || other.nativeTitleBar == nativeTitleBar)&&(identical(other.startInFullScreen, startInFullScreen) || other.startInFullScreen == startInFullScreen)&&(identical(other.navigationRail, navigationRail) || other.navigationRail == navigationRail)&&(identical(other.ignorePressure, ignorePressure) || other.ignorePressure == ignorePressure)&&(identical(other.syncMode, syncMode) || other.syncMode == syncMode)&&(identical(other.inputConfiguration, inputConfiguration) || other.inputConfiguration == inputConfiguration)&&(identical(other.fallbackPack, fallbackPack) || other.fallbackPack == fallbackPack)&&const DeepCollectionEquality().equals(other.starred, starred)&&(identical(other.defaultTemplate, defaultTemplate) || other.defaultTemplate == defaultTemplate)&&(identical(other.navigatorPosition, navigatorPosition) || other.navigatorPosition == navigatorPosition)&&(identical(other.toolbarPosition, toolbarPosition) || other.toolbarPosition == toolbarPosition)&&(identical(other.toolbarSize, toolbarSize) || other.toolbarSize == toolbarSize)&&(identical(other.sortBy, sortBy) || other.sortBy == sortBy)&&(identical(other.sortOrder, sortOrder) || other.sortOrder == sortOrder)&&(identical(other.imageScale, imageScale) || other.imageScale == imageScale)&&(identical(other.pdfQuality, pdfQuality) || other.pdfQuality == pdfQuality)&&(identical(other.platformTheme, platformTheme) || other.platformTheme == platformTheme)&&const DeepCollectionEquality().equals(other.recentColors, recentColors)&&const DeepCollectionEquality().equals(other.flags, flags)&&(identical(other.spreadPages, spreadPages) || other.spreadPages == spreadPages)&&(identical(other.highContrast, highContrast) || other.highContrast == highContrast)&&(identical(other.gridView, gridView) || other.gridView == gridView)&&(identical(other.autosave, autosave) || other.autosave == autosave)&&(identical(other.showSaveButton, showSaveButton) || other.showSaveButton == showSaveButton)&&(identical(other.toolbarRows, toolbarRows) || other.toolbarRows == toolbarRows)&&(identical(other.delayedAutosave, delayedAutosave) || other.delayedAutosave == delayedAutosave)&&(identical(other.autosaveDelaySeconds, autosaveDelaySeconds) || other.autosaveDelaySeconds == autosaveDelaySeconds)&&(identical(other.hideCursorWhileDrawing, hideCursorWhileDrawing) || other.hideCursorWhileDrawing == hideCursorWhileDrawing)&&(identical(other.utilities, utilities) || other.utilities == utilities)&&(identical(other.onStartup, onStartup) || other.onStartup == onStartup)&&(identical(other.simpleToolbarVisibility, simpleToolbarVisibility) || other.simpleToolbarVisibility == simpleToolbarVisibility)&&(identical(other.optionsPanelPosition, optionsPanelPosition) || other.optionsPanelPosition == optionsPanelPosition)&&(identical(other.renderResolution, renderResolution) || other.renderResolution == renderResolution)&&(identical(other.moveOnGesture, moveOnGesture) || other.moveOnGesture == moveOnGesture)&&const DeepCollectionEquality().equals(other.swamps, swamps)&&(identical(other.selectedPalette, selectedPalette) || other.selectedPalette == selectedPalette)&&(identical(other.showVerboseLogs, showVerboseLogs) || other.showVerboseLogs == showVerboseLogs)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is ButterflySettings&&(identical(other.theme, theme) || other.theme == theme)&&(identical(other.density, density) || other.density == density)&&(identical(other.localeTag, localeTag) || other.localeTag == localeTag)&&(identical(other.documentPath, documentPath) || other.documentPath == documentPath)&&(identical(other.gestureSensitivity, gestureSensitivity) || other.gestureSensitivity == gestureSensitivity)&&(identical(other.touchSensitivity, touchSensitivity) || other.touchSensitivity == touchSensitivity)&&(identical(other.selectSensitivity, selectSensitivity) || other.selectSensitivity == selectSensitivity)&&(identical(other.scrollSensitivity, scrollSensitivity) || other.scrollSensitivity == scrollSensitivity)&&(identical(other.penOnlyInput, penOnlyInput) || other.penOnlyInput == penOnlyInput)&&(identical(other.inputGestures, inputGestures) || other.inputGestures == inputGestures)&&(identical(other.design, design) || other.design == design)&&(identical(other.bannerVisibility, bannerVisibility) || other.bannerVisibility == bannerVisibility)&&const DeepCollectionEquality().equals(other.history, history)&&(identical(other.zoomEnabled, zoomEnabled) || other.zoomEnabled == zoomEnabled)&&(identical(other.lastVersion, lastVersion) || other.lastVersion == lastVersion)&&const DeepCollectionEquality().equals(other.connections, connections)&&(identical(other.defaultRemote, defaultRemote) || other.defaultRemote == defaultRemote)&&(identical(other.nativeTitleBar, nativeTitleBar) || other.nativeTitleBar == nativeTitleBar)&&(identical(other.startInFullScreen, startInFullScreen) || other.startInFullScreen == startInFullScreen)&&(identical(other.navigationRail, navigationRail) || other.navigationRail == navigationRail)&&(identical(other.ignorePressure, ignorePressure) || other.ignorePressure == ignorePressure)&&(identical(other.syncMode, syncMode) || other.syncMode == syncMode)&&(identical(other.inputConfiguration, inputConfiguration) || other.inputConfiguration == inputConfiguration)&&(identical(other.fallbackPack, fallbackPack) || other.fallbackPack == fallbackPack)&&const DeepCollectionEquality().equals(other.starred, starred)&&const DeepCollectionEquality().equals(other.favoriteTemplates, favoriteTemplates)&&(identical(other.defaultTemplate, defaultTemplate) || other.defaultTemplate == defaultTemplate)&&(identical(other.navigatorPosition, navigatorPosition) || other.navigatorPosition == navigatorPosition)&&(identical(other.toolbarPosition, toolbarPosition) || other.toolbarPosition == toolbarPosition)&&(identical(other.toolbarSize, toolbarSize) || other.toolbarSize == toolbarSize)&&(identical(other.sortBy, sortBy) || other.sortBy == sortBy)&&(identical(other.sortOrder, sortOrder) || other.sortOrder == sortOrder)&&(identical(other.imageScale, imageScale) || other.imageScale == imageScale)&&(identical(other.pdfQuality, pdfQuality) || other.pdfQuality == pdfQuality)&&(identical(other.platformTheme, platformTheme) || other.platformTheme == platformTheme)&&const DeepCollectionEquality().equals(other.recentColors, recentColors)&&const DeepCollectionEquality().equals(other.flags, flags)&&(identical(other.spreadPages, spreadPages) || other.spreadPages == spreadPages)&&(identical(other.highContrast, highContrast) || other.highContrast == highContrast)&&(identical(other.gridView, gridView) || other.gridView == gridView)&&(identical(other.autosave, autosave) || other.autosave == autosave)&&(identical(other.showSaveButton, showSaveButton) || other.showSaveButton == showSaveButton)&&(identical(other.toolbarRows, toolbarRows) || other.toolbarRows == toolbarRows)&&(identical(other.delayedAutosave, delayedAutosave) || other.delayedAutosave == delayedAutosave)&&(identical(other.autosaveDelaySeconds, autosaveDelaySeconds) || other.autosaveDelaySeconds == autosaveDelaySeconds)&&(identical(other.hideCursorWhileDrawing, hideCursorWhileDrawing) || other.hideCursorWhileDrawing == hideCursorWhileDrawing)&&(identical(other.utilities, utilities) || other.utilities == utilities)&&(identical(other.onStartup, onStartup) || other.onStartup == onStartup)&&(identical(other.simpleToolbarVisibility, simpleToolbarVisibility) || other.simpleToolbarVisibility == simpleToolbarVisibility)&&(identical(other.optionsPanelPosition, optionsPanelPosition) || other.optionsPanelPosition == optionsPanelPosition)&&(identical(other.renderResolution, renderResolution) || other.renderResolution == renderResolution)&&(identical(other.moveOnGesture, moveOnGesture) || other.moveOnGesture == moveOnGesture)&&const DeepCollectionEquality().equals(other.swamps, swamps)&&(identical(other.selectedPalette, selectedPalette) || other.selectedPalette == selectedPalette)&&(identical(other.showVerboseLogs, showVerboseLogs) || other.showVerboseLogs == showVerboseLogs)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hashAll([runtimeType,theme,density,localeTag,documentPath,gestureSensitivity,touchSensitivity,selectSensitivity,scrollSensitivity,penOnlyInput,inputGestures,design,bannerVisibility,const DeepCollectionEquality().hash(history),zoomEnabled,lastVersion,const DeepCollectionEquality().hash(connections),defaultRemote,nativeTitleBar,startInFullScreen,navigationRail,ignorePressure,syncMode,inputConfiguration,fallbackPack,const DeepCollectionEquality().hash(starred),defaultTemplate,navigatorPosition,toolbarPosition,toolbarSize,sortBy,sortOrder,imageScale,pdfQuality,platformTheme,const DeepCollectionEquality().hash(recentColors),const DeepCollectionEquality().hash(flags),spreadPages,highContrast,gridView,autosave,showSaveButton,toolbarRows,delayedAutosave,autosaveDelaySeconds,hideCursorWhileDrawing,utilities,onStartup,simpleToolbarVisibility,optionsPanelPosition,renderResolution,moveOnGesture,const DeepCollectionEquality().hash(swamps),selectedPalette,showVerboseLogs]); +int get hashCode => Object.hashAll([runtimeType,theme,density,localeTag,documentPath,gestureSensitivity,touchSensitivity,selectSensitivity,scrollSensitivity,penOnlyInput,inputGestures,design,bannerVisibility,const DeepCollectionEquality().hash(history),zoomEnabled,lastVersion,const DeepCollectionEquality().hash(connections),defaultRemote,nativeTitleBar,startInFullScreen,navigationRail,ignorePressure,syncMode,inputConfiguration,fallbackPack,const DeepCollectionEquality().hash(starred),const DeepCollectionEquality().hash(favoriteTemplates),defaultTemplate,navigatorPosition,toolbarPosition,toolbarSize,sortBy,sortOrder,imageScale,pdfQuality,platformTheme,const DeepCollectionEquality().hash(recentColors),const DeepCollectionEquality().hash(flags),spreadPages,highContrast,gridView,autosave,showSaveButton,toolbarRows,delayedAutosave,autosaveDelaySeconds,hideCursorWhileDrawing,utilities,onStartup,simpleToolbarVisibility,optionsPanelPosition,renderResolution,moveOnGesture,const DeepCollectionEquality().hash(swamps),selectedPalette,showVerboseLogs]); @override String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { - return 'ButterflySettings(theme: $theme, density: $density, localeTag: $localeTag, documentPath: $documentPath, gestureSensitivity: $gestureSensitivity, touchSensitivity: $touchSensitivity, selectSensitivity: $selectSensitivity, scrollSensitivity: $scrollSensitivity, penOnlyInput: $penOnlyInput, inputGestures: $inputGestures, design: $design, bannerVisibility: $bannerVisibility, history: $history, zoomEnabled: $zoomEnabled, lastVersion: $lastVersion, connections: $connections, defaultRemote: $defaultRemote, nativeTitleBar: $nativeTitleBar, startInFullScreen: $startInFullScreen, navigationRail: $navigationRail, ignorePressure: $ignorePressure, syncMode: $syncMode, inputConfiguration: $inputConfiguration, fallbackPack: $fallbackPack, starred: $starred, defaultTemplate: $defaultTemplate, navigatorPosition: $navigatorPosition, toolbarPosition: $toolbarPosition, toolbarSize: $toolbarSize, sortBy: $sortBy, sortOrder: $sortOrder, imageScale: $imageScale, pdfQuality: $pdfQuality, platformTheme: $platformTheme, recentColors: $recentColors, flags: $flags, spreadPages: $spreadPages, highContrast: $highContrast, gridView: $gridView, autosave: $autosave, showSaveButton: $showSaveButton, toolbarRows: $toolbarRows, delayedAutosave: $delayedAutosave, autosaveDelaySeconds: $autosaveDelaySeconds, hideCursorWhileDrawing: $hideCursorWhileDrawing, utilities: $utilities, onStartup: $onStartup, simpleToolbarVisibility: $simpleToolbarVisibility, optionsPanelPosition: $optionsPanelPosition, renderResolution: $renderResolution, moveOnGesture: $moveOnGesture, swamps: $swamps, selectedPalette: $selectedPalette, showVerboseLogs: $showVerboseLogs)'; + return 'ButterflySettings(theme: $theme, density: $density, localeTag: $localeTag, documentPath: $documentPath, gestureSensitivity: $gestureSensitivity, touchSensitivity: $touchSensitivity, selectSensitivity: $selectSensitivity, scrollSensitivity: $scrollSensitivity, penOnlyInput: $penOnlyInput, inputGestures: $inputGestures, design: $design, bannerVisibility: $bannerVisibility, history: $history, zoomEnabled: $zoomEnabled, lastVersion: $lastVersion, connections: $connections, defaultRemote: $defaultRemote, nativeTitleBar: $nativeTitleBar, startInFullScreen: $startInFullScreen, navigationRail: $navigationRail, ignorePressure: $ignorePressure, syncMode: $syncMode, inputConfiguration: $inputConfiguration, fallbackPack: $fallbackPack, starred: $starred, favoriteTemplates: $favoriteTemplates, defaultTemplate: $defaultTemplate, navigatorPosition: $navigatorPosition, toolbarPosition: $toolbarPosition, toolbarSize: $toolbarSize, sortBy: $sortBy, sortOrder: $sortOrder, imageScale: $imageScale, pdfQuality: $pdfQuality, platformTheme: $platformTheme, recentColors: $recentColors, flags: $flags, spreadPages: $spreadPages, highContrast: $highContrast, gridView: $gridView, autosave: $autosave, showSaveButton: $showSaveButton, toolbarRows: $toolbarRows, delayedAutosave: $delayedAutosave, autosaveDelaySeconds: $autosaveDelaySeconds, hideCursorWhileDrawing: $hideCursorWhileDrawing, utilities: $utilities, onStartup: $onStartup, simpleToolbarVisibility: $simpleToolbarVisibility, optionsPanelPosition: $optionsPanelPosition, renderResolution: $renderResolution, moveOnGesture: $moveOnGesture, swamps: $swamps, selectedPalette: $selectedPalette, showVerboseLogs: $showVerboseLogs)'; } @@ -221,7 +370,7 @@ abstract mixin class $ButterflySettingsCopyWith<$Res> { factory $ButterflySettingsCopyWith(ButterflySettings value, $Res Function(ButterflySettings) _then) = _$ButterflySettingsCopyWithImpl; @useResult $Res call({ - ThemeMode theme, ThemeDensity density, String localeTag, String documentPath, double gestureSensitivity, double touchSensitivity, double selectSensitivity, double scrollSensitivity, bool penOnlyInput, bool inputGestures, String design, BannerVisibility bannerVisibility,@JsonKey(includeFromJson: false, includeToJson: false) List history, bool zoomEnabled, String? lastVersion,@JsonKey(includeFromJson: false, includeToJson: false) List connections, String defaultRemote, bool nativeTitleBar, bool startInFullScreen, bool navigationRail, IgnorePressure ignorePressure, SyncMode syncMode, InputConfiguration inputConfiguration, String fallbackPack, List starred, String defaultTemplate, NavigatorPosition navigatorPosition, ToolbarPosition toolbarPosition, ToolbarSize toolbarSize, SortBy sortBy, SortOrder sortOrder, double imageScale, double pdfQuality, PlatformTheme platformTheme,@SRGBConverter() List recentColors, List flags, bool spreadPages, bool highContrast, bool gridView, bool autosave, bool showSaveButton, int toolbarRows, bool delayedAutosave, int autosaveDelaySeconds, bool hideCursorWhileDrawing, UtilitiesState utilities, StartupBehavior onStartup, SimpleToolbarVisibility simpleToolbarVisibility, OptionsPanelPosition optionsPanelPosition, RenderResolution renderResolution, bool moveOnGesture, List swamps, PackAssetLocation? selectedPalette, bool showVerboseLogs + ThemeMode theme, ThemeDensity density, String localeTag, String documentPath, double gestureSensitivity, double touchSensitivity, double selectSensitivity, double scrollSensitivity, bool penOnlyInput, bool inputGestures, String design, BannerVisibility bannerVisibility,@JsonKey(includeFromJson: false, includeToJson: false) List history, bool zoomEnabled, String? lastVersion,@JsonKey(includeFromJson: false, includeToJson: false) List connections, String defaultRemote, bool nativeTitleBar, bool startInFullScreen, bool navigationRail, IgnorePressure ignorePressure, SyncMode syncMode, InputConfiguration inputConfiguration, String fallbackPack, List starred,@JsonKey(includeFromJson: false, includeToJson: false) List favoriteTemplates, String defaultTemplate, NavigatorPosition navigatorPosition, ToolbarPosition toolbarPosition, ToolbarSize toolbarSize, SortBy sortBy, SortOrder sortOrder, double imageScale, double pdfQuality, PlatformTheme platformTheme,@SRGBConverter() List recentColors, List flags, bool spreadPages, bool highContrast, bool gridView, bool autosave, bool showSaveButton, int toolbarRows, bool delayedAutosave, int autosaveDelaySeconds, bool hideCursorWhileDrawing, UtilitiesState utilities, StartupBehavior onStartup, SimpleToolbarVisibility simpleToolbarVisibility, OptionsPanelPosition optionsPanelPosition, RenderResolution renderResolution, bool moveOnGesture, List swamps, PackAssetLocation? selectedPalette, bool showVerboseLogs }); @@ -238,7 +387,7 @@ class _$ButterflySettingsCopyWithImpl<$Res> /// Create a copy of ButterflySettings /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? theme = null,Object? density = null,Object? localeTag = null,Object? documentPath = null,Object? gestureSensitivity = null,Object? touchSensitivity = null,Object? selectSensitivity = null,Object? scrollSensitivity = null,Object? penOnlyInput = null,Object? inputGestures = null,Object? design = null,Object? bannerVisibility = null,Object? history = null,Object? zoomEnabled = null,Object? lastVersion = freezed,Object? connections = null,Object? defaultRemote = null,Object? nativeTitleBar = null,Object? startInFullScreen = null,Object? navigationRail = null,Object? ignorePressure = null,Object? syncMode = null,Object? inputConfiguration = null,Object? fallbackPack = null,Object? starred = null,Object? defaultTemplate = null,Object? navigatorPosition = null,Object? toolbarPosition = null,Object? toolbarSize = null,Object? sortBy = null,Object? sortOrder = null,Object? imageScale = null,Object? pdfQuality = null,Object? platformTheme = null,Object? recentColors = null,Object? flags = null,Object? spreadPages = null,Object? highContrast = null,Object? gridView = null,Object? autosave = null,Object? showSaveButton = null,Object? toolbarRows = null,Object? delayedAutosave = null,Object? autosaveDelaySeconds = null,Object? hideCursorWhileDrawing = null,Object? utilities = null,Object? onStartup = null,Object? simpleToolbarVisibility = null,Object? optionsPanelPosition = null,Object? renderResolution = null,Object? moveOnGesture = null,Object? swamps = null,Object? selectedPalette = freezed,Object? showVerboseLogs = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? theme = null,Object? density = null,Object? localeTag = null,Object? documentPath = null,Object? gestureSensitivity = null,Object? touchSensitivity = null,Object? selectSensitivity = null,Object? scrollSensitivity = null,Object? penOnlyInput = null,Object? inputGestures = null,Object? design = null,Object? bannerVisibility = null,Object? history = null,Object? zoomEnabled = null,Object? lastVersion = freezed,Object? connections = null,Object? defaultRemote = null,Object? nativeTitleBar = null,Object? startInFullScreen = null,Object? navigationRail = null,Object? ignorePressure = null,Object? syncMode = null,Object? inputConfiguration = null,Object? fallbackPack = null,Object? starred = null,Object? favoriteTemplates = null,Object? defaultTemplate = null,Object? navigatorPosition = null,Object? toolbarPosition = null,Object? toolbarSize = null,Object? sortBy = null,Object? sortOrder = null,Object? imageScale = null,Object? pdfQuality = null,Object? platformTheme = null,Object? recentColors = null,Object? flags = null,Object? spreadPages = null,Object? highContrast = null,Object? gridView = null,Object? autosave = null,Object? showSaveButton = null,Object? toolbarRows = null,Object? delayedAutosave = null,Object? autosaveDelaySeconds = null,Object? hideCursorWhileDrawing = null,Object? utilities = null,Object? onStartup = null,Object? simpleToolbarVisibility = null,Object? optionsPanelPosition = null,Object? renderResolution = null,Object? moveOnGesture = null,Object? swamps = null,Object? selectedPalette = freezed,Object? showVerboseLogs = null,}) { return _then(_self.copyWith( theme: null == theme ? _self.theme : theme // ignore: cast_nullable_to_non_nullable as ThemeMode,density: null == density ? _self.density : density // ignore: cast_nullable_to_non_nullable @@ -265,7 +414,8 @@ as IgnorePressure,syncMode: null == syncMode ? _self.syncMode : syncMode // igno as SyncMode,inputConfiguration: null == inputConfiguration ? _self.inputConfiguration : inputConfiguration // ignore: cast_nullable_to_non_nullable as InputConfiguration,fallbackPack: null == fallbackPack ? _self.fallbackPack : fallbackPack // ignore: cast_nullable_to_non_nullable as String,starred: null == starred ? _self.starred : starred // ignore: cast_nullable_to_non_nullable -as List,defaultTemplate: null == defaultTemplate ? _self.defaultTemplate : defaultTemplate // ignore: cast_nullable_to_non_nullable +as List,favoriteTemplates: null == favoriteTemplates ? _self.favoriteTemplates : favoriteTemplates // ignore: cast_nullable_to_non_nullable +as List,defaultTemplate: null == defaultTemplate ? _self.defaultTemplate : defaultTemplate // ignore: cast_nullable_to_non_nullable as String,navigatorPosition: null == navigatorPosition ? _self.navigatorPosition : navigatorPosition // ignore: cast_nullable_to_non_nullable as NavigatorPosition,toolbarPosition: null == toolbarPosition ? _self.toolbarPosition : toolbarPosition // ignore: cast_nullable_to_non_nullable as ToolbarPosition,toolbarSize: null == toolbarSize ? _self.toolbarSize : toolbarSize // ignore: cast_nullable_to_non_nullable @@ -336,7 +486,7 @@ $PackAssetLocationCopyWith<$Res>? get selectedPalette { @JsonSerializable() class _ButterflySettings extends ButterflySettings with DiagnosticableTreeMixin { - const _ButterflySettings({this.theme = ThemeMode.system, this.density = ThemeDensity.system, this.localeTag = '', this.documentPath = '', this.gestureSensitivity = 1, this.touchSensitivity = 1, this.selectSensitivity = 1, this.scrollSensitivity = 1, this.penOnlyInput = false, this.inputGestures = true, this.design = '', this.bannerVisibility = BannerVisibility.always, @JsonKey(includeFromJson: false, includeToJson: false) final List history = const [], this.zoomEnabled = true, this.lastVersion, @JsonKey(includeFromJson: false, includeToJson: false) final List connections = const [], this.defaultRemote = '', this.nativeTitleBar = false, this.startInFullScreen = false, this.navigationRail = true, this.ignorePressure = IgnorePressure.first, this.syncMode = SyncMode.noMobile, this.inputConfiguration = const InputConfiguration(), this.fallbackPack = '', final List starred = const [], this.defaultTemplate = '', this.navigatorPosition = NavigatorPosition.left, this.toolbarPosition = ToolbarPosition.inline, this.toolbarSize = ToolbarSize.normal, this.sortBy = SortBy.modified, this.sortOrder = SortOrder.descending, this.imageScale = 0.5, this.pdfQuality = 2, this.platformTheme = PlatformTheme.system, @SRGBConverter() final List recentColors = const [], final List flags = const [], this.spreadPages = false, this.highContrast = false, this.gridView = false, this.autosave = true, this.showSaveButton = true, this.toolbarRows = 1, this.delayedAutosave = true, this.autosaveDelaySeconds = 3, this.hideCursorWhileDrawing = false, this.utilities = const UtilitiesState(), this.onStartup = StartupBehavior.openHomeScreen, this.simpleToolbarVisibility = SimpleToolbarVisibility.show, this.optionsPanelPosition = OptionsPanelPosition.top, this.renderResolution = RenderResolution.normal, this.moveOnGesture = true, final List swamps = const [], this.selectedPalette, this.showVerboseLogs = false}): _history = history,_connections = connections,_starred = starred,_recentColors = recentColors,_flags = flags,_swamps = swamps,super._(); + const _ButterflySettings({this.theme = ThemeMode.system, this.density = ThemeDensity.system, this.localeTag = '', this.documentPath = '', this.gestureSensitivity = 1, this.touchSensitivity = 1, this.selectSensitivity = 1, this.scrollSensitivity = 1, this.penOnlyInput = false, this.inputGestures = true, this.design = '', this.bannerVisibility = BannerVisibility.always, @JsonKey(includeFromJson: false, includeToJson: false) final List history = const [], this.zoomEnabled = true, this.lastVersion, @JsonKey(includeFromJson: false, includeToJson: false) final List connections = const [], this.defaultRemote = '', this.nativeTitleBar = false, this.startInFullScreen = false, this.navigationRail = true, this.ignorePressure = IgnorePressure.first, this.syncMode = SyncMode.noMobile, this.inputConfiguration = const InputConfiguration(), this.fallbackPack = '', final List starred = const [], @JsonKey(includeFromJson: false, includeToJson: false) final List favoriteTemplates = const [], this.defaultTemplate = '', this.navigatorPosition = NavigatorPosition.left, this.toolbarPosition = ToolbarPosition.inline, this.toolbarSize = ToolbarSize.normal, this.sortBy = SortBy.modified, this.sortOrder = SortOrder.descending, this.imageScale = 0.5, this.pdfQuality = 2, this.platformTheme = PlatformTheme.system, @SRGBConverter() final List recentColors = const [], final List flags = const [], this.spreadPages = false, this.highContrast = false, this.gridView = false, this.autosave = true, this.showSaveButton = true, this.toolbarRows = 1, this.delayedAutosave = true, this.autosaveDelaySeconds = 3, this.hideCursorWhileDrawing = false, this.utilities = const UtilitiesState(), this.onStartup = StartupBehavior.openHomeScreen, this.simpleToolbarVisibility = SimpleToolbarVisibility.show, this.optionsPanelPosition = OptionsPanelPosition.top, this.renderResolution = RenderResolution.normal, this.moveOnGesture = true, final List swamps = const [], this.selectedPalette, this.showVerboseLogs = false}): _history = history,_connections = connections,_starred = starred,_favoriteTemplates = favoriteTemplates,_recentColors = recentColors,_flags = flags,_swamps = swamps,super._(); factory _ButterflySettings.fromJson(Map json) => _$ButterflySettingsFromJson(json); @override@JsonKey() final ThemeMode theme; @@ -382,6 +532,13 @@ class _ButterflySettings extends ButterflySettings with DiagnosticableTreeMixin return EqualUnmodifiableListView(_starred); } + final List _favoriteTemplates; +@override@JsonKey(includeFromJson: false, includeToJson: false) List get favoriteTemplates { + if (_favoriteTemplates is EqualUnmodifiableListView) return _favoriteTemplates; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_favoriteTemplates); +} + @override@JsonKey() final String defaultTemplate; @override@JsonKey() final NavigatorPosition navigatorPosition; @override@JsonKey() final ToolbarPosition toolbarPosition; @@ -444,21 +601,21 @@ Map toJson() { void debugFillProperties(DiagnosticPropertiesBuilder properties) { properties ..add(DiagnosticsProperty('type', 'ButterflySettings')) - ..add(DiagnosticsProperty('theme', theme))..add(DiagnosticsProperty('density', density))..add(DiagnosticsProperty('localeTag', localeTag))..add(DiagnosticsProperty('documentPath', documentPath))..add(DiagnosticsProperty('gestureSensitivity', gestureSensitivity))..add(DiagnosticsProperty('touchSensitivity', touchSensitivity))..add(DiagnosticsProperty('selectSensitivity', selectSensitivity))..add(DiagnosticsProperty('scrollSensitivity', scrollSensitivity))..add(DiagnosticsProperty('penOnlyInput', penOnlyInput))..add(DiagnosticsProperty('inputGestures', inputGestures))..add(DiagnosticsProperty('design', design))..add(DiagnosticsProperty('bannerVisibility', bannerVisibility))..add(DiagnosticsProperty('history', history))..add(DiagnosticsProperty('zoomEnabled', zoomEnabled))..add(DiagnosticsProperty('lastVersion', lastVersion))..add(DiagnosticsProperty('connections', connections))..add(DiagnosticsProperty('defaultRemote', defaultRemote))..add(DiagnosticsProperty('nativeTitleBar', nativeTitleBar))..add(DiagnosticsProperty('startInFullScreen', startInFullScreen))..add(DiagnosticsProperty('navigationRail', navigationRail))..add(DiagnosticsProperty('ignorePressure', ignorePressure))..add(DiagnosticsProperty('syncMode', syncMode))..add(DiagnosticsProperty('inputConfiguration', inputConfiguration))..add(DiagnosticsProperty('fallbackPack', fallbackPack))..add(DiagnosticsProperty('starred', starred))..add(DiagnosticsProperty('defaultTemplate', defaultTemplate))..add(DiagnosticsProperty('navigatorPosition', navigatorPosition))..add(DiagnosticsProperty('toolbarPosition', toolbarPosition))..add(DiagnosticsProperty('toolbarSize', toolbarSize))..add(DiagnosticsProperty('sortBy', sortBy))..add(DiagnosticsProperty('sortOrder', sortOrder))..add(DiagnosticsProperty('imageScale', imageScale))..add(DiagnosticsProperty('pdfQuality', pdfQuality))..add(DiagnosticsProperty('platformTheme', platformTheme))..add(DiagnosticsProperty('recentColors', recentColors))..add(DiagnosticsProperty('flags', flags))..add(DiagnosticsProperty('spreadPages', spreadPages))..add(DiagnosticsProperty('highContrast', highContrast))..add(DiagnosticsProperty('gridView', gridView))..add(DiagnosticsProperty('autosave', autosave))..add(DiagnosticsProperty('showSaveButton', showSaveButton))..add(DiagnosticsProperty('toolbarRows', toolbarRows))..add(DiagnosticsProperty('delayedAutosave', delayedAutosave))..add(DiagnosticsProperty('autosaveDelaySeconds', autosaveDelaySeconds))..add(DiagnosticsProperty('hideCursorWhileDrawing', hideCursorWhileDrawing))..add(DiagnosticsProperty('utilities', utilities))..add(DiagnosticsProperty('onStartup', onStartup))..add(DiagnosticsProperty('simpleToolbarVisibility', simpleToolbarVisibility))..add(DiagnosticsProperty('optionsPanelPosition', optionsPanelPosition))..add(DiagnosticsProperty('renderResolution', renderResolution))..add(DiagnosticsProperty('moveOnGesture', moveOnGesture))..add(DiagnosticsProperty('swamps', swamps))..add(DiagnosticsProperty('selectedPalette', selectedPalette))..add(DiagnosticsProperty('showVerboseLogs', showVerboseLogs)); + ..add(DiagnosticsProperty('theme', theme))..add(DiagnosticsProperty('density', density))..add(DiagnosticsProperty('localeTag', localeTag))..add(DiagnosticsProperty('documentPath', documentPath))..add(DiagnosticsProperty('gestureSensitivity', gestureSensitivity))..add(DiagnosticsProperty('touchSensitivity', touchSensitivity))..add(DiagnosticsProperty('selectSensitivity', selectSensitivity))..add(DiagnosticsProperty('scrollSensitivity', scrollSensitivity))..add(DiagnosticsProperty('penOnlyInput', penOnlyInput))..add(DiagnosticsProperty('inputGestures', inputGestures))..add(DiagnosticsProperty('design', design))..add(DiagnosticsProperty('bannerVisibility', bannerVisibility))..add(DiagnosticsProperty('history', history))..add(DiagnosticsProperty('zoomEnabled', zoomEnabled))..add(DiagnosticsProperty('lastVersion', lastVersion))..add(DiagnosticsProperty('connections', connections))..add(DiagnosticsProperty('defaultRemote', defaultRemote))..add(DiagnosticsProperty('nativeTitleBar', nativeTitleBar))..add(DiagnosticsProperty('startInFullScreen', startInFullScreen))..add(DiagnosticsProperty('navigationRail', navigationRail))..add(DiagnosticsProperty('ignorePressure', ignorePressure))..add(DiagnosticsProperty('syncMode', syncMode))..add(DiagnosticsProperty('inputConfiguration', inputConfiguration))..add(DiagnosticsProperty('fallbackPack', fallbackPack))..add(DiagnosticsProperty('starred', starred))..add(DiagnosticsProperty('favoriteTemplates', favoriteTemplates))..add(DiagnosticsProperty('defaultTemplate', defaultTemplate))..add(DiagnosticsProperty('navigatorPosition', navigatorPosition))..add(DiagnosticsProperty('toolbarPosition', toolbarPosition))..add(DiagnosticsProperty('toolbarSize', toolbarSize))..add(DiagnosticsProperty('sortBy', sortBy))..add(DiagnosticsProperty('sortOrder', sortOrder))..add(DiagnosticsProperty('imageScale', imageScale))..add(DiagnosticsProperty('pdfQuality', pdfQuality))..add(DiagnosticsProperty('platformTheme', platformTheme))..add(DiagnosticsProperty('recentColors', recentColors))..add(DiagnosticsProperty('flags', flags))..add(DiagnosticsProperty('spreadPages', spreadPages))..add(DiagnosticsProperty('highContrast', highContrast))..add(DiagnosticsProperty('gridView', gridView))..add(DiagnosticsProperty('autosave', autosave))..add(DiagnosticsProperty('showSaveButton', showSaveButton))..add(DiagnosticsProperty('toolbarRows', toolbarRows))..add(DiagnosticsProperty('delayedAutosave', delayedAutosave))..add(DiagnosticsProperty('autosaveDelaySeconds', autosaveDelaySeconds))..add(DiagnosticsProperty('hideCursorWhileDrawing', hideCursorWhileDrawing))..add(DiagnosticsProperty('utilities', utilities))..add(DiagnosticsProperty('onStartup', onStartup))..add(DiagnosticsProperty('simpleToolbarVisibility', simpleToolbarVisibility))..add(DiagnosticsProperty('optionsPanelPosition', optionsPanelPosition))..add(DiagnosticsProperty('renderResolution', renderResolution))..add(DiagnosticsProperty('moveOnGesture', moveOnGesture))..add(DiagnosticsProperty('swamps', swamps))..add(DiagnosticsProperty('selectedPalette', selectedPalette))..add(DiagnosticsProperty('showVerboseLogs', showVerboseLogs)); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _ButterflySettings&&(identical(other.theme, theme) || other.theme == theme)&&(identical(other.density, density) || other.density == density)&&(identical(other.localeTag, localeTag) || other.localeTag == localeTag)&&(identical(other.documentPath, documentPath) || other.documentPath == documentPath)&&(identical(other.gestureSensitivity, gestureSensitivity) || other.gestureSensitivity == gestureSensitivity)&&(identical(other.touchSensitivity, touchSensitivity) || other.touchSensitivity == touchSensitivity)&&(identical(other.selectSensitivity, selectSensitivity) || other.selectSensitivity == selectSensitivity)&&(identical(other.scrollSensitivity, scrollSensitivity) || other.scrollSensitivity == scrollSensitivity)&&(identical(other.penOnlyInput, penOnlyInput) || other.penOnlyInput == penOnlyInput)&&(identical(other.inputGestures, inputGestures) || other.inputGestures == inputGestures)&&(identical(other.design, design) || other.design == design)&&(identical(other.bannerVisibility, bannerVisibility) || other.bannerVisibility == bannerVisibility)&&const DeepCollectionEquality().equals(other._history, _history)&&(identical(other.zoomEnabled, zoomEnabled) || other.zoomEnabled == zoomEnabled)&&(identical(other.lastVersion, lastVersion) || other.lastVersion == lastVersion)&&const DeepCollectionEquality().equals(other._connections, _connections)&&(identical(other.defaultRemote, defaultRemote) || other.defaultRemote == defaultRemote)&&(identical(other.nativeTitleBar, nativeTitleBar) || other.nativeTitleBar == nativeTitleBar)&&(identical(other.startInFullScreen, startInFullScreen) || other.startInFullScreen == startInFullScreen)&&(identical(other.navigationRail, navigationRail) || other.navigationRail == navigationRail)&&(identical(other.ignorePressure, ignorePressure) || other.ignorePressure == ignorePressure)&&(identical(other.syncMode, syncMode) || other.syncMode == syncMode)&&(identical(other.inputConfiguration, inputConfiguration) || other.inputConfiguration == inputConfiguration)&&(identical(other.fallbackPack, fallbackPack) || other.fallbackPack == fallbackPack)&&const DeepCollectionEquality().equals(other._starred, _starred)&&(identical(other.defaultTemplate, defaultTemplate) || other.defaultTemplate == defaultTemplate)&&(identical(other.navigatorPosition, navigatorPosition) || other.navigatorPosition == navigatorPosition)&&(identical(other.toolbarPosition, toolbarPosition) || other.toolbarPosition == toolbarPosition)&&(identical(other.toolbarSize, toolbarSize) || other.toolbarSize == toolbarSize)&&(identical(other.sortBy, sortBy) || other.sortBy == sortBy)&&(identical(other.sortOrder, sortOrder) || other.sortOrder == sortOrder)&&(identical(other.imageScale, imageScale) || other.imageScale == imageScale)&&(identical(other.pdfQuality, pdfQuality) || other.pdfQuality == pdfQuality)&&(identical(other.platformTheme, platformTheme) || other.platformTheme == platformTheme)&&const DeepCollectionEquality().equals(other._recentColors, _recentColors)&&const DeepCollectionEquality().equals(other._flags, _flags)&&(identical(other.spreadPages, spreadPages) || other.spreadPages == spreadPages)&&(identical(other.highContrast, highContrast) || other.highContrast == highContrast)&&(identical(other.gridView, gridView) || other.gridView == gridView)&&(identical(other.autosave, autosave) || other.autosave == autosave)&&(identical(other.showSaveButton, showSaveButton) || other.showSaveButton == showSaveButton)&&(identical(other.toolbarRows, toolbarRows) || other.toolbarRows == toolbarRows)&&(identical(other.delayedAutosave, delayedAutosave) || other.delayedAutosave == delayedAutosave)&&(identical(other.autosaveDelaySeconds, autosaveDelaySeconds) || other.autosaveDelaySeconds == autosaveDelaySeconds)&&(identical(other.hideCursorWhileDrawing, hideCursorWhileDrawing) || other.hideCursorWhileDrawing == hideCursorWhileDrawing)&&(identical(other.utilities, utilities) || other.utilities == utilities)&&(identical(other.onStartup, onStartup) || other.onStartup == onStartup)&&(identical(other.simpleToolbarVisibility, simpleToolbarVisibility) || other.simpleToolbarVisibility == simpleToolbarVisibility)&&(identical(other.optionsPanelPosition, optionsPanelPosition) || other.optionsPanelPosition == optionsPanelPosition)&&(identical(other.renderResolution, renderResolution) || other.renderResolution == renderResolution)&&(identical(other.moveOnGesture, moveOnGesture) || other.moveOnGesture == moveOnGesture)&&const DeepCollectionEquality().equals(other._swamps, _swamps)&&(identical(other.selectedPalette, selectedPalette) || other.selectedPalette == selectedPalette)&&(identical(other.showVerboseLogs, showVerboseLogs) || other.showVerboseLogs == showVerboseLogs)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ButterflySettings&&(identical(other.theme, theme) || other.theme == theme)&&(identical(other.density, density) || other.density == density)&&(identical(other.localeTag, localeTag) || other.localeTag == localeTag)&&(identical(other.documentPath, documentPath) || other.documentPath == documentPath)&&(identical(other.gestureSensitivity, gestureSensitivity) || other.gestureSensitivity == gestureSensitivity)&&(identical(other.touchSensitivity, touchSensitivity) || other.touchSensitivity == touchSensitivity)&&(identical(other.selectSensitivity, selectSensitivity) || other.selectSensitivity == selectSensitivity)&&(identical(other.scrollSensitivity, scrollSensitivity) || other.scrollSensitivity == scrollSensitivity)&&(identical(other.penOnlyInput, penOnlyInput) || other.penOnlyInput == penOnlyInput)&&(identical(other.inputGestures, inputGestures) || other.inputGestures == inputGestures)&&(identical(other.design, design) || other.design == design)&&(identical(other.bannerVisibility, bannerVisibility) || other.bannerVisibility == bannerVisibility)&&const DeepCollectionEquality().equals(other._history, _history)&&(identical(other.zoomEnabled, zoomEnabled) || other.zoomEnabled == zoomEnabled)&&(identical(other.lastVersion, lastVersion) || other.lastVersion == lastVersion)&&const DeepCollectionEquality().equals(other._connections, _connections)&&(identical(other.defaultRemote, defaultRemote) || other.defaultRemote == defaultRemote)&&(identical(other.nativeTitleBar, nativeTitleBar) || other.nativeTitleBar == nativeTitleBar)&&(identical(other.startInFullScreen, startInFullScreen) || other.startInFullScreen == startInFullScreen)&&(identical(other.navigationRail, navigationRail) || other.navigationRail == navigationRail)&&(identical(other.ignorePressure, ignorePressure) || other.ignorePressure == ignorePressure)&&(identical(other.syncMode, syncMode) || other.syncMode == syncMode)&&(identical(other.inputConfiguration, inputConfiguration) || other.inputConfiguration == inputConfiguration)&&(identical(other.fallbackPack, fallbackPack) || other.fallbackPack == fallbackPack)&&const DeepCollectionEquality().equals(other._starred, _starred)&&const DeepCollectionEquality().equals(other._favoriteTemplates, _favoriteTemplates)&&(identical(other.defaultTemplate, defaultTemplate) || other.defaultTemplate == defaultTemplate)&&(identical(other.navigatorPosition, navigatorPosition) || other.navigatorPosition == navigatorPosition)&&(identical(other.toolbarPosition, toolbarPosition) || other.toolbarPosition == toolbarPosition)&&(identical(other.toolbarSize, toolbarSize) || other.toolbarSize == toolbarSize)&&(identical(other.sortBy, sortBy) || other.sortBy == sortBy)&&(identical(other.sortOrder, sortOrder) || other.sortOrder == sortOrder)&&(identical(other.imageScale, imageScale) || other.imageScale == imageScale)&&(identical(other.pdfQuality, pdfQuality) || other.pdfQuality == pdfQuality)&&(identical(other.platformTheme, platformTheme) || other.platformTheme == platformTheme)&&const DeepCollectionEquality().equals(other._recentColors, _recentColors)&&const DeepCollectionEquality().equals(other._flags, _flags)&&(identical(other.spreadPages, spreadPages) || other.spreadPages == spreadPages)&&(identical(other.highContrast, highContrast) || other.highContrast == highContrast)&&(identical(other.gridView, gridView) || other.gridView == gridView)&&(identical(other.autosave, autosave) || other.autosave == autosave)&&(identical(other.showSaveButton, showSaveButton) || other.showSaveButton == showSaveButton)&&(identical(other.toolbarRows, toolbarRows) || other.toolbarRows == toolbarRows)&&(identical(other.delayedAutosave, delayedAutosave) || other.delayedAutosave == delayedAutosave)&&(identical(other.autosaveDelaySeconds, autosaveDelaySeconds) || other.autosaveDelaySeconds == autosaveDelaySeconds)&&(identical(other.hideCursorWhileDrawing, hideCursorWhileDrawing) || other.hideCursorWhileDrawing == hideCursorWhileDrawing)&&(identical(other.utilities, utilities) || other.utilities == utilities)&&(identical(other.onStartup, onStartup) || other.onStartup == onStartup)&&(identical(other.simpleToolbarVisibility, simpleToolbarVisibility) || other.simpleToolbarVisibility == simpleToolbarVisibility)&&(identical(other.optionsPanelPosition, optionsPanelPosition) || other.optionsPanelPosition == optionsPanelPosition)&&(identical(other.renderResolution, renderResolution) || other.renderResolution == renderResolution)&&(identical(other.moveOnGesture, moveOnGesture) || other.moveOnGesture == moveOnGesture)&&const DeepCollectionEquality().equals(other._swamps, _swamps)&&(identical(other.selectedPalette, selectedPalette) || other.selectedPalette == selectedPalette)&&(identical(other.showVerboseLogs, showVerboseLogs) || other.showVerboseLogs == showVerboseLogs)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hashAll([runtimeType,theme,density,localeTag,documentPath,gestureSensitivity,touchSensitivity,selectSensitivity,scrollSensitivity,penOnlyInput,inputGestures,design,bannerVisibility,const DeepCollectionEquality().hash(_history),zoomEnabled,lastVersion,const DeepCollectionEquality().hash(_connections),defaultRemote,nativeTitleBar,startInFullScreen,navigationRail,ignorePressure,syncMode,inputConfiguration,fallbackPack,const DeepCollectionEquality().hash(_starred),defaultTemplate,navigatorPosition,toolbarPosition,toolbarSize,sortBy,sortOrder,imageScale,pdfQuality,platformTheme,const DeepCollectionEquality().hash(_recentColors),const DeepCollectionEquality().hash(_flags),spreadPages,highContrast,gridView,autosave,showSaveButton,toolbarRows,delayedAutosave,autosaveDelaySeconds,hideCursorWhileDrawing,utilities,onStartup,simpleToolbarVisibility,optionsPanelPosition,renderResolution,moveOnGesture,const DeepCollectionEquality().hash(_swamps),selectedPalette,showVerboseLogs]); +int get hashCode => Object.hashAll([runtimeType,theme,density,localeTag,documentPath,gestureSensitivity,touchSensitivity,selectSensitivity,scrollSensitivity,penOnlyInput,inputGestures,design,bannerVisibility,const DeepCollectionEquality().hash(_history),zoomEnabled,lastVersion,const DeepCollectionEquality().hash(_connections),defaultRemote,nativeTitleBar,startInFullScreen,navigationRail,ignorePressure,syncMode,inputConfiguration,fallbackPack,const DeepCollectionEquality().hash(_starred),const DeepCollectionEquality().hash(_favoriteTemplates),defaultTemplate,navigatorPosition,toolbarPosition,toolbarSize,sortBy,sortOrder,imageScale,pdfQuality,platformTheme,const DeepCollectionEquality().hash(_recentColors),const DeepCollectionEquality().hash(_flags),spreadPages,highContrast,gridView,autosave,showSaveButton,toolbarRows,delayedAutosave,autosaveDelaySeconds,hideCursorWhileDrawing,utilities,onStartup,simpleToolbarVisibility,optionsPanelPosition,renderResolution,moveOnGesture,const DeepCollectionEquality().hash(_swamps),selectedPalette,showVerboseLogs]); @override String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { - return 'ButterflySettings(theme: $theme, density: $density, localeTag: $localeTag, documentPath: $documentPath, gestureSensitivity: $gestureSensitivity, touchSensitivity: $touchSensitivity, selectSensitivity: $selectSensitivity, scrollSensitivity: $scrollSensitivity, penOnlyInput: $penOnlyInput, inputGestures: $inputGestures, design: $design, bannerVisibility: $bannerVisibility, history: $history, zoomEnabled: $zoomEnabled, lastVersion: $lastVersion, connections: $connections, defaultRemote: $defaultRemote, nativeTitleBar: $nativeTitleBar, startInFullScreen: $startInFullScreen, navigationRail: $navigationRail, ignorePressure: $ignorePressure, syncMode: $syncMode, inputConfiguration: $inputConfiguration, fallbackPack: $fallbackPack, starred: $starred, defaultTemplate: $defaultTemplate, navigatorPosition: $navigatorPosition, toolbarPosition: $toolbarPosition, toolbarSize: $toolbarSize, sortBy: $sortBy, sortOrder: $sortOrder, imageScale: $imageScale, pdfQuality: $pdfQuality, platformTheme: $platformTheme, recentColors: $recentColors, flags: $flags, spreadPages: $spreadPages, highContrast: $highContrast, gridView: $gridView, autosave: $autosave, showSaveButton: $showSaveButton, toolbarRows: $toolbarRows, delayedAutosave: $delayedAutosave, autosaveDelaySeconds: $autosaveDelaySeconds, hideCursorWhileDrawing: $hideCursorWhileDrawing, utilities: $utilities, onStartup: $onStartup, simpleToolbarVisibility: $simpleToolbarVisibility, optionsPanelPosition: $optionsPanelPosition, renderResolution: $renderResolution, moveOnGesture: $moveOnGesture, swamps: $swamps, selectedPalette: $selectedPalette, showVerboseLogs: $showVerboseLogs)'; + return 'ButterflySettings(theme: $theme, density: $density, localeTag: $localeTag, documentPath: $documentPath, gestureSensitivity: $gestureSensitivity, touchSensitivity: $touchSensitivity, selectSensitivity: $selectSensitivity, scrollSensitivity: $scrollSensitivity, penOnlyInput: $penOnlyInput, inputGestures: $inputGestures, design: $design, bannerVisibility: $bannerVisibility, history: $history, zoomEnabled: $zoomEnabled, lastVersion: $lastVersion, connections: $connections, defaultRemote: $defaultRemote, nativeTitleBar: $nativeTitleBar, startInFullScreen: $startInFullScreen, navigationRail: $navigationRail, ignorePressure: $ignorePressure, syncMode: $syncMode, inputConfiguration: $inputConfiguration, fallbackPack: $fallbackPack, starred: $starred, favoriteTemplates: $favoriteTemplates, defaultTemplate: $defaultTemplate, navigatorPosition: $navigatorPosition, toolbarPosition: $toolbarPosition, toolbarSize: $toolbarSize, sortBy: $sortBy, sortOrder: $sortOrder, imageScale: $imageScale, pdfQuality: $pdfQuality, platformTheme: $platformTheme, recentColors: $recentColors, flags: $flags, spreadPages: $spreadPages, highContrast: $highContrast, gridView: $gridView, autosave: $autosave, showSaveButton: $showSaveButton, toolbarRows: $toolbarRows, delayedAutosave: $delayedAutosave, autosaveDelaySeconds: $autosaveDelaySeconds, hideCursorWhileDrawing: $hideCursorWhileDrawing, utilities: $utilities, onStartup: $onStartup, simpleToolbarVisibility: $simpleToolbarVisibility, optionsPanelPosition: $optionsPanelPosition, renderResolution: $renderResolution, moveOnGesture: $moveOnGesture, swamps: $swamps, selectedPalette: $selectedPalette, showVerboseLogs: $showVerboseLogs)'; } @@ -469,7 +626,7 @@ abstract mixin class _$ButterflySettingsCopyWith<$Res> implements $ButterflySett factory _$ButterflySettingsCopyWith(_ButterflySettings value, $Res Function(_ButterflySettings) _then) = __$ButterflySettingsCopyWithImpl; @override @useResult $Res call({ - ThemeMode theme, ThemeDensity density, String localeTag, String documentPath, double gestureSensitivity, double touchSensitivity, double selectSensitivity, double scrollSensitivity, bool penOnlyInput, bool inputGestures, String design, BannerVisibility bannerVisibility,@JsonKey(includeFromJson: false, includeToJson: false) List history, bool zoomEnabled, String? lastVersion,@JsonKey(includeFromJson: false, includeToJson: false) List connections, String defaultRemote, bool nativeTitleBar, bool startInFullScreen, bool navigationRail, IgnorePressure ignorePressure, SyncMode syncMode, InputConfiguration inputConfiguration, String fallbackPack, List starred, String defaultTemplate, NavigatorPosition navigatorPosition, ToolbarPosition toolbarPosition, ToolbarSize toolbarSize, SortBy sortBy, SortOrder sortOrder, double imageScale, double pdfQuality, PlatformTheme platformTheme,@SRGBConverter() List recentColors, List flags, bool spreadPages, bool highContrast, bool gridView, bool autosave, bool showSaveButton, int toolbarRows, bool delayedAutosave, int autosaveDelaySeconds, bool hideCursorWhileDrawing, UtilitiesState utilities, StartupBehavior onStartup, SimpleToolbarVisibility simpleToolbarVisibility, OptionsPanelPosition optionsPanelPosition, RenderResolution renderResolution, bool moveOnGesture, List swamps, PackAssetLocation? selectedPalette, bool showVerboseLogs + ThemeMode theme, ThemeDensity density, String localeTag, String documentPath, double gestureSensitivity, double touchSensitivity, double selectSensitivity, double scrollSensitivity, bool penOnlyInput, bool inputGestures, String design, BannerVisibility bannerVisibility,@JsonKey(includeFromJson: false, includeToJson: false) List history, bool zoomEnabled, String? lastVersion,@JsonKey(includeFromJson: false, includeToJson: false) List connections, String defaultRemote, bool nativeTitleBar, bool startInFullScreen, bool navigationRail, IgnorePressure ignorePressure, SyncMode syncMode, InputConfiguration inputConfiguration, String fallbackPack, List starred,@JsonKey(includeFromJson: false, includeToJson: false) List favoriteTemplates, String defaultTemplate, NavigatorPosition navigatorPosition, ToolbarPosition toolbarPosition, ToolbarSize toolbarSize, SortBy sortBy, SortOrder sortOrder, double imageScale, double pdfQuality, PlatformTheme platformTheme,@SRGBConverter() List recentColors, List flags, bool spreadPages, bool highContrast, bool gridView, bool autosave, bool showSaveButton, int toolbarRows, bool delayedAutosave, int autosaveDelaySeconds, bool hideCursorWhileDrawing, UtilitiesState utilities, StartupBehavior onStartup, SimpleToolbarVisibility simpleToolbarVisibility, OptionsPanelPosition optionsPanelPosition, RenderResolution renderResolution, bool moveOnGesture, List swamps, PackAssetLocation? selectedPalette, bool showVerboseLogs }); @@ -486,7 +643,7 @@ class __$ButterflySettingsCopyWithImpl<$Res> /// Create a copy of ButterflySettings /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? theme = null,Object? density = null,Object? localeTag = null,Object? documentPath = null,Object? gestureSensitivity = null,Object? touchSensitivity = null,Object? selectSensitivity = null,Object? scrollSensitivity = null,Object? penOnlyInput = null,Object? inputGestures = null,Object? design = null,Object? bannerVisibility = null,Object? history = null,Object? zoomEnabled = null,Object? lastVersion = freezed,Object? connections = null,Object? defaultRemote = null,Object? nativeTitleBar = null,Object? startInFullScreen = null,Object? navigationRail = null,Object? ignorePressure = null,Object? syncMode = null,Object? inputConfiguration = null,Object? fallbackPack = null,Object? starred = null,Object? defaultTemplate = null,Object? navigatorPosition = null,Object? toolbarPosition = null,Object? toolbarSize = null,Object? sortBy = null,Object? sortOrder = null,Object? imageScale = null,Object? pdfQuality = null,Object? platformTheme = null,Object? recentColors = null,Object? flags = null,Object? spreadPages = null,Object? highContrast = null,Object? gridView = null,Object? autosave = null,Object? showSaveButton = null,Object? toolbarRows = null,Object? delayedAutosave = null,Object? autosaveDelaySeconds = null,Object? hideCursorWhileDrawing = null,Object? utilities = null,Object? onStartup = null,Object? simpleToolbarVisibility = null,Object? optionsPanelPosition = null,Object? renderResolution = null,Object? moveOnGesture = null,Object? swamps = null,Object? selectedPalette = freezed,Object? showVerboseLogs = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? theme = null,Object? density = null,Object? localeTag = null,Object? documentPath = null,Object? gestureSensitivity = null,Object? touchSensitivity = null,Object? selectSensitivity = null,Object? scrollSensitivity = null,Object? penOnlyInput = null,Object? inputGestures = null,Object? design = null,Object? bannerVisibility = null,Object? history = null,Object? zoomEnabled = null,Object? lastVersion = freezed,Object? connections = null,Object? defaultRemote = null,Object? nativeTitleBar = null,Object? startInFullScreen = null,Object? navigationRail = null,Object? ignorePressure = null,Object? syncMode = null,Object? inputConfiguration = null,Object? fallbackPack = null,Object? starred = null,Object? favoriteTemplates = null,Object? defaultTemplate = null,Object? navigatorPosition = null,Object? toolbarPosition = null,Object? toolbarSize = null,Object? sortBy = null,Object? sortOrder = null,Object? imageScale = null,Object? pdfQuality = null,Object? platformTheme = null,Object? recentColors = null,Object? flags = null,Object? spreadPages = null,Object? highContrast = null,Object? gridView = null,Object? autosave = null,Object? showSaveButton = null,Object? toolbarRows = null,Object? delayedAutosave = null,Object? autosaveDelaySeconds = null,Object? hideCursorWhileDrawing = null,Object? utilities = null,Object? onStartup = null,Object? simpleToolbarVisibility = null,Object? optionsPanelPosition = null,Object? renderResolution = null,Object? moveOnGesture = null,Object? swamps = null,Object? selectedPalette = freezed,Object? showVerboseLogs = null,}) { return _then(_ButterflySettings( theme: null == theme ? _self.theme : theme // ignore: cast_nullable_to_non_nullable as ThemeMode,density: null == density ? _self.density : density // ignore: cast_nullable_to_non_nullable @@ -513,7 +670,8 @@ as IgnorePressure,syncMode: null == syncMode ? _self.syncMode : syncMode // igno as SyncMode,inputConfiguration: null == inputConfiguration ? _self.inputConfiguration : inputConfiguration // ignore: cast_nullable_to_non_nullable as InputConfiguration,fallbackPack: null == fallbackPack ? _self.fallbackPack : fallbackPack // ignore: cast_nullable_to_non_nullable as String,starred: null == starred ? _self._starred : starred // ignore: cast_nullable_to_non_nullable -as List,defaultTemplate: null == defaultTemplate ? _self.defaultTemplate : defaultTemplate // ignore: cast_nullable_to_non_nullable +as List,favoriteTemplates: null == favoriteTemplates ? _self._favoriteTemplates : favoriteTemplates // ignore: cast_nullable_to_non_nullable +as List,defaultTemplate: null == defaultTemplate ? _self.defaultTemplate : defaultTemplate // ignore: cast_nullable_to_non_nullable as String,navigatorPosition: null == navigatorPosition ? _self.navigatorPosition : navigatorPosition // ignore: cast_nullable_to_non_nullable as NavigatorPosition,toolbarPosition: null == toolbarPosition ? _self.toolbarPosition : toolbarPosition // ignore: cast_nullable_to_non_nullable as ToolbarPosition,toolbarSize: null == toolbarSize ? _self.toolbarSize : toolbarSize // ignore: cast_nullable_to_non_nullable diff --git a/app/lib/cubits/settings.g.dart b/app/lib/cubits/settings.g.dart index 1b702cd7d364..28bdd62456ef 100644 --- a/app/lib/cubits/settings.g.dart +++ b/app/lib/cubits/settings.g.dart @@ -6,6 +6,14 @@ part of 'settings.dart'; // JsonSerializableGenerator // ************************************************************************** +_FavoriteLocation _$FavoriteLocationFromJson(Map json) => _FavoriteLocation( + remote: json['remote'] as String?, + path: json['path'] as String, +); + +Map _$FavoriteLocationToJson(_FavoriteLocation instance) => + {'remote': instance.remote, 'path': instance.path}; + _InputConfiguration _$InputConfigurationFromJson(Map json) => _InputConfiguration( leftMouse: json['leftMouse'] == null diff --git a/app/lib/dialogs/background/dialog.dart b/app/lib/dialogs/background/dialog.dart index 4828fb6ec341..db9f29df8407 100644 --- a/app/lib/dialogs/background/dialog.dart +++ b/app/lib/dialogs/background/dialog.dart @@ -92,7 +92,10 @@ class _BackgroundDialogState extends State ), }; return ResponsiveAlertDialog( - constraints: const BoxConstraints(maxWidth: 600, maxHeight: 800), + constraints: const BoxConstraints( + maxWidth: LeapBreakpoints.medium, + maxHeight: 800, + ), title: Text(AppLocalizations.of(context).background), leading: const PhosphorIcon(PhosphorIconsLight.image), headerActions: [ diff --git a/app/lib/dialogs/background/general.dart b/app/lib/dialogs/background/general.dart index 65f4a90a9916..fdb4b99c8d4c 100644 --- a/app/lib/dialogs/background/general.dart +++ b/app/lib/dialogs/background/general.dart @@ -1,108 +1,208 @@ part of 'dialog.dart'; -class _GeneralBackgroundPropertiesView extends StatelessWidget { +class _GeneralBackgroundPropertiesView extends StatefulWidget { final ValueChanged onChanged; const _GeneralBackgroundPropertiesView({required this.onChanged}); + @override + State<_GeneralBackgroundPropertiesView> createState() => + _GeneralBackgroundPropertiesViewState(); +} + +class _GeneralBackgroundPropertiesViewState + extends State<_GeneralBackgroundPropertiesView> { + bool _showDark = false; + bool _initialized = false; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (!_initialized) { + _showDark = Theme.of(context).brightness == Brightness.dark; + _initialized = true; + } + } + @override Widget build(BuildContext context) { - return ListView( + final patterns = PatternTemplate.values.where((element) { + if (_showDark) { + return element.background == PatternBackground.dark; + } else { + return element.background != PatternBackground.dark; + } + }).toList(); + + return Column( children: [ - ...[ - PatternTemplate.values.where((element) => !element.dark), - PatternTemplate.values.where((element) => element.dark), - ].map( - (e) => Wrap( - alignment: WrapAlignment.center, - children: e.map((template) { - var created = template.create(); - return BoxTile( - title: Text( - template.getLocalizedName(context), - textAlign: TextAlign.center, + Padding( + padding: const EdgeInsets.all(8.0), + child: SegmentedButton( + segments: [ + ButtonSegment( + value: false, + label: Text(AppLocalizations.of(context).lightTheme), + icon: const Icon(PhosphorIconsLight.sun), + ), + ButtonSegment( + value: true, + label: Text(AppLocalizations.of(context).darkTheme), + icon: const Icon(PhosphorIconsLight.moon), + ), + ], + selected: {_showDark}, + onSelectionChanged: (newSelection) { + setState(() { + _showDark = newSelection.first; + }); + }, + ), + ), + Expanded( + child: GridView( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 200, + childAspectRatio: 1.1, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + ), + children: [ + ...patterns.map((template) { + return _BackgroundCard( + title: template.getLocalizedName(context), + image: AssetImage(template.asset), + onTap: () { + var created = template.create(); + widget.onChanged(Background.texture(texture: created)); + }, + ); + }), + _BackgroundCard( + title: AppLocalizations.of(context).image, + icon: const Icon(PhosphorIconsLight.image, size: 48), + onTap: () async { + final state = context.read().state; + if (state is! DocumentLoaded) return; + final (result, _) = await importFile(context, [ + AssetFileType.image, + ]); + if (result == null) return; + final dataPath = Uri.dataFromBytes(result).toString(); + final codec = await ui.instantiateImageCodec(result); + try { + final frame = await codec.getNextFrame(); + final image = frame.image; + final width = image.width.toDouble(), + height = image.height.toDouble(); + image.dispose(); + widget.onChanged( + ImageBackground( + source: dataPath, + width: width, + height: height, + ), + ); + } finally { + codec.dispose(); + } + }, + ), + _BackgroundCard( + title: AppLocalizations.of(context).svg, + icon: const Icon( + PhosphorIconsLight.fileSvg, + textDirection: TextDirection.ltr, + size: 48, ), - icon: Image.asset(template.asset, width: 64), - onTap: () { - onChanged(Background.texture(texture: created)); + onTap: () async { + final state = context.read().state; + if (state is! DocumentLoaded) return; + final (result, _) = await importFile(context, [ + AssetFileType.svg, + ]); + if (result == null) return; + final dataPath = Uri.dataFromBytes(result).toString(); + final contentString = String.fromCharCodes(result); + var info = await vg.loadPicture( + SvgStringLoader(contentString), + null, + ); + try { + final size = info.size; + var height = size.height, width = size.width; + if (!height.isFinite) height = 0; + if (!width.isFinite) width = 0; + widget.onChanged( + SvgBackground( + source: dataPath, + width: width, + height: height, + ), + ); + } finally { + info.picture.dispose(); + } }, - ); - }).toList(), + ), + ], ), ), - const SizedBox(height: 8), - Wrap( - alignment: WrapAlignment.center, + ], + ); + } +} + +class _BackgroundCard extends StatelessWidget { + final String title; + final ImageProvider? image; + final Widget? icon; + final VoidCallback onTap; + + const _BackgroundCard({ + required this.title, + this.image, + this.icon, + required this.onTap, + }) : assert(image != null || icon != null); + + @override + Widget build(BuildContext context) { + return Card( + clipBehavior: Clip.antiAlias, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: BorderSide.none, + ), + child: InkWell( + onTap: onTap, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - BoxTile( - title: Text(AppLocalizations.of(context).image), - icon: const Icon(PhosphorIconsLight.image), - onTap: () async { - final state = context.read().state; - if (state is! DocumentLoaded) return; - final (result, _) = await importFile(context, [ - AssetFileType.image, - ]); - if (result == null) return; - final dataPath = Uri.dataFromBytes(result).toString(); - final codec = await ui.instantiateImageCodec(result); - try { - final frame = await codec.getNextFrame(); - final image = frame.image; - final width = image.width.toDouble(), - height = image.height.toDouble(); - image.dispose(); - onChanged( - ImageBackground( - source: dataPath, - width: width, - height: height, - ), - ); - } finally { - codec.dispose(); - } - }, + AspectRatio( + aspectRatio: kThumbnailRatio, + child: image != null + ? Image(image: image!, fit: BoxFit.cover) + : Center(child: icon), ), - BoxTile( - title: Text(AppLocalizations.of(context).svg), - icon: const Icon( - PhosphorIconsLight.fileSvg, - textDirection: TextDirection.ltr, + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + title, + style: Theme.of(context).textTheme.titleMedium, + maxLines: 2, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + ), + ], ), - onTap: () async { - final state = context.read().state; - if (state is! DocumentLoaded) return; - final (result, _) = await importFile(context, [ - AssetFileType.svg, - ]); - if (result == null) return; - final dataPath = Uri.dataFromBytes(result).toString(); - final contentString = String.fromCharCodes(result); - var info = await vg.loadPicture( - SvgStringLoader(contentString), - null, - ); - try { - final size = info.size; - var height = size.height, width = size.width; - if (!height.isFinite) height = 0; - if (!width.isFinite) width = 0; - onChanged( - SvgBackground( - source: dataPath, - width: width, - height: height, - ), - ); - } finally { - info.picture.dispose(); - } - }, ), ], ), - ], + ), ); } } diff --git a/app/lib/dialogs/template.dart b/app/lib/dialogs/template.dart index b013ffccd16f..388153091aac 100644 --- a/app/lib/dialogs/template.dart +++ b/app/lib/dialogs/template.dart @@ -3,10 +3,12 @@ import 'package:butterfly/actions/new.dart'; import 'package:butterfly/api/file_system.dart'; import 'package:butterfly/api/save.dart'; import 'package:butterfly/cubits/settings.dart'; +import 'package:butterfly/models/defaults.dart'; import 'package:butterfly/visualizer/tool.dart'; import 'package:butterfly/widgets/connection_button.dart'; import 'package:butterfly/widgets/option_button.dart'; import 'package:butterfly_api/butterfly_api.dart'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:butterfly/src/generated/i18n/app_localizations.dart'; @@ -46,16 +48,29 @@ class TemplateDialog extends StatefulWidget { class _TemplateDialogState extends State { late TemplateFileSystem _templateSystem; late final ButterflyFileSystem _fileSystem; - Future>>? _templatesFuture; + Future> _combinedFuture = Future.value([ + >[], + [], + ]); final TextEditingController _searchController = TextEditingController(); final List _selectedTemplates = []; + NoteData? _clickedTemplate; + PatternBackground _selectedBackground = PatternBackground.light; @override void initState() { super.initState(); _fileSystem = context.read(); _templateSystem = _fileSystem.buildDefaultTemplateSystem(); - WidgetsBinding.instance.addPostFrameCallback((_) => load()); + WidgetsBinding.instance.addPostFrameCallback((_) { + final brightness = Theme.of(context).brightness; + setState(() { + _selectedBackground = brightness == Brightness.light + ? PatternBackground.light + : PatternBackground.dark; + }); + load(); + }); } @override @@ -66,26 +81,31 @@ class _TemplateDialogState extends State { void load() { setState(() { - _templatesFuture = _templateSystem.initialize().then((value) async { - var templates = (await _templateSystem.getFiles()).toList(); - templates = templates - .where( - (element) => - element.data!.name?.toLowerCase().contains( - _searchController.text.toLowerCase(), - ) ?? - true, - ) - .toList(); - return templates; + final userFuture = _templateSystem.initialize().then((value) async { + return (await _templateSystem.getFiles()).toList(); }); + Future> coreFuture = Future.value([]); + if (context.mounted) { + coreFuture = DocumentDefaults.getCoreTemplates( + context, + background: _selectedBackground, + ); + } + _combinedFuture = Future.wait([userFuture, coreFuture]); }); } @override Widget build(BuildContext context) { + final width = MediaQuery.widthOf(context); + final isMobile = width < LeapBreakpoints.medium; + final detailsTemplate = _clickedTemplate; + final bloc = widget.bloc; return ResponsiveAlertDialog( - constraints: const BoxConstraints(maxWidth: 600, maxHeight: 800), + constraints: const BoxConstraints( + maxWidth: LeapBreakpoints.expanded, + maxHeight: 800, + ), title: Text(AppLocalizations.of(context).templates), leading: IconButton.outlined( icon: const PhosphorIcon(PhosphorIconsLight.x), @@ -125,99 +145,288 @@ class _TemplateDialogState extends State { tooltip: AppLocalizations.of(context).export, ), ), - IconButton( - icon: const PhosphorIcon(PhosphorIconsLight.clockCounterClockwise), - tooltip: AppLocalizations.of(context).defaultTemplate, - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text(AppLocalizations.of(context).defaultTemplate), - content: Text(AppLocalizations.of(context).reallyReset), - actions: [ - TextButton( - child: Text( - MaterialLocalizations.of(context).cancelButtonLabel, - ), - onPressed: () => Navigator.of(context).pop(), - ), - ElevatedButton( - child: Text( - MaterialLocalizations.of(context).okButtonLabel, - ), - onPressed: () async { - for (final template in await _templateSystem.getKeys()) { - _templateSystem.deleteFile(template); - } - if (context.mounted) { - await _templateSystem.initialize(force: true); - } - if (context.mounted) { - Navigator.of(context).pop(); - load(); - } - }, - ), - ], - ), - ); - }, - ), + if (bloc != null) + IconButton( + icon: Icon(PhosphorIconsLight.plus), + onPressed: () => _showCreateDialog(bloc), + tooltip: LeapLocalizations.of(context).create, + ), ], - content: FutureBuilder>>( - future: _templatesFuture, - builder: _buildBody, + content: Row( + children: [ + Expanded( + flex: 2, + child: FutureBuilder>( + future: _combinedFuture, + builder: (context, snapshot) { + if (snapshot.hasError) { + return Text(snapshot.error.toString()); + } + if (!snapshot.hasData) { + return const Center(child: CircularProgressIndicator()); + } + final userTemplates = + snapshot.data![0] as List>; + final coreTemplates = snapshot.data![1] as List; + return BlocBuilder( + buildWhen: (previous, current) => + previous.favoriteTemplates != current.favoriteTemplates, + builder: (context, state) { + return _buildBody( + context, + userTemplates, + coreTemplates, + isMobile, + state.favoriteTemplates, + ); + }, + ); + }, + ), + ), + if (!isMobile) + Expanded( + child: detailsTemplate == null + ? Card( + child: Center( + child: Text(AppLocalizations.of(context).noElements), + ), + ) + : _TemplateDetailsView( + template: detailsTemplate, + onOpen: () { + openNewDocument( + context, + widget.bloc != null, + detailsTemplate, + _templateSystem.storage?.identifier, + ); + }, + ), + ), + ], ), ); } - Widget _buildBody( + Widget _buildItem( BuildContext context, - AsyncSnapshot>> snapshot, + MapEntry>> entry, + int index, + bool isMobile, + List favoriteTemplates, ) { - if (snapshot.hasError) { - return Text(snapshot.error.toString()); + final template = entry.value[index]; + final isCore = entry.key == null; + final location = FavoriteLocation( + remote: isCore ? null : template.remote, + path: template.pathWithoutLeadingSlash, + ); + final starred = favoriteTemplates.contains(location); + + if (isMobile) { + return _TemplateItem( + file: template, + fileSystem: _templateSystem, + bloc: widget.bloc, + key: ValueKey(template.path), + selected: _selectedTemplates.contains(template.path), + isCore: isCore, + starred: starred, + location: location, + onSelected: () { + setState(() { + _selectedTemplates.add(template.path); + }); + }, + onUnselected: () { + setState(() { + _selectedTemplates.remove(template.path); + }); + }, + onChanged: () { + load(); + }, + onTap: () { + showLeapBottomSheet( + context: context, + titleBuilder: (context) => Text(template.data!.name ?? ''), + leadingBuilder: (context) { + final thumbnail = template.data!.getThumbnail(); + const fallback = PhosphorIcon( + PhosphorIconsLight.file, + textDirection: TextDirection.ltr, + size: 48, + ); + return thumbnail != null + ? AspectRatio( + aspectRatio: kThumbnailRatio, + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.memory( + thumbnail, + fit: BoxFit.cover, + cacheHeight: kThumbnailHeight, + cacheWidth: kThumbnailWidth, + ), + ), + ) + : fallback; + }, + leadingWidth: 128, + childrenBuilder: (context) => [ + const SizedBox(height: 8), + _TemplateDetailsView( + template: template.data!, + scrollable: false, + onOpen: () { + Navigator.of(context).pop(); + openNewDocument( + context, + widget.bloc != null, + template.data!, + _templateSystem.storage?.identifier, + ); + }, + ), + ], + ); + }, + ); + } else { + return _TemplateCard( + file: template, + fileSystem: _templateSystem, + bloc: widget.bloc, + key: ValueKey(template.path), + selected: _selectedTemplates.contains(template.path), + isActive: _clickedTemplate == template.data, + isCore: entry.key == '', + starred: starred, + location: location, + onSelected: () { + setState(() { + _selectedTemplates.add(template.path); + }); + }, + onUnselected: () { + setState(() { + _selectedTemplates.remove(template.path); + }); + }, + onChanged: () { + load(); + }, + onTap: () { + setState(() { + _clickedTemplate = template.data; + }); + }, + ); } - if (!snapshot.hasData) { - return const Center(child: CircularProgressIndicator()); + } + + Widget _buildBody( + BuildContext context, + List> userTemplates, + List coreTemplates, + bool isMobile, + List favoriteTemplates, + ) { + final filteredUserTemplates = userTemplates + .where( + (element) => + element.data!.name?.toLowerCase().contains( + _searchController.text.toLowerCase(), + ) ?? + true, + ) + .toList(); + final filteredCoreTemplates = coreTemplates + .where( + (element) => + element.name?.toLowerCase().contains( + _searchController.text.toLowerCase(), + ) ?? + true, + ) + .toList(); + final everythingSelected = + _selectedTemplates.length == filteredUserTemplates.length; + final groupedTemplates = filteredUserTemplates + .groupListsBy((element) { + final parent = element.parent; + if (parent == '/' || parent == '.') return '/'; + return parent; + }) + .entries + .toList(); + groupedTemplates.sort((a, b) { + final aKey = a.key; + final bKey = b.key; + if (aKey == null) return -1; + if (bKey == null) return 1; + return aKey.compareTo(bKey); + }); + if (filteredCoreTemplates.isNotEmpty) { + groupedTemplates.add( + MapEntry( + null, + filteredCoreTemplates + .map( + (e) => + FileSystemFile(AssetLocation.local(e.name ?? ''), data: e), + ) + .toList(), + ), + ); } - final templates = snapshot.data!; - final everythingSelected = _selectedTemplates.length == templates.length; return Stack( alignment: Alignment.topCenter, children: [ - ListView.builder( - itemCount: templates.length, - padding: const EdgeInsets.only( - top: 64, - bottom: 32, - left: 8, - right: 8, - ), - itemBuilder: (context, index) { - final template = templates[index]; - final path = template.path; - return _TemplateItem( - file: template, - fileSystem: _templateSystem, - bloc: widget.bloc, - key: ValueKey(path), - selected: _selectedTemplates.contains(path), - onSelected: () { - setState(() { - _selectedTemplates.add(path); - }); - }, - onUnselected: () { - setState(() { - _selectedTemplates.remove(path); - }); - }, - onChanged: () { - load(); - }, - ); - }, + ListView( + children: [ + const SizedBox(height: 64), + for (final entry in groupedTemplates) ...[ + if (entry.key == null || entry.key != '/') + _buildHeader(context, entry.key), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: isMobile + ? ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => _buildItem( + context, + entry, + index, + isMobile, + favoriteTemplates, + ), + itemCount: entry.value.length, + ) + : GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: + const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 200, + childAspectRatio: 1.1, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + ), + itemBuilder: (context, index) => _buildItem( + context, + entry, + index, + isMobile, + favoriteTemplates, + ), + itemCount: entry.value.length, + ), + ), + ], + ], ), Padding( padding: const EdgeInsets.symmetric(horizontal: 12), @@ -226,8 +435,8 @@ class _TemplateDialogState extends State { constraints: const BoxConstraints(maxWidth: 500, minHeight: 50), controller: _searchController, hintText: AppLocalizations.of(context).search, - onChanged: (value) async { - load(); + onChanged: (value) { + setState(() {}); }, ), ), @@ -240,15 +449,7 @@ class _TemplateDialogState extends State { AnimatedSwitcher( duration: const Duration(milliseconds: 100), child: _selectedTemplates.isEmpty - ? widget.bloc == null - ? SizedBox() - : FloatingActionButton.extended( - onPressed: () => _showCreateDialog(widget.bloc!), - label: Text(LeapLocalizations.of(context).create), - icon: const PhosphorIcon( - PhosphorIconsLight.floppyDisk, - ), - ) + ? SizedBox() : Card( child: Padding( padding: const EdgeInsets.all(8), @@ -266,7 +467,7 @@ class _TemplateDialogState extends State { ).invertSelection, onPressed: () { setState(() { - final inverted = templates + final inverted = filteredUserTemplates .map((e) => e.path) .toSet() .difference( @@ -293,7 +494,9 @@ class _TemplateDialogState extends State { _selectedTemplates.clear(); if (!everythingSelected) { _selectedTemplates.addAll( - templates.map((e) => e.path), + filteredUserTemplates.map( + (e) => e.path, + ), ); } }); @@ -315,7 +518,7 @@ class _TemplateDialogState extends State { _overrideTools( _templateSystem, widget.bloc!, - templates + filteredUserTemplates .where( (element) => _selectedTemplates @@ -409,6 +612,42 @@ class _TemplateDialogState extends State { ); } + Widget _buildHeader(BuildContext context, String? title) { + return Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), + child: Row( + children: [ + Expanded( + child: Text( + title ?? 'Core', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + + IconButton( + icon: Icon( + _selectedBackground == PatternBackground.light + ? PhosphorIconsLight.sun + : PhosphorIconsLight.moon, + ), + onPressed: () { + setState(() { + _selectedBackground = + _selectedBackground == PatternBackground.light + ? PatternBackground.dark + : PatternBackground.light; + load(); + }); + }, + tooltip: AppLocalizations.of(context).background, + ), + ], + ), + ); + } + Future _showCreateDialog(DocumentBloc bloc) { final state = bloc.state; var initialName = ''; @@ -499,12 +738,153 @@ class _TemplateDialogState extends State { } } +class _TemplateDetailsView extends StatelessWidget { + final NoteData template; + final VoidCallback? onOpen; + final bool scrollable; + + const _TemplateDetailsView({ + required this.template, + this.onOpen, + this.scrollable = true, + }); + + @override + Widget build(BuildContext context) { + final metadata = + template.getMetadata() ?? FileMetadata(type: NoteFileType.template); + final info = template.getInfo(); + final thumbnail = template.getThumbnail(); + const fallback = PhosphorIcon( + PhosphorIconsLight.file, + textDirection: TextDirection.ltr, + size: 48, + ); + final thumbnailWidget = thumbnail != null + ? AspectRatio( + aspectRatio: kThumbnailRatio, + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.memory( + thumbnail, + fit: BoxFit.cover, + cacheHeight: kThumbnailHeight, + cacheWidth: kThumbnailWidth, + ), + ), + ) + : fallback; + + final details = [ + if (metadata.description.isNotEmpty) + ListTile( + title: Text(AppLocalizations.of(context).description), + subtitle: Text(metadata.description), + ), + if (metadata.directory.isNotEmpty) + ListTile( + title: Text(AppLocalizations.of(context).directory), + subtitle: Text(metadata.directory), + ), + ListTile( + title: Text(AppLocalizations.of(context).tools), + subtitle: Wrap( + alignment: WrapAlignment.center, + children: [ + for (final tool in info?.tools ?? []) + SizedBox.square( + dimension: 64, + child: OptionButton( + icon: Icon(tool.icon(PhosphorIconsStyle.light)), + tooltip: tool.name, + ), + ), + ], + ), + ), + ]; + + if (!scrollable) { + return Column( + children: [ + Card.filled( + child: Padding( + padding: const EdgeInsets.all(2), + child: Column(children: details), + ), + ), + if (onOpen != null) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: onOpen, + child: Text(LeapLocalizations.of(context).create), + ), + ), + ), + ], + ); + } + + return Padding( + padding: const EdgeInsets.all(8.0), + child: Card( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 300), + child: thumbnailWidget, + ), + ), + const SizedBox(height: 16), + Text( + metadata.name, + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 8), + ...details, + ], + ), + ), + ), + if (onOpen != null) + Padding( + padding: const EdgeInsets.all(16.0), + child: SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: onOpen, + child: Text(LeapLocalizations.of(context).create), + ), + ), + ), + ], + ), + ), + ); + } +} + class _TemplateItem extends StatelessWidget { final FileSystemFile file; final TemplateFileSystem fileSystem; final DocumentBloc? bloc; final VoidCallback onChanged, onSelected, onUnselected; final bool selected; + final VoidCallback? onTap; + final bool isCore; + final bool starred; + final FavoriteLocation location; const _TemplateItem({ required this.file, @@ -515,17 +895,18 @@ class _TemplateItem extends StatelessWidget { super.key, required this.onSelected, required this.onUnselected, + this.onTap, + this.isCore = false, + required this.starred, + required this.location, }); @override Widget build(BuildContext context) { final settingsCubit = context.read(); - final settings = settingsCubit.state; final template = file.data!; final path = file.pathWithoutLeadingSlash; - final isDefault = settings.defaultTemplate == path; final metadata = template.getMetadata(); - final info = template.getInfo(); if (metadata == null) { return const SizedBox(); } @@ -543,28 +924,40 @@ class _TemplateItem extends StatelessWidget { child: Image.memory( thumbnail, fit: BoxFit.cover, - cacheHeight: kThumbnailWidth, - cacheWidth: kThumbnailHeight, + cacheHeight: kThumbnailHeight, + cacheWidth: kThumbnailWidth, ), ), ) : fallback; return EditableListTile( + showEditIcon: !isCore, initialValue: metadata.name, subtitle: Text(metadata.description), leading: SizedBox( - width: 112, + width: 150, child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Checkbox( - value: selected, - onChanged: (value) { - if (value == true) { - onSelected(); - } else { - onUnselected(); - } + if (!isCore) + Checkbox( + value: selected, + onChanged: (value) { + if (value == true) { + onSelected(); + } else { + onUnselected(); + } + }, + ) + else + const SizedBox(width: 48), + IconButton( + icon: Icon(PhosphorIconsLight.star), + selectedIcon: Icon(PhosphorIconsFill.star), + isSelected: starred, + onPressed: () { + settingsCubit.toggleFavoriteTemplate(location); }, ), const SizedBox(width: 2), @@ -572,153 +965,294 @@ class _TemplateItem extends StatelessWidget { ], ), ), - onSaved: (value) async { - if (value == path) return; - await fileSystem.deleteFile(path); - await fileSystem.createFile( - value, - template.setMetadata(metadata.copyWith(name: value)), - ); - onChanged(); - }, - actions: [ - CheckboxMenuButton( - value: isDefault, - child: Text(AppLocalizations.of(context).defaultTemplate), - onChanged: (value) async { - settingsCubit.changeDefaultTemplate(path); - onChanged(); - }, - ), - if (bloc != null) - MenuItemButton( - leadingIcon: const PhosphorIcon(PhosphorIconsLight.toolbox), - child: Text(AppLocalizations.of(context).overrideTools), - onPressed: () async { - _overrideTools(fileSystem, bloc!, [file]); - }, - ), - MenuItemButton( - leadingIcon: const PhosphorIcon(PhosphorIconsLight.copy), - child: Text(AppLocalizations.of(context).duplicate), - onPressed: () async { - final result = await showDialog( - context: context, - builder: (ctx) => NameDialog( - value: file.fileNameWithoutExtension, - validator: defaultFileNameValidator(context), - ), - ); - if (result == null) return; - if (context.mounted) { - await fileSystem.createFileWithName( - template.setMetadata(metadata.copyWith(name: result)), - name: result, + onSaved: isCore + ? null + : (value) async { + if (value == path) return; + await fileSystem.deleteFile(path); + await fileSystem.createFile( + value, + template.setMetadata(metadata.copyWith(name: value)), ); onChanged(); - } - }, + }, + actions: _buildTemplateMenuChildren( + context, + file: file, + fileSystem: fileSystem, + bloc: bloc, + onChanged: onChanged, + isCore: isCore, + ), + onTap: + onTap ?? + () => openNewDocument( + context, + bloc != null, + template, + fileSystem.storage?.identifier, + ), + ); + } +} + +class _TemplateCard extends StatelessWidget { + final FileSystemFile file; + final TemplateFileSystem fileSystem; + final DocumentBloc? bloc; + final VoidCallback onChanged, onSelected, onUnselected; + final bool selected; + final VoidCallback? onTap; + final bool isActive; + final bool isCore; + final bool starred; + final FavoriteLocation location; + + const _TemplateCard({ + required this.file, + required this.fileSystem, + required this.onChanged, + required this.selected, + this.bloc, + super.key, + required this.onSelected, + required this.onUnselected, + this.onTap, + this.isActive = false, + this.isCore = false, + required this.starred, + required this.location, + }); + + @override + Widget build(BuildContext context) { + final settingsCubit = context.read(); + final settings = settingsCubit.state; + final template = file.data!; + final path = file.pathWithoutLeadingSlash; + final isDefault = settings.defaultTemplate == path; + final metadata = template.getMetadata(); + if (metadata == null) { + return const SizedBox(); + } + final thumbnail = template.getThumbnail(); + const fallback = PhosphorIcon( + PhosphorIconsLight.file, + textDirection: TextDirection.ltr, + size: 48, + ); + + return ContextRegion( + menuChildren: _buildTemplateMenuChildren( + context, + file: file, + fileSystem: fileSystem, + bloc: bloc, + onChanged: onChanged, + isCore: isCore, + ), + builder: (context, button, controller) => Card( + clipBehavior: Clip.antiAlias, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: isActive + ? BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 2, + ) + : BorderSide.none, ), - MenuItemButton( - leadingIcon: const PhosphorIcon(PhosphorIconsLight.info), - child: Text(AppLocalizations.of(context).information), - onPressed: () async { - showLeapBottomSheet( - context: context, - titleBuilder: (context) => Text(metadata.name), - leadingBuilder: (context) => Center(child: leading), - leadingWidth: 128, - childrenBuilder: (context) => [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Card.filled( - child: Padding( - padding: EdgeInsets.all(2), - child: Column( - children: [ - if (metadata.description.isNotEmpty) - ListTile( - title: Text( - AppLocalizations.of(context).description, - ), - subtitle: Text(metadata.description), + child: InkWell( + onTap: + onTap ?? + () => openNewDocument( + context, + bloc != null, + template, + fileSystem.storage?.identifier, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + AspectRatio( + aspectRatio: kThumbnailRatio, + child: Stack( + fit: StackFit.expand, + children: [ + thumbnail != null + ? Image.memory( + thumbnail, + fit: BoxFit.cover, + cacheHeight: kThumbnailHeight, + cacheWidth: kThumbnailWidth, + ) + : Center(child: fallback), + Positioned( + top: 4, + right: 4, + child: IconButton.filledTonal( + icon: Icon(PhosphorIconsLight.star), + selectedIcon: Icon(PhosphorIconsFill.star), + isSelected: starred, + onPressed: () { + settingsCubit.toggleFavoriteTemplate(location); + }, + ), + ), + if (!isCore) + Positioned( + top: 4, + left: 4, + child: Checkbox( + value: selected, + onChanged: (value) { + if (value == true) { + onSelected(); + } else { + onUnselected(); + } + }, + ), + ), + if (isDefault) + Positioned( + top: 4, + right: 4, + child: Icon(PhosphorIconsLight.star, size: 16), + ), + ], + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.fromLTRB(8, 8, 0, 8), + child: Row( + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + metadata.name, + style: Theme.of(context).textTheme.titleMedium, + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - if (metadata.directory.isNotEmpty) - ListTile( - title: Text( - AppLocalizations.of(context).directory, + if (metadata.description.isNotEmpty) + Text( + metadata.description, + style: Theme.of(context).textTheme.bodySmall, + maxLines: 2, + overflow: TextOverflow.ellipsis, ), - subtitle: Text(metadata.directory), - ), - ListTile( - title: Text(AppLocalizations.of(context).tools), - subtitle: Wrap( - alignment: WrapAlignment.center, - children: [ - for (final tool in info?.tools ?? []) - SizedBox.square( - dimension: 64, - child: OptionButton( - icon: Icon( - tool.icon(PhosphorIconsStyle.light), - ), - tooltip: tool.name, - ), - ), - ], - ), - ), - ], + ], + ), ), - ), + button, + ], ), ), - ], - ); - }, - ), - SubmenuButton( - leadingIcon: const PhosphorIcon(PhosphorIconsLight.download), - menuChildren: [ - MenuItemButton( - leadingIcon: const PhosphorIcon(PhosphorIconsLight.archive), - child: Text(AppLocalizations.of(context).packagedFile), - onPressed: () async { - await exportData(context, file.data!, isTextBased: false); - }, - ), - MenuItemButton( - leadingIcon: const PhosphorIcon(PhosphorIconsLight.file), - child: Text(AppLocalizations.of(context).rawFile), - onPressed: () async { - await exportData(context, file.data!, isTextBased: true); - }, - ), - ], - child: Text(AppLocalizations.of(context).export), - ), - MenuItemButton( - leadingIcon: const PhosphorIcon(PhosphorIconsLight.trash), - child: Text(AppLocalizations.of(context).delete), - onPressed: () async { - final result = await showDialog( - context: context, - builder: (ctx) => const DeleteDialog(), - ); - if (result != true) return; - if (context.mounted) { - await fileSystem.deleteFile(path); - onChanged(); - } - }, + ), + ], + ), ), - ], - onTap: () => openNewDocument( - context, - bloc != null, - template, - fileSystem.storage?.identifier, ), ); } } + +List _buildTemplateMenuChildren( + BuildContext context, { + required FileSystemFile file, + required TemplateFileSystem fileSystem, + required DocumentBloc? bloc, + required VoidCallback onChanged, + required bool isCore, +}) { + final settingsCubit = context.read(); + final settings = settingsCubit.state; + final path = file.pathWithoutLeadingSlash; + final isDefault = settings.defaultTemplate == path; + final template = file.data!; + final metadata = template.getMetadata()!; + + return [ + if (!isCore) + CheckboxMenuButton( + value: isDefault, + child: Text(AppLocalizations.of(context).defaultTemplate), + onChanged: (value) async { + settingsCubit.changeDefaultTemplate(path); + onChanged(); + }, + ), + if (bloc != null && !isCore) + MenuItemButton( + leadingIcon: const PhosphorIcon(PhosphorIconsLight.toolbox), + child: Text(AppLocalizations.of(context).overrideTools), + onPressed: () async { + _overrideTools(fileSystem, bloc, [file]); + }, + ), + MenuItemButton( + leadingIcon: const PhosphorIcon(PhosphorIconsLight.copy), + child: Text(AppLocalizations.of(context).duplicate), + onPressed: () async { + final result = await showDialog( + context: context, + builder: (ctx) => NameDialog( + value: file.fileNameWithoutExtension, + validator: defaultFileNameValidator(context), + ), + ); + if (result == null) return; + if (context.mounted) { + await fileSystem.createFileWithName( + template.setMetadata(metadata.copyWith(name: result)), + suffix: '.bfly', + name: result, + ); + onChanged(); + } + }, + ), + if (!isCore) + SubmenuButton( + leadingIcon: const PhosphorIcon(PhosphorIconsLight.download), + menuChildren: [ + MenuItemButton( + leadingIcon: const PhosphorIcon(PhosphorIconsLight.archive), + child: Text(AppLocalizations.of(context).packagedFile), + onPressed: () async { + await exportData(context, file.data!, isTextBased: false); + }, + ), + MenuItemButton( + leadingIcon: const PhosphorIcon(PhosphorIconsLight.file), + child: Text(AppLocalizations.of(context).rawFile), + onPressed: () async { + await exportData(context, file.data!, isTextBased: true); + }, + ), + ], + child: Text(AppLocalizations.of(context).export), + ), + if (!isCore) + MenuItemButton( + leadingIcon: const PhosphorIcon(PhosphorIconsLight.trash), + child: Text(AppLocalizations.of(context).delete), + onPressed: () async { + final result = await showDialog( + context: context, + builder: (ctx) => const DeleteDialog(), + ); + if (result != true) return; + if (context.mounted) { + await fileSystem.deleteFile(path); + onChanged(); + } + }, + ), + ]; +} diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index 6079a457c5c3..3b4bc6d7c790 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -141,6 +141,7 @@ "solid": "Solid", "double": "Double", "dotted": "Dotted", + "dottedDark": "Dotted dark", "dashed": "Dashed", "wavy": "Wavy", "fontWeight": "Font weight", @@ -1267,5 +1268,13 @@ "customizeShortcuts": "Customize shortcuts", "clickToSave": "Click to save", "toolbars": "Toolbars", - "logs": "Logs" + "logs": "Logs", + "ruledSimple": "Ruled simple", + "ruledSimpleDark": "Ruled simple dark", + "quadSimple": "Quad simple", + "quadSimpleDark": "Quad simple dark", + "redLinedRuled": "Red lined ruled", + "redLinedRuledDark": "Red lined ruled dark", + "redLinedQuad": "Red lined quad", + "redLinedQuadDark": "Red lined quad" } diff --git a/app/lib/models/defaults.dart b/app/lib/models/defaults.dart index 31be436dfe3f..bf707b3cd6d6 100644 --- a/app/lib/models/defaults.dart +++ b/app/lib/models/defaults.dart @@ -3,6 +3,7 @@ import 'dart:ui' as ui; import 'package:archive/archive.dart'; import 'package:butterfly/helpers/color.dart'; +import 'package:butterfly/visualizer/preset.dart'; import 'package:flutter/material.dart'; import 'package:butterfly_api/butterfly_api.dart'; import 'package:flutter/services.dart' show rootBundle; @@ -14,6 +15,7 @@ class DocumentDefaults { DocumentDefaults._(); + // ignore: unused_element static Future _createPlainThumnail(SRGBColor color) async { final size = Size(kThumbnailWidth.toDouble(), kThumbnailHeight.toDouble()); final recorder = ui.PictureRecorder(); @@ -48,27 +50,72 @@ class DocumentDefaults { ) .toList(); - static Future> getDefaults(BuildContext context) async { - return Future.wait( - [ - (AppLocalizations.of(context).light, PatternTemplate.plain.create()), - (AppLocalizations.of(context).dark, PatternTemplate.plainDark.create()), - ].map((e) async { - final bg = Background.texture(texture: e.$2); - final color = bg.defaultColor; + static Future> getCoreTemplates( + BuildContext context, { + PatternBackground? background, + }) async { + PatternTexture createRedLinedPattern() { + return PatternTexture( + boxColor: SRGBColor.transparent, + boxXColor: BasicColors.red, + boxWidth: 1200, + boxXCount: 1, + boxXStroke: 1, + ); + } + + return Future.wait([ + ...PatternTemplate.values + .where((e) => background == null || e.background == background) + .map((e) async { + final bg = Background.texture(texture: e.create()); + return createTemplate( + name: e.getLocalizedName(context), + thumbnail: Uint8List.sublistView(await rootBundle.load(e.asset)), + backgrounds: [bg], + ); + }), + ...[ + ( + PatternTemplate.ruledSimple, + 'templates/red_lined_ruled.png', + AppLocalizations.of(context).redLinedRuled, + ), + ( + PatternTemplate.ruledSimpleDark, + 'templates/red_lined_ruled_dark.png', + AppLocalizations.of(context).redLinedRuledDark, + ), + ( + PatternTemplate.quadSimple, + 'templates/red_lined_quad.png', + AppLocalizations.of(context).redLinedQuad, + ), + ( + PatternTemplate.quadSimpleDark, + 'templates/red_lined_quad_dark.png', + AppLocalizations.of(context).redLinedQuadDark, + ), + ].where((e) => background == null || e.$1.background == background).map(( + e, + ) async { + final lined = Background.texture(texture: createRedLinedPattern()); + final bg = Background.texture(texture: e.$1.create()); return createTemplate( - name: e.$1, - thumbnail: await _createPlainThumnail(color), - backgrounds: [bg], + name: e.$3, + thumbnail: Uint8List.sublistView(await rootBundle.load(e.$2)), + backgrounds: [bg, lined], ); }), - ); + ]); } + static Future _loadNoteData(String name) async => NoteData.fromData( + Uint8List.sublistView(await rootBundle.load('defaults/$name.tbfly')), + ); + static Future getCorePack() async { - return _corePack ??= NoteData.fromData( - Uint8List.sublistView(await rootBundle.load('defaults/pack.tbfly')), - ); + return _corePack ??= await _loadNoteData('defaults/pack.tbfly'); } static String translate(String key, Map translations) { diff --git a/app/lib/selections/tools/texture.dart b/app/lib/selections/tools/texture.dart index f190babeb6c9..045589d371c8 100644 --- a/app/lib/selections/tools/texture.dart +++ b/app/lib/selections/tools/texture.dart @@ -14,8 +14,12 @@ class TextureToolSelection extends ToolSelection { ...super.buildProperties(context), const SizedBox(height: 8), ...[ - PatternTemplate.values.where((element) => !element.dark), - PatternTemplate.values.where((element) => element.dark), + PatternTemplate.values.where( + (element) => element.background != PatternBackground.dark, + ), + PatternTemplate.values.where( + (element) => element.background == PatternBackground.dark, + ), ].map( (e) => Wrap( alignment: WrapAlignment.center, diff --git a/app/lib/views/home/page.dart b/app/lib/views/home/page.dart index 845861269b9e..bc246d36b144 100644 --- a/app/lib/views/home/page.dart +++ b/app/lib/views/home/page.dart @@ -4,11 +4,13 @@ import 'package:butterfly/api/file_system.dart'; import 'package:butterfly/api/open.dart'; import 'package:butterfly/cubits/settings.dart'; import 'package:butterfly/dialogs/template.dart'; +import 'package:butterfly/models/defaults.dart'; import 'package:butterfly/services/import.dart'; import 'package:butterfly/settings/home.dart'; import 'package:butterfly/views/files/card.dart'; import 'package:butterfly/views/files/recently.dart'; import 'package:butterfly_api/butterfly_api.dart'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:butterfly/src/generated/i18n/app_localizations.dart'; diff --git a/app/lib/views/home/start.dart b/app/lib/views/home/start.dart index 22f59891b5fc..ba2ff696f45d 100644 --- a/app/lib/views/home/start.dart +++ b/app/lib/views/home/start.dart @@ -18,7 +18,7 @@ class _QuickstartHomeView extends StatefulWidget { class _QuickstartHomeViewState extends State<_QuickstartHomeView> { final ScrollController _scrollController = ScrollController(); late TemplateFileSystem _templateSystem; - Future>? _templatesFuture; + Future>? _templatesFuture; @override void initState() { @@ -26,7 +26,11 @@ class _QuickstartHomeViewState extends State<_QuickstartHomeView> { _templateSystem = context.read().buildTemplateSystem( widget.remote, ); - _templatesFuture = _fetchTemplates(); + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + _templatesFuture = _fetchTemplates(); + }); + }); } @override @@ -48,11 +52,35 @@ class _QuickstartHomeViewState extends State<_QuickstartHomeView> { super.dispose(); } - Future> _fetchTemplates() => _templateSystem.initialize().then( - (value) => _templateSystem.getFiles().then( - (value) => value.map((e) => e.data!).toList(), - ), - ); + Future> _fetchTemplates() async { + if (!mounted) return []; + final settings = context.read().state; + final favorites = settings.favoriteTemplates; + final fileSystem = context.read(); + final coreTemplates = await DocumentDefaults.getCoreTemplates(context); + + final result = <({NoteData file, String remote})>[]; + + for (final location in favorites) { + final remote = location.remote; + if (remote == null) { + final template = coreTemplates.firstWhereOrNull( + (e) => e.name == location.path, + ); + if (template == null) continue; + result.add((file: template, remote: settings.defaultRemote)); + continue; + } + final templateSystem = fileSystem.buildTemplateSystem( + settings.getRemote(remote), + ); + final file = await templateSystem.getFile(location.path); + if (file == null) continue; + result.add((file: file, remote: remote)); + } + + return result; + } @override Widget build(BuildContext context) { @@ -89,7 +117,7 @@ class _QuickstartHomeViewState extends State<_QuickstartHomeView> { ], ), SizedBox(height: widget.isMobile ? 8 : 16), - FutureBuilder>( + FutureBuilder>( future: _templatesFuture, builder: (context, snapshot) { if (snapshot.hasError) { @@ -136,18 +164,14 @@ class _QuickstartHomeViewState extends State<_QuickstartHomeView> { ); } final children = templates.map((e) { - final thumbnail = e.getThumbnail(); - final metadata = e.getMetadata()!; + final data = e.file; + final thumbnail = data.getThumbnail(); + final metadata = data.getMetadata()!; return AssetCard( metadata: metadata, thumbnail: thumbnail, onTap: () async { - await openNewDocument( - context, - false, - e, - widget.remote?.identifier, - ); + await openNewDocument(context, false, data, e.remote); widget.onReload(); }, ); diff --git a/app/lib/views/main.dart b/app/lib/views/main.dart index 5ea3e7853c76..b35f29787aa3 100644 --- a/app/lib/views/main.dart +++ b/app/lib/views/main.dart @@ -254,6 +254,7 @@ class _ProjectPageState extends State { if (kDebugMode) { print(e); } + if (!mounted) return; setState(() { _transformCubit = TransformCubit(pixelRatio); _currentIndexCubit = CurrentIndexCubit( diff --git a/app/lib/visualizer/preset.dart b/app/lib/visualizer/preset.dart index ec53883a3f5a..0cf7e9424f64 100644 --- a/app/lib/visualizer/preset.dart +++ b/app/lib/visualizer/preset.dart @@ -10,10 +10,20 @@ extension PatternTemplateHelper on PatternTemplate { PatternTemplate.ruled => AppLocalizations.of(context).ruled, PatternTemplate.quad => AppLocalizations.of(context).quad, PatternTemplate.music => AppLocalizations.of(context).music, + PatternTemplate.dotted => AppLocalizations.of(context).dotted, + PatternTemplate.ruledSimple => AppLocalizations.of(context).ruledSimple, + PatternTemplate.quadSimple => AppLocalizations.of(context).quadSimple, PatternTemplate.plainDark => AppLocalizations.of(context).plainDark, PatternTemplate.ruledDark => AppLocalizations.of(context).ruledDark, PatternTemplate.quadDark => AppLocalizations.of(context).quadDark, PatternTemplate.musicDark => AppLocalizations.of(context).musicDark, + PatternTemplate.dottedDark => AppLocalizations.of(context).dottedDark, + PatternTemplate.ruledSimpleDark => AppLocalizations.of( + context, + ).ruledSimpleDark, + PatternTemplate.quadSimpleDark => AppLocalizations.of( + context, + ).quadSimpleDark, }; } diff --git a/app/lib/widgets/editable_list_tile.dart b/app/lib/widgets/editable_list_tile.dart index eac0d300d70b..11031e5b0823 100644 --- a/app/lib/widgets/editable_list_tile.dart +++ b/app/lib/widgets/editable_list_tile.dart @@ -60,6 +60,15 @@ class _EditableListTileState extends State { void didUpdateWidget(covariant EditableListTile oldWidget) { super.didUpdateWidget(oldWidget); + if (widget.onSaved != oldWidget.onSaved && oldWidget.onSaved != null) { + _onSaved(); + } + if (widget.onSaved != oldWidget.onSaved) { + setState(() { + _isEditing = false; + }); + } + if (widget.controller == oldWidget.controller) { return; } @@ -92,11 +101,12 @@ class _EditableListTileState extends State { tooltip: AppLocalizations.of(context).actions, builder: (context, button, controller) => _buildWidget(context, button), menuChildren: [ - MenuItemButton( - leadingIcon: const PhosphorIcon(PhosphorIconsLight.textT), - onPressed: _edit, - child: Text(AppLocalizations.of(context).rename), - ), + if (widget.onSaved != null) + MenuItemButton( + leadingIcon: const PhosphorIcon(PhosphorIconsLight.textT), + onPressed: _edit, + child: Text(AppLocalizations.of(context).rename), + ), ...widget.actions!, ], ); diff --git a/app/templates/dotted.png b/app/templates/dotted.png new file mode 100644 index 000000000000..3c9f51673512 Binary files /dev/null and b/app/templates/dotted.png differ diff --git a/app/templates/dotted_dark.png b/app/templates/dotted_dark.png new file mode 100644 index 000000000000..533bc136da0f Binary files /dev/null and b/app/templates/dotted_dark.png differ diff --git a/app/templates/music.png b/app/templates/music.png index f896fb24cfbb..f17d8944fbd5 100644 Binary files a/app/templates/music.png and b/app/templates/music.png differ diff --git a/app/templates/music_dark.png b/app/templates/music_dark.png index 1c6a38ac76f5..4ebef51c2b8c 100644 Binary files a/app/templates/music_dark.png and b/app/templates/music_dark.png differ diff --git a/app/templates/plain.png b/app/templates/plain.png index 6d291a011ec8..7e953424f7dd 100644 Binary files a/app/templates/plain.png and b/app/templates/plain.png differ diff --git a/app/templates/plain_dark.png b/app/templates/plain_dark.png index 8a49ddf7ee59..1e6e0e6b733d 100644 Binary files a/app/templates/plain_dark.png and b/app/templates/plain_dark.png differ diff --git a/app/templates/quad.png b/app/templates/quad.png index 5a4aca96f86b..150a7d7c466e 100644 Binary files a/app/templates/quad.png and b/app/templates/quad.png differ diff --git a/app/templates/quad_dark.png b/app/templates/quad_dark.png index 4a906b8c9fa0..d73ff9484aa7 100644 Binary files a/app/templates/quad_dark.png and b/app/templates/quad_dark.png differ diff --git a/app/templates/quad_simple.png b/app/templates/quad_simple.png new file mode 100644 index 000000000000..cc7c4fb1b3c6 Binary files /dev/null and b/app/templates/quad_simple.png differ diff --git a/app/templates/quad_simple_dark.png b/app/templates/quad_simple_dark.png new file mode 100644 index 000000000000..2f252c94b031 Binary files /dev/null and b/app/templates/quad_simple_dark.png differ diff --git a/app/templates/red_lined_quad.png b/app/templates/red_lined_quad.png new file mode 100644 index 000000000000..58b9d71dc298 Binary files /dev/null and b/app/templates/red_lined_quad.png differ diff --git a/app/templates/red_lined_quad_dark.png b/app/templates/red_lined_quad_dark.png new file mode 100644 index 000000000000..5129e54c2cca Binary files /dev/null and b/app/templates/red_lined_quad_dark.png differ diff --git a/app/templates/red_lined_ruled.png b/app/templates/red_lined_ruled.png new file mode 100644 index 000000000000..020154dc244b Binary files /dev/null and b/app/templates/red_lined_ruled.png differ diff --git a/app/templates/red_lined_ruled_dark.png b/app/templates/red_lined_ruled_dark.png new file mode 100644 index 000000000000..4dd778898a8b Binary files /dev/null and b/app/templates/red_lined_ruled_dark.png differ diff --git a/app/templates/ruled.png b/app/templates/ruled.png index eb690195f4aa..d1156cb4fcc2 100644 Binary files a/app/templates/ruled.png and b/app/templates/ruled.png differ diff --git a/app/templates/ruled_dark.png b/app/templates/ruled_dark.png index 86e415c1d516..aa4589d663de 100644 Binary files a/app/templates/ruled_dark.png and b/app/templates/ruled_dark.png differ diff --git a/app/templates/ruled_simple.png b/app/templates/ruled_simple.png new file mode 100644 index 000000000000..e2fec7f51fa6 Binary files /dev/null and b/app/templates/ruled_simple.png differ diff --git a/app/templates/ruled_simple_dark.png b/app/templates/ruled_simple_dark.png new file mode 100644 index 000000000000..9da5a2010a9b Binary files /dev/null and b/app/templates/ruled_simple_dark.png differ diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index fecdd164d42e..dc42da47fb57 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -1335,8 +1335,8 @@ packages: '@surma/rollup-plugin-off-main-thread@2.2.3': resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} - '@swc/helpers@0.5.17': - resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} + '@swc/helpers@0.5.18': + resolution: {integrity: sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==} '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -5172,7 +5172,7 @@ snapshots: magic-string: 0.25.9 string.prototype.matchall: 4.0.12 - '@swc/helpers@0.5.17': + '@swc/helpers@0.5.18': dependencies: tslib: 2.8.1 @@ -6039,7 +6039,7 @@ snapshots: fontkit@2.0.4: dependencies: - '@swc/helpers': 0.5.17 + '@swc/helpers': 0.5.18 brotli: 1.3.3 clone: 2.1.2 dfa: 1.2.0