From b4d9680cf61059f3cfde62f22b2678ce3f0dc4fa Mon Sep 17 00:00:00 2001 From: Lenkomotive Date: Wed, 9 Oct 2024 22:35:59 +0200 Subject: [PATCH 01/16] PAINTROID-454: Flutter: Add Layers --- .../state/layer_menu_state_data.dart | 11 ++ .../state/layer_menu_state_data.freezed.dart | 135 ++++++++++++++++++ .../state/layer_menu_state_provider.dart | 18 +++ .../state/layer_menu_state_provider.g.dart | 27 ++++ .../components/bottom_bar/bottom_nav_bar.dart | 110 +++++++------- .../components/layer_menu/layer_menu.dart | 30 ++++ .../pages/workspace_page/workspace_page.dart | 6 +- lib/ui/shared/fade_in_out_widget.dart | 28 ++++ pubspec.lock | 72 ++++++---- 9 files changed, 358 insertions(+), 79 deletions(-) create mode 100644 lib/core/providers/state/layer_menu_state_data.dart create mode 100644 lib/core/providers/state/layer_menu_state_data.freezed.dart create mode 100644 lib/core/providers/state/layer_menu_state_provider.dart create mode 100644 lib/core/providers/state/layer_menu_state_provider.g.dart create mode 100644 lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart create mode 100644 lib/ui/shared/fade_in_out_widget.dart diff --git a/lib/core/providers/state/layer_menu_state_data.dart b/lib/core/providers/state/layer_menu_state_data.dart new file mode 100644 index 00000000..e5ca684c --- /dev/null +++ b/lib/core/providers/state/layer_menu_state_data.dart @@ -0,0 +1,11 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'layer_menu_state_data.freezed.dart'; + +@immutable +@freezed +class LayerMenuStateData with _$LayerMenuStateData { + const factory LayerMenuStateData({ + required bool isVisible, + }) = _LayerMenuStateData; +} diff --git a/lib/core/providers/state/layer_menu_state_data.freezed.dart b/lib/core/providers/state/layer_menu_state_data.freezed.dart new file mode 100644 index 00000000..4b19bd3d --- /dev/null +++ b/lib/core/providers/state/layer_menu_state_data.freezed.dart @@ -0,0 +1,135 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'layer_menu_state_data.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$LayerMenuStateData { + bool get isVisible => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $LayerMenuStateDataCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LayerMenuStateDataCopyWith<$Res> { + factory $LayerMenuStateDataCopyWith( + LayerMenuStateData value, $Res Function(LayerMenuStateData) then) = + _$LayerMenuStateDataCopyWithImpl<$Res, LayerMenuStateData>; + @useResult + $Res call({bool isVisible}); +} + +/// @nodoc +class _$LayerMenuStateDataCopyWithImpl<$Res, $Val extends LayerMenuStateData> + implements $LayerMenuStateDataCopyWith<$Res> { + _$LayerMenuStateDataCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? isVisible = null, + }) { + return _then(_value.copyWith( + isVisible: null == isVisible + ? _value.isVisible + : isVisible // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$LayerMenuStateDataImplCopyWith<$Res> + implements $LayerMenuStateDataCopyWith<$Res> { + factory _$$LayerMenuStateDataImplCopyWith(_$LayerMenuStateDataImpl value, + $Res Function(_$LayerMenuStateDataImpl) then) = + __$$LayerMenuStateDataImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({bool isVisible}); +} + +/// @nodoc +class __$$LayerMenuStateDataImplCopyWithImpl<$Res> + extends _$LayerMenuStateDataCopyWithImpl<$Res, _$LayerMenuStateDataImpl> + implements _$$LayerMenuStateDataImplCopyWith<$Res> { + __$$LayerMenuStateDataImplCopyWithImpl(_$LayerMenuStateDataImpl _value, + $Res Function(_$LayerMenuStateDataImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? isVisible = null, + }) { + return _then(_$LayerMenuStateDataImpl( + isVisible: null == isVisible + ? _value.isVisible + : isVisible // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$LayerMenuStateDataImpl implements _LayerMenuStateData { + const _$LayerMenuStateDataImpl({required this.isVisible}); + + @override + final bool isVisible; + + @override + String toString() { + return 'LayerMenuStateData(isVisible: $isVisible)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LayerMenuStateDataImpl && + (identical(other.isVisible, isVisible) || + other.isVisible == isVisible)); + } + + @override + int get hashCode => Object.hash(runtimeType, isVisible); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$LayerMenuStateDataImplCopyWith<_$LayerMenuStateDataImpl> get copyWith => + __$$LayerMenuStateDataImplCopyWithImpl<_$LayerMenuStateDataImpl>( + this, _$identity); +} + +abstract class _LayerMenuStateData implements LayerMenuStateData { + const factory _LayerMenuStateData({required final bool isVisible}) = + _$LayerMenuStateDataImpl; + + @override + bool get isVisible; + @override + @JsonKey(ignore: true) + _$$LayerMenuStateDataImplCopyWith<_$LayerMenuStateDataImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/core/providers/state/layer_menu_state_provider.dart b/lib/core/providers/state/layer_menu_state_provider.dart new file mode 100644 index 00000000..62bcdf91 --- /dev/null +++ b/lib/core/providers/state/layer_menu_state_provider.dart @@ -0,0 +1,18 @@ +import 'package:paintroid/core/providers/state/layer_menu_state_data.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'layer_menu_state_provider.g.dart'; + +@Riverpod(keepAlive: true) +class LayerMenuStateProvider extends _$LayerMenuStateProvider { + @override + LayerMenuStateData build() { + return const LayerMenuStateData( + isVisible: false, + ); + } + + void toggleVisibility() { + state = state.copyWith(isVisible: !state.isVisible); + } +} diff --git a/lib/core/providers/state/layer_menu_state_provider.g.dart b/lib/core/providers/state/layer_menu_state_provider.g.dart new file mode 100644 index 00000000..e906cb19 --- /dev/null +++ b/lib/core/providers/state/layer_menu_state_provider.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'layer_menu_state_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$layerMenuStateProviderHash() => + r'1dc3f0fa55df98057c2b981e7b215ccea251542a'; + +/// See also [LayerMenuStateProvider]. +@ProviderFor(LayerMenuStateProvider) +final layerMenuStateProvider = + NotifierProvider.internal( + LayerMenuStateProvider.new, + name: r'layerMenuStateProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$layerMenuStateProviderHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$LayerMenuStateProvider = Notifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/ui/pages/workspace_page/components/bottom_bar/bottom_nav_bar.dart b/lib/ui/pages/workspace_page/components/bottom_bar/bottom_nav_bar.dart index 6f23f496..e4e83f9e 100644 --- a/lib/ui/pages/workspace_page/components/bottom_bar/bottom_nav_bar.dart +++ b/lib/ui/pages/workspace_page/components/bottom_bar/bottom_nav_bar.dart @@ -1,10 +1,9 @@ +import 'package:colorpicker/colorpicker.dart'; import 'package:flutter/material.dart'; - import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:colorpicker/colorpicker.dart'; - import 'package:paintroid/core/enums/tool_types.dart'; import 'package:paintroid/core/localization/app_localizations.dart'; +import 'package:paintroid/core/providers/state/layer_menu_state_provider.dart'; import 'package:paintroid/core/providers/state/paint_provider.dart'; import 'package:paintroid/core/providers/state/tool_options_visibility_state_provider.dart'; import 'package:paintroid/core/providers/state/toolbox_state_provider.dart'; @@ -78,59 +77,66 @@ class BottomNavBar extends ConsumerWidget { ); return currentToolData; } -} -void _onNavigationItemSelected(int index, BuildContext context, WidgetRef ref) { - BottomNavBarItem item = BottomNavBarItem.values[index]; - switch (item) { - case BottomNavBarItem.TOOLS: - _showToolBottomSheet(context); - break; - case BottomNavBarItem.TOOL_OPTIONS: - _handleToolOptionsVisibility(ref); - break; - case BottomNavBarItem.COLOR: - _showColorPicker(context, ref); - break; - default: - return; + void _onNavigationItemSelected( + int index, BuildContext context, WidgetRef ref) { + BottomNavBarItem item = BottomNavBarItem.values[index]; + switch (item) { + case BottomNavBarItem.TOOLS: + _showToolBottomSheet(context); + break; + case BottomNavBarItem.TOOL_OPTIONS: + _handleToolOptionsVisibility(ref); + break; + case BottomNavBarItem.COLOR: + _showColorPicker(context, ref); + break; + case BottomNavBarItem.LAYERS: + _showLayerMenu(context, ref); + default: + return; + } } -} -void _showToolBottomSheet(BuildContext context) { - double screenHeight = MediaQuery.of(context).size.height; - showModalBottomSheet( - context: context, - builder: (BuildContext context) => SizedBox( - height: screenHeight * 0.5, - child: const ToolsBottomSheet(), - ), - ); -} + void _showToolBottomSheet(BuildContext context) { + double screenHeight = MediaQuery.of(context).size.height; + showModalBottomSheet( + context: context, + builder: (BuildContext context) => SizedBox( + height: screenHeight * 0.5, + child: const ToolsBottomSheet(), + ), + ); + } -void _handleToolOptionsVisibility(WidgetRef ref) { - ref.read(toolOptionsVisibilityStateProvider.notifier).toggleVisibility(); -} + void _handleToolOptionsVisibility(WidgetRef ref) { + ref.read(toolOptionsVisibilityStateProvider.notifier).toggleVisibility(); + } -void _showColorPicker(BuildContext context, WidgetRef ref) { - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (BuildContext dialogContext) => Container( - height: MediaQuery.of(dialogContext).size.height * 0.7, - alignment: Alignment.center, - decoration: BoxDecoration( - color: PaintroidTheme.of(dialogContext).onSurfaceColor, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(16.0), - topRight: Radius.circular(16.0), - )), - child: ColorPicker( - currentColor: ref.watch(paintProvider).color, - onColorChanged: (newColor) { - ref.watch(paintProvider.notifier).updateColor(newColor); - }, + void _showColorPicker(BuildContext context, WidgetRef ref) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (BuildContext dialogContext) => Container( + height: MediaQuery.of(dialogContext).size.height * 0.7, + alignment: Alignment.center, + decoration: BoxDecoration( + color: PaintroidTheme.of(dialogContext).onSurfaceColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16.0), + topRight: Radius.circular(16.0), + )), + child: ColorPicker( + currentColor: ref.watch(paintProvider).color, + onColorChanged: (newColor) { + ref.watch(paintProvider.notifier).updateColor(newColor); + }, + ), ), - ), - ); + ); + } + + void _showLayerMenu(BuildContext context, WidgetRef ref) { + ref.read(layerMenuStateProvider.notifier).toggleVisibility(); + } } diff --git a/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart b/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart new file mode 100644 index 00000000..af1f231c --- /dev/null +++ b/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:paintroid/core/providers/state/layer_menu_state_provider.dart'; +import 'package:paintroid/ui/shared/fade_in_out_widget.dart'; + +class LayerMenu extends ConsumerWidget { + const LayerMenu({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isVisible = ref.watch( + layerMenuStateProvider.select((state) => state.isVisible), + ); + return Positioned( + top: 32, + bottom: 32, + right: 8, + child: FadeInOutWidget( + isVisible: isVisible, + child: Container( + width: 200, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ); + } +} diff --git a/lib/ui/pages/workspace_page/workspace_page.dart b/lib/ui/pages/workspace_page/workspace_page.dart index 6d31be18..23c5d62d 100644 --- a/lib/ui/pages/workspace_page/workspace_page.dart +++ b/lib/ui/pages/workspace_page/workspace_page.dart @@ -1,17 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; - import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:toast/toast.dart'; - import 'package:paintroid/core/providers/object/io_handler.dart'; import 'package:paintroid/core/providers/state/workspace_state_notifier.dart'; import 'package:paintroid/ui/pages/workspace_page/components/bottom_bar/bottom_nav_bar.dart'; import 'package:paintroid/ui/pages/workspace_page/components/bottom_bar/tool_options/tool_options.dart'; import 'package:paintroid/ui/pages/workspace_page/components/drawing_surface/drawing_canvas.dart'; import 'package:paintroid/ui/pages/workspace_page/components/drawing_surface/exit_fullscreen_button.dart'; +import 'package:paintroid/ui/pages/workspace_page/components/layer_menu/layer_menu.dart'; import 'package:paintroid/ui/pages/workspace_page/components/top_bar/top_app_bar.dart'; import 'package:paintroid/ui/shared/dialogs/discard_changes_dialog.dart'; +import 'package:toast/toast.dart'; class WorkspacePage extends ConsumerStatefulWidget { const WorkspacePage({super.key}); @@ -78,6 +77,7 @@ class _WorkspaceScreenState extends ConsumerState { ) else const ToolOptions(), + const LayerMenu(), ], ), bottomNavigationBar: isFullscreen ? null : const BottomNavBar(), diff --git a/lib/ui/shared/fade_in_out_widget.dart b/lib/ui/shared/fade_in_out_widget.dart new file mode 100644 index 00000000..574a266b --- /dev/null +++ b/lib/ui/shared/fade_in_out_widget.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +const Duration animationDuration = Duration(milliseconds: 300); +const double maxAnimationOpacity = 1.0; +const double minAnimationOpacity = 0.0; + +class FadeInOutWidget extends StatelessWidget { + final bool isVisible; + final Widget child; + + const FadeInOutWidget({ + super.key, + required this.isVisible, + required this.child, + }); + + @override + Widget build(BuildContext context) { + return IgnorePointer( + ignoring: !isVisible, + child: AnimatedOpacity( + opacity: isVisible ? maxAnimationOpacity : minAnimationOpacity, + duration: animationDuration, + child: child, + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 41146529..01c51cd8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -292,10 +292,10 @@ packages: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" file_picker: dependency: "direct main" description: @@ -607,10 +607,10 @@ packages: dependency: "direct main" description: name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf url: "https://pub.dev" source: hosted - version: "0.18.1" + version: "0.19.0" io: dependency: transitive description: @@ -651,6 +651,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + url: "https://pub.dev" + source: hosted + version: "10.0.4" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" lints: dependency: transitive description: @@ -679,26 +703,26 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.12.0" mime: dependency: transitive description: @@ -751,10 +775,10 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" path_drawing: dependency: transitive description: @@ -871,10 +895,10 @@ packages: dependency: transitive description: name: platform - sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.4" plugin_platform_interface: dependency: transitive description: @@ -895,10 +919,10 @@ packages: dependency: transitive description: name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "5.0.2" pub_semver: dependency: transitive description: @@ -1196,10 +1220,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.0" timing: dependency: transitive description: @@ -1316,10 +1340,10 @@ packages: dependency: transitive description: name: vm_service - sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "11.10.0" + version: "14.2.1" watcher: dependency: transitive description: @@ -1348,10 +1372,10 @@ packages: dependency: transitive description: name: webdriver - sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49" + sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" win32: dependency: transitive description: @@ -1393,5 +1417,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" From 23485ba9a2f35326f76c648cb0a3bb0b85edadb1 Mon Sep 17 00:00:00 2001 From: Lenkomotive Date: Wed, 9 Oct 2024 22:37:58 +0200 Subject: [PATCH 02/16] PAINTROID-454: Flutter: Add Layers - remove unused parameter --- .../workspace_page/components/bottom_bar/bottom_nav_bar.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ui/pages/workspace_page/components/bottom_bar/bottom_nav_bar.dart b/lib/ui/pages/workspace_page/components/bottom_bar/bottom_nav_bar.dart index e4e83f9e..4f408397 100644 --- a/lib/ui/pages/workspace_page/components/bottom_bar/bottom_nav_bar.dart +++ b/lib/ui/pages/workspace_page/components/bottom_bar/bottom_nav_bar.dart @@ -92,7 +92,7 @@ class BottomNavBar extends ConsumerWidget { _showColorPicker(context, ref); break; case BottomNavBarItem.LAYERS: - _showLayerMenu(context, ref); + _showLayerMenu(ref); default: return; } @@ -136,7 +136,7 @@ class BottomNavBar extends ConsumerWidget { ); } - void _showLayerMenu(BuildContext context, WidgetRef ref) { + void _showLayerMenu(WidgetRef ref) { ref.read(layerMenuStateProvider.notifier).toggleVisibility(); } } From 08de768a0e74bf545be099e5c788f06f75fc6125 Mon Sep 17 00:00:00 2001 From: Lenkomotive Date: Wed, 9 Oct 2024 22:41:54 +0200 Subject: [PATCH 03/16] PAINTROID-454: Flutter: Add Layers - refactor ToolOptions to use fadeinoutwidget --- .../bottom_bar/tool_options/tool_option.dart | 28 ------------------- .../bottom_bar/tool_options/tool_options.dart | 9 ++---- 2 files changed, 3 insertions(+), 34 deletions(-) delete mode 100644 lib/ui/pages/workspace_page/components/bottom_bar/tool_options/tool_option.dart diff --git a/lib/ui/pages/workspace_page/components/bottom_bar/tool_options/tool_option.dart b/lib/ui/pages/workspace_page/components/bottom_bar/tool_options/tool_option.dart deleted file mode 100644 index 666ffda1..00000000 --- a/lib/ui/pages/workspace_page/components/bottom_bar/tool_options/tool_option.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; - -class ToolOption extends StatelessWidget { - final bool isIgnoring; - final double opacity; - final Widget child; - final Duration duration; - - const ToolOption({ - super.key, - required this.isIgnoring, - required this.opacity, - required this.child, - this.duration = const Duration(milliseconds: 300), - }); - - @override - Widget build(BuildContext context) { - return IgnorePointer( - ignoring: isIgnoring, - child: AnimatedOpacity( - opacity: opacity, - duration: duration, - child: child, - ), - ); - } -} diff --git a/lib/ui/pages/workspace_page/components/bottom_bar/tool_options/tool_options.dart b/lib/ui/pages/workspace_page/components/bottom_bar/tool_options/tool_options.dart index 0e3968c6..19ae0556 100644 --- a/lib/ui/pages/workspace_page/components/bottom_bar/tool_options/tool_options.dart +++ b/lib/ui/pages/workspace_page/components/bottom_bar/tool_options/tool_options.dart @@ -5,12 +5,10 @@ import 'package:paintroid/core/providers/state/tool_options_visibility_state_pro import 'package:paintroid/core/providers/state/toolbox_state_provider.dart'; import 'package:paintroid/ui/pages/workspace_page/components/bottom_bar/tool_options/shapes_tool/shapes_tool_options.dart'; import 'package:paintroid/ui/pages/workspace_page/components/bottom_bar/tool_options/stroke_tool_options.dart'; -import 'package:paintroid/ui/pages/workspace_page/components/bottom_bar/tool_options/tool_option.dart'; +import 'package:paintroid/ui/shared/fade_in_out_widget.dart'; class ToolOptions extends ConsumerWidget { const ToolOptions({super.key}); - final maxOpacity = 1.0; - final minOpacity = 0.0; @override Widget build(BuildContext context, WidgetRef ref) { @@ -21,9 +19,8 @@ class ToolOptions extends ConsumerWidget { return Padding( padding: const EdgeInsets.all(8), - child: ToolOption( - isIgnoring: !visible, - opacity: visible ? maxOpacity : minOpacity, + child: FadeInOutWidget( + isVisible: visible, child: switch (currentToolType) { ToolType.BRUSH => const StrokeToolOptions(), ToolType.ERASER => const StrokeToolOptions(), From 0f84aa8c6d2657eb1da987773b4d1ba24c8e8c6a Mon Sep 17 00:00:00 2001 From: Lenkomotive Date: Wed, 9 Oct 2024 22:52:21 +0200 Subject: [PATCH 04/16] PAINTROID-454: Flutter: Add Layers - hide after clicking on canvas --- lib/core/providers/state/layer_menu_state_provider.dart | 4 ++++ .../components/drawing_surface/drawing_canvas.dart | 4 ++-- .../workspace_page/components/layer_menu/layer_menu.dart | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/core/providers/state/layer_menu_state_provider.dart b/lib/core/providers/state/layer_menu_state_provider.dart index 62bcdf91..b4625ced 100644 --- a/lib/core/providers/state/layer_menu_state_provider.dart +++ b/lib/core/providers/state/layer_menu_state_provider.dart @@ -15,4 +15,8 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { void toggleVisibility() { state = state.copyWith(isVisible: !state.isVisible); } + + void hide() { + state = state.copyWith(isVisible: false); + } } diff --git a/lib/ui/pages/workspace_page/components/drawing_surface/drawing_canvas.dart b/lib/ui/pages/workspace_page/components/drawing_surface/drawing_canvas.dart index b4892e61..a7945b3a 100644 --- a/lib/ui/pages/workspace_page/components/drawing_surface/drawing_canvas.dart +++ b/lib/ui/pages/workspace_page/components/drawing_surface/drawing_canvas.dart @@ -1,11 +1,10 @@ import 'package:flutter/material.dart'; - import 'package:flutter_riverpod/flutter_riverpod.dart'; - import 'package:paintroid/core/enums/tool_types.dart'; import 'package:paintroid/core/providers/object/canvas_painter_provider.dart'; import 'package:paintroid/core/providers/object/device_service.dart'; import 'package:paintroid/core/providers/state/canvas_state_provider.dart'; +import 'package:paintroid/core/providers/state/layer_menu_state_provider.dart'; import 'package:paintroid/core/providers/state/toolbox_state_provider.dart'; import 'package:paintroid/core/providers/state/workspace_state_notifier.dart'; import 'package:paintroid/ui/pages/workspace_page/components/drawing_surface/canvas_painter.dart'; @@ -46,6 +45,7 @@ class _DrawingCanvasState extends ConsumerState { }); void _onPointerDown(PointerDownEvent _) { + ref.read(layerMenuStateProvider.notifier).hide(); _pointersOnScreen++; if (_pointersOnScreen >= 2) { _isZooming = true; diff --git a/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart b/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart index af1f231c..9bd9630a 100644 --- a/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart +++ b/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart @@ -12,8 +12,8 @@ class LayerMenu extends ConsumerWidget { layerMenuStateProvider.select((state) => state.isVisible), ); return Positioned( - top: 32, - bottom: 32, + top: 54, + bottom: 54, right: 8, child: FadeInOutWidget( isVisible: isVisible, From 80a1af50db00a385fff88a9c802d9f1fbabdbbb3 Mon Sep 17 00:00:00 2001 From: Lenkomotive Date: Wed, 9 Oct 2024 22:54:28 +0200 Subject: [PATCH 05/16] PAINTROID-454: Flutter: Add Layers - hide after clicking on canvas --- lib/ui/shared/fade_in_out_widget.dart | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/ui/shared/fade_in_out_widget.dart b/lib/ui/shared/fade_in_out_widget.dart index 574a266b..7f39c362 100644 --- a/lib/ui/shared/fade_in_out_widget.dart +++ b/lib/ui/shared/fade_in_out_widget.dart @@ -1,9 +1,5 @@ import 'package:flutter/material.dart'; -const Duration animationDuration = Duration(milliseconds: 300); -const double maxAnimationOpacity = 1.0; -const double minAnimationOpacity = 0.0; - class FadeInOutWidget extends StatelessWidget { final bool isVisible; final Widget child; @@ -19,8 +15,8 @@ class FadeInOutWidget extends StatelessWidget { return IgnorePointer( ignoring: !isVisible, child: AnimatedOpacity( - opacity: isVisible ? maxAnimationOpacity : minAnimationOpacity, - duration: animationDuration, + opacity: isVisible ? 1.0 : 0.0, + duration: const Duration(milliseconds: 300), child: child, ), ); From bc56d56c7090b9fd9e456ea7cc98ebbea962b553 Mon Sep 17 00:00:00 2001 From: Lenkomotive Date: Thu, 10 Oct 2024 22:00:07 +0200 Subject: [PATCH 06/16] PAINTROID-454: Flutter: Add Layers - reorder positions, select, delete, add layer --- .../state/layer_menu_state_data.dart | 2 + .../state/layer_menu_state_data.freezed.dart | 41 ++++- .../state/layer_menu_state_provider.dart | 49 +++++- .../state/layer_menu_state_provider.g.dart | 2 +- .../providers/state/layer_state_data.dart | 12 ++ .../state/layer_state_data.freezed.dart | 152 ++++++++++++++++++ .../components/layer_menu/layer.dart | 50 ++++++ .../components/layer_menu/layer_menu.dart | 43 +++++ 8 files changed, 338 insertions(+), 13 deletions(-) create mode 100644 lib/core/providers/state/layer_state_data.dart create mode 100644 lib/core/providers/state/layer_state_data.freezed.dart create mode 100644 lib/ui/pages/workspace_page/components/layer_menu/layer.dart diff --git a/lib/core/providers/state/layer_menu_state_data.dart b/lib/core/providers/state/layer_menu_state_data.dart index e5ca684c..e0e8b1a3 100644 --- a/lib/core/providers/state/layer_menu_state_data.dart +++ b/lib/core/providers/state/layer_menu_state_data.dart @@ -1,4 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:paintroid/core/providers/state/layer_state_data.dart'; part 'layer_menu_state_data.freezed.dart'; @@ -7,5 +8,6 @@ part 'layer_menu_state_data.freezed.dart'; class LayerMenuStateData with _$LayerMenuStateData { const factory LayerMenuStateData({ required bool isVisible, + required List layer, }) = _LayerMenuStateData; } diff --git a/lib/core/providers/state/layer_menu_state_data.freezed.dart b/lib/core/providers/state/layer_menu_state_data.freezed.dart index 4b19bd3d..02214554 100644 --- a/lib/core/providers/state/layer_menu_state_data.freezed.dart +++ b/lib/core/providers/state/layer_menu_state_data.freezed.dart @@ -17,6 +17,7 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$LayerMenuStateData { bool get isVisible => throw _privateConstructorUsedError; + List get layer => throw _privateConstructorUsedError; @JsonKey(ignore: true) $LayerMenuStateDataCopyWith get copyWith => @@ -29,7 +30,7 @@ abstract class $LayerMenuStateDataCopyWith<$Res> { LayerMenuStateData value, $Res Function(LayerMenuStateData) then) = _$LayerMenuStateDataCopyWithImpl<$Res, LayerMenuStateData>; @useResult - $Res call({bool isVisible}); + $Res call({bool isVisible, List layer}); } /// @nodoc @@ -46,12 +47,17 @@ class _$LayerMenuStateDataCopyWithImpl<$Res, $Val extends LayerMenuStateData> @override $Res call({ Object? isVisible = null, + Object? layer = null, }) { return _then(_value.copyWith( isVisible: null == isVisible ? _value.isVisible : isVisible // ignore: cast_nullable_to_non_nullable as bool, + layer: null == layer + ? _value.layer + : layer // ignore: cast_nullable_to_non_nullable + as List, ) as $Val); } } @@ -64,7 +70,7 @@ abstract class _$$LayerMenuStateDataImplCopyWith<$Res> __$$LayerMenuStateDataImplCopyWithImpl<$Res>; @override @useResult - $Res call({bool isVisible}); + $Res call({bool isVisible, List layer}); } /// @nodoc @@ -79,12 +85,17 @@ class __$$LayerMenuStateDataImplCopyWithImpl<$Res> @override $Res call({ Object? isVisible = null, + Object? layer = null, }) { return _then(_$LayerMenuStateDataImpl( isVisible: null == isVisible ? _value.isVisible : isVisible // ignore: cast_nullable_to_non_nullable as bool, + layer: null == layer + ? _value._layer + : layer // ignore: cast_nullable_to_non_nullable + as List, )); } } @@ -92,14 +103,23 @@ class __$$LayerMenuStateDataImplCopyWithImpl<$Res> /// @nodoc class _$LayerMenuStateDataImpl implements _LayerMenuStateData { - const _$LayerMenuStateDataImpl({required this.isVisible}); + const _$LayerMenuStateDataImpl( + {required this.isVisible, required final List layer}) + : _layer = layer; @override final bool isVisible; + final List _layer; + @override + List get layer { + if (_layer is EqualUnmodifiableListView) return _layer; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_layer); + } @override String toString() { - return 'LayerMenuStateData(isVisible: $isVisible)'; + return 'LayerMenuStateData(isVisible: $isVisible, layer: $layer)'; } @override @@ -108,11 +128,13 @@ class _$LayerMenuStateDataImpl implements _LayerMenuStateData { (other.runtimeType == runtimeType && other is _$LayerMenuStateDataImpl && (identical(other.isVisible, isVisible) || - other.isVisible == isVisible)); + other.isVisible == isVisible) && + const DeepCollectionEquality().equals(other._layer, _layer)); } @override - int get hashCode => Object.hash(runtimeType, isVisible); + int get hashCode => Object.hash( + runtimeType, isVisible, const DeepCollectionEquality().hash(_layer)); @JsonKey(ignore: true) @override @@ -123,12 +145,15 @@ class _$LayerMenuStateDataImpl implements _LayerMenuStateData { } abstract class _LayerMenuStateData implements LayerMenuStateData { - const factory _LayerMenuStateData({required final bool isVisible}) = - _$LayerMenuStateDataImpl; + const factory _LayerMenuStateData( + {required final bool isVisible, + required final List layer}) = _$LayerMenuStateDataImpl; @override bool get isVisible; @override + List get layer; + @override @JsonKey(ignore: true) _$$LayerMenuStateDataImplCopyWith<_$LayerMenuStateDataImpl> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/core/providers/state/layer_menu_state_provider.dart b/lib/core/providers/state/layer_menu_state_provider.dart index b4625ced..3cfe10f3 100644 --- a/lib/core/providers/state/layer_menu_state_provider.dart +++ b/lib/core/providers/state/layer_menu_state_provider.dart @@ -1,4 +1,5 @@ import 'package:paintroid/core/providers/state/layer_menu_state_data.dart'; +import 'package:paintroid/core/providers/state/layer_state_data.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'layer_menu_state_provider.g.dart'; @@ -9,14 +10,54 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { LayerMenuStateData build() { return const LayerMenuStateData( isVisible: false, + layer: [ + LayerStateData(id: 0, isSelected: false), + LayerStateData(id: 1, isSelected: false), + LayerStateData(id: 2, isSelected: false), + LayerStateData(id: 3, isSelected: false), + LayerStateData(id: 4, isSelected: false), + LayerStateData(id: 5, isSelected: false), + LayerStateData(id: 6, isSelected: false), + ], ); } - void toggleVisibility() { - state = state.copyWith(isVisible: !state.isVisible); + void toggleVisibility() => + state = state.copyWith(isVisible: !state.isVisible); + + void hide() => state = state.copyWith(isVisible: false); + + void reorder(int oldIndex, int newIndex) { + List layerList = List.from(state.layer); + if (oldIndex < newIndex) { + newIndex -= 1; + } + final movedLayer = layerList.removeAt(oldIndex); + layerList.insert(newIndex, movedLayer); + state = state.copyWith(layer: layerList); + } + + void toggleSelection(int layerId) { + final updatedLayerList = state.layer.map((layer) { + if (layer.id == layerId) { + return layer.copyWith(isSelected: !layer.isSelected); + } + return layer; + }).toList(); + state = state.copyWith(layer: updatedLayerList); + } + + void addLayer() { + final newLayer = LayerStateData( + id: state.layer.length + 1, + isSelected: false, + ); + state = state.copyWith(layer: [...state.layer, newLayer]); } - void hide() { - state = state.copyWith(isVisible: false); + void deleteLayer() { + final updatedLayerList = + state.layer.where((layer) => !layer.isSelected).toList(); + state = state.copyWith(layer: updatedLayerList); } } diff --git a/lib/core/providers/state/layer_menu_state_provider.g.dart b/lib/core/providers/state/layer_menu_state_provider.g.dart index e906cb19..b301c9d7 100644 --- a/lib/core/providers/state/layer_menu_state_provider.g.dart +++ b/lib/core/providers/state/layer_menu_state_provider.g.dart @@ -7,7 +7,7 @@ part of 'layer_menu_state_provider.dart'; // ************************************************************************** String _$layerMenuStateProviderHash() => - r'1dc3f0fa55df98057c2b981e7b215ccea251542a'; + r'311c058569eb9676b09e9de5c21a44516f2ee183'; /// See also [LayerMenuStateProvider]. @ProviderFor(LayerMenuStateProvider) diff --git a/lib/core/providers/state/layer_state_data.dart b/lib/core/providers/state/layer_state_data.dart new file mode 100644 index 00000000..b03533c7 --- /dev/null +++ b/lib/core/providers/state/layer_state_data.dart @@ -0,0 +1,12 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'layer_state_data.freezed.dart'; + +@immutable +@freezed +class LayerStateData with _$LayerStateData { + const factory LayerStateData({ + required int id, + required bool isSelected, + }) = _LayerStateData; +} diff --git a/lib/core/providers/state/layer_state_data.freezed.dart b/lib/core/providers/state/layer_state_data.freezed.dart new file mode 100644 index 00000000..c03ca7e8 --- /dev/null +++ b/lib/core/providers/state/layer_state_data.freezed.dart @@ -0,0 +1,152 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'layer_state_data.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$LayerStateData { + int get id => throw _privateConstructorUsedError; + bool get isSelected => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $LayerStateDataCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LayerStateDataCopyWith<$Res> { + factory $LayerStateDataCopyWith( + LayerStateData value, $Res Function(LayerStateData) then) = + _$LayerStateDataCopyWithImpl<$Res, LayerStateData>; + @useResult + $Res call({int id, bool isSelected}); +} + +/// @nodoc +class _$LayerStateDataCopyWithImpl<$Res, $Val extends LayerStateData> + implements $LayerStateDataCopyWith<$Res> { + _$LayerStateDataCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? isSelected = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + isSelected: null == isSelected + ? _value.isSelected + : isSelected // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$LayerStateDataImplCopyWith<$Res> + implements $LayerStateDataCopyWith<$Res> { + factory _$$LayerStateDataImplCopyWith(_$LayerStateDataImpl value, + $Res Function(_$LayerStateDataImpl) then) = + __$$LayerStateDataImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int id, bool isSelected}); +} + +/// @nodoc +class __$$LayerStateDataImplCopyWithImpl<$Res> + extends _$LayerStateDataCopyWithImpl<$Res, _$LayerStateDataImpl> + implements _$$LayerStateDataImplCopyWith<$Res> { + __$$LayerStateDataImplCopyWithImpl( + _$LayerStateDataImpl _value, $Res Function(_$LayerStateDataImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? isSelected = null, + }) { + return _then(_$LayerStateDataImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + isSelected: null == isSelected + ? _value.isSelected + : isSelected // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$LayerStateDataImpl implements _LayerStateData { + const _$LayerStateDataImpl({required this.id, required this.isSelected}); + + @override + final int id; + @override + final bool isSelected; + + @override + String toString() { + return 'LayerStateData(id: $id, isSelected: $isSelected)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LayerStateDataImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.isSelected, isSelected) || + other.isSelected == isSelected)); + } + + @override + int get hashCode => Object.hash(runtimeType, id, isSelected); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$LayerStateDataImplCopyWith<_$LayerStateDataImpl> get copyWith => + __$$LayerStateDataImplCopyWithImpl<_$LayerStateDataImpl>( + this, _$identity); +} + +abstract class _LayerStateData implements LayerStateData { + const factory _LayerStateData( + {required final int id, + required final bool isSelected}) = _$LayerStateDataImpl; + + @override + int get id; + @override + bool get isSelected; + @override + @JsonKey(ignore: true) + _$$LayerStateDataImplCopyWith<_$LayerStateDataImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/ui/pages/workspace_page/components/layer_menu/layer.dart b/lib/ui/pages/workspace_page/components/layer_menu/layer.dart new file mode 100644 index 00000000..a25dfbba --- /dev/null +++ b/lib/ui/pages/workspace_page/components/layer_menu/layer.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:paintroid/core/providers/state/layer_menu_state_provider.dart'; + +List customColors = [ + const Color(0xFFFF5733), // Fiery Red + const Color(0xFF33C1FF), // Sky Blue + const Color(0xFF75FF33), // Lime Green + const Color(0xFFFF33A8), // Hot Pink + const Color(0xFF33FFF5), // Aqua + const Color(0xFFFFD133), // Golden Yellow + const Color(0xFF8D33FF), // Deep Purple + const Color(0xFFFF8333), // Vibrant Orange + const Color(0xFF33FF8D), // Mint Green + const Color(0xFF3361FF), // Bright Indigo +]; + +class Layer extends ConsumerWidget { + const Layer({ + super.key, + this.isSelected = false, + required this.id, + }); + + final int id; + final bool isSelected; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return GestureDetector( + onTap: () { + ref.read(layerMenuStateProvider.notifier).toggleSelection(id); + }, + child: Container( + height: 80, + margin: const EdgeInsets.only(bottom: 8), + decoration: BoxDecoration( + color: customColors[id], + borderRadius: BorderRadius.circular(12), + border: isSelected + ? Border.all( + color: Colors.blue, + width: 5.0, + ) + : null, + ), + ), + ); + } +} diff --git a/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart b/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart index 9bd9630a..8d3b0a6a 100644 --- a/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart +++ b/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:paintroid/core/providers/state/layer_menu_state_provider.dart'; +import 'package:paintroid/ui/pages/workspace_page/components/layer_menu/layer.dart'; import 'package:paintroid/ui/shared/fade_in_out_widget.dart'; class LayerMenu extends ConsumerWidget { @@ -11,6 +12,9 @@ class LayerMenu extends ConsumerWidget { final isVisible = ref.watch( layerMenuStateProvider.select((state) => state.isVisible), ); + final layers = ref.watch( + layerMenuStateProvider.select((state) => state.layer), + ); return Positioned( top: 54, bottom: 54, @@ -23,6 +27,45 @@ class LayerMenu extends ConsumerWidget { color: Colors.white, borderRadius: BorderRadius.circular(12), ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: () { + ref.read(layerMenuStateProvider.notifier).addLayer(); + }, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + ref.read(layerMenuStateProvider.notifier).deleteLayer(); + }, + ), + ], + ), + Expanded( + child: ReorderableListView( + padding: const EdgeInsets.all(8), + onReorder: (int oldIndex, int newIndex) { + ref.read(layerMenuStateProvider.notifier).reorder( + oldIndex, + newIndex, + ); + }, + children: List.generate(layers.length, (index) { + return Layer( + id: layers[index].id, + key: ValueKey(layers[index].id), + isSelected: layers[index].isSelected, + ); + }), + ), + ), + ], + ), ), ), ); From 9d428d8c9f42926fdd3cfd5b8a19b9e92615ffab Mon Sep 17 00:00:00 2001 From: Lenkomotive Date: Thu, 10 Oct 2024 22:06:22 +0200 Subject: [PATCH 07/16] PAINTROID-454: Flutter: Add Layers - reorder positions, select, delete, add layer --- .../pages/workspace_page/components/layer_menu/layer_menu.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart b/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart index 8d3b0a6a..a8cf0235 100644 --- a/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart +++ b/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart @@ -24,7 +24,7 @@ class LayerMenu extends ConsumerWidget { child: Container( width: 200, decoration: BoxDecoration( - color: Colors.white, + color: Colors.white.withOpacity(0.7), borderRadius: BorderRadius.circular(12), ), child: Column( From 3dc0ed448812ac6e45acdc105b0350d51742b8a9 Mon Sep 17 00:00:00 2001 From: Lenkomotive Date: Sat, 12 Oct 2024 14:48:00 +0200 Subject: [PATCH 08/16] PAINTROID-454: Flutter: Add Layers - reorder positions, fix some bugs --- .../state/layer_menu_state_provider.dart | 41 ++- .../state/layer_menu_state_provider.g.dart | 2 +- .../providers/state/layer_state_data.dart | 3 +- .../state/layer_state_data.freezed.dart | 40 +-- .../components/layer_menu/layer.dart | 77 +++-- .../components/layer_menu/layer_menu.dart | 24 +- pubspec.lock | 302 ++++++++++-------- pubspec.yaml | 1 + 8 files changed, 293 insertions(+), 197 deletions(-) diff --git a/lib/core/providers/state/layer_menu_state_provider.dart b/lib/core/providers/state/layer_menu_state_provider.dart index 3cfe10f3..716dc824 100644 --- a/lib/core/providers/state/layer_menu_state_provider.dart +++ b/lib/core/providers/state/layer_menu_state_provider.dart @@ -1,23 +1,24 @@ +import 'package:flutter/cupertino.dart'; import 'package:paintroid/core/providers/state/layer_menu_state_data.dart'; import 'package:paintroid/core/providers/state/layer_state_data.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:uuid/uuid.dart'; part 'layer_menu_state_provider.g.dart'; @Riverpod(keepAlive: true) class LayerMenuStateProvider extends _$LayerMenuStateProvider { + final uuid = const Uuid(); + @override LayerMenuStateData build() { - return const LayerMenuStateData( + return LayerMenuStateData( isVisible: false, layer: [ - LayerStateData(id: 0, isSelected: false), - LayerStateData(id: 1, isSelected: false), - LayerStateData(id: 2, isSelected: false), - LayerStateData(id: 3, isSelected: false), - LayerStateData(id: 4, isSelected: false), - LayerStateData(id: 5, isSelected: false), - LayerStateData(id: 6, isSelected: false), + LayerStateData( + key: ValueKey(uuid.v4()), + isSelected: false, + ), ], ); } @@ -37,19 +38,33 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { state = state.copyWith(layer: layerList); } - void toggleSelection(int layerId) { - final updatedLayerList = state.layer.map((layer) { - if (layer.id == layerId) { - return layer.copyWith(isSelected: !layer.isSelected); + void toggleSelection(Key? layerKey) { + final layers = state.layer; + + final selectedCount = layers.where((layer) => layer.isSelected).length; + final unselectedCount = layers.length - selectedCount; + + final updatedLayerList = layers.map((layer) { + if (layer.key == layerKey) { + if (!layer.isSelected) { + if (unselectedCount <= 1) { + return layer; + } else { + return layer.copyWith(isSelected: true); + } + } else { + return layer.copyWith(isSelected: false); + } } return layer; }).toList(); + state = state.copyWith(layer: updatedLayerList); } void addLayer() { final newLayer = LayerStateData( - id: state.layer.length + 1, + key: ValueKey(uuid.v4()), isSelected: false, ); state = state.copyWith(layer: [...state.layer, newLayer]); diff --git a/lib/core/providers/state/layer_menu_state_provider.g.dart b/lib/core/providers/state/layer_menu_state_provider.g.dart index b301c9d7..7ed88c9d 100644 --- a/lib/core/providers/state/layer_menu_state_provider.g.dart +++ b/lib/core/providers/state/layer_menu_state_provider.g.dart @@ -7,7 +7,7 @@ part of 'layer_menu_state_provider.dart'; // ************************************************************************** String _$layerMenuStateProviderHash() => - r'311c058569eb9676b09e9de5c21a44516f2ee183'; + r'e78261920cf20ae52a3349a3efaa6a1f30ced750'; /// See also [LayerMenuStateProvider]. @ProviderFor(LayerMenuStateProvider) diff --git a/lib/core/providers/state/layer_state_data.dart b/lib/core/providers/state/layer_state_data.dart index b03533c7..d9555693 100644 --- a/lib/core/providers/state/layer_state_data.dart +++ b/lib/core/providers/state/layer_state_data.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'layer_state_data.freezed.dart'; @@ -6,7 +7,7 @@ part 'layer_state_data.freezed.dart'; @freezed class LayerStateData with _$LayerStateData { const factory LayerStateData({ - required int id, + required ValueKey key, required bool isSelected, }) = _LayerStateData; } diff --git a/lib/core/providers/state/layer_state_data.freezed.dart b/lib/core/providers/state/layer_state_data.freezed.dart index c03ca7e8..e12e5780 100644 --- a/lib/core/providers/state/layer_state_data.freezed.dart +++ b/lib/core/providers/state/layer_state_data.freezed.dart @@ -16,7 +16,7 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$LayerStateData { - int get id => throw _privateConstructorUsedError; + ValueKey get key => throw _privateConstructorUsedError; bool get isSelected => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -30,7 +30,7 @@ abstract class $LayerStateDataCopyWith<$Res> { LayerStateData value, $Res Function(LayerStateData) then) = _$LayerStateDataCopyWithImpl<$Res, LayerStateData>; @useResult - $Res call({int id, bool isSelected}); + $Res call({ValueKey key, bool isSelected}); } /// @nodoc @@ -46,14 +46,14 @@ class _$LayerStateDataCopyWithImpl<$Res, $Val extends LayerStateData> @pragma('vm:prefer-inline') @override $Res call({ - Object? id = null, + Object? key = null, Object? isSelected = null, }) { return _then(_value.copyWith( - id: null == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as int, + key: null == key + ? _value.key + : key // ignore: cast_nullable_to_non_nullable + as ValueKey, isSelected: null == isSelected ? _value.isSelected : isSelected // ignore: cast_nullable_to_non_nullable @@ -70,7 +70,7 @@ abstract class _$$LayerStateDataImplCopyWith<$Res> __$$LayerStateDataImplCopyWithImpl<$Res>; @override @useResult - $Res call({int id, bool isSelected}); + $Res call({ValueKey key, bool isSelected}); } /// @nodoc @@ -84,14 +84,14 @@ class __$$LayerStateDataImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? id = null, + Object? key = null, Object? isSelected = null, }) { return _then(_$LayerStateDataImpl( - id: null == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as int, + key: null == key + ? _value.key + : key // ignore: cast_nullable_to_non_nullable + as ValueKey, isSelected: null == isSelected ? _value.isSelected : isSelected // ignore: cast_nullable_to_non_nullable @@ -103,16 +103,16 @@ class __$$LayerStateDataImplCopyWithImpl<$Res> /// @nodoc class _$LayerStateDataImpl implements _LayerStateData { - const _$LayerStateDataImpl({required this.id, required this.isSelected}); + const _$LayerStateDataImpl({required this.key, required this.isSelected}); @override - final int id; + final ValueKey key; @override final bool isSelected; @override String toString() { - return 'LayerStateData(id: $id, isSelected: $isSelected)'; + return 'LayerStateData(key: $key, isSelected: $isSelected)'; } @override @@ -120,13 +120,13 @@ class _$LayerStateDataImpl implements _LayerStateData { return identical(this, other) || (other.runtimeType == runtimeType && other is _$LayerStateDataImpl && - (identical(other.id, id) || other.id == id) && + (identical(other.key, key) || other.key == key) && (identical(other.isSelected, isSelected) || other.isSelected == isSelected)); } @override - int get hashCode => Object.hash(runtimeType, id, isSelected); + int get hashCode => Object.hash(runtimeType, key, isSelected); @JsonKey(ignore: true) @override @@ -138,11 +138,11 @@ class _$LayerStateDataImpl implements _LayerStateData { abstract class _LayerStateData implements LayerStateData { const factory _LayerStateData( - {required final int id, + {required final ValueKey key, required final bool isSelected}) = _$LayerStateDataImpl; @override - int get id; + ValueKey get key; @override bool get isSelected; @override diff --git a/lib/ui/pages/workspace_page/components/layer_menu/layer.dart b/lib/ui/pages/workspace_page/components/layer_menu/layer.dart index a25dfbba..ac0d678f 100644 --- a/lib/ui/pages/workspace_page/components/layer_menu/layer.dart +++ b/lib/ui/pages/workspace_page/components/layer_menu/layer.dart @@ -1,48 +1,79 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:paintroid/core/providers/state/layer_menu_state_provider.dart'; +import 'package:paintroid/ui/theme/data/custom_colors.dart'; -List customColors = [ - const Color(0xFFFF5733), // Fiery Red - const Color(0xFF33C1FF), // Sky Blue - const Color(0xFF75FF33), // Lime Green - const Color(0xFFFF33A8), // Hot Pink - const Color(0xFF33FFF5), // Aqua - const Color(0xFFFFD133), // Golden Yellow - const Color(0xFF8D33FF), // Deep Purple - const Color(0xFFFF8333), // Vibrant Orange - const Color(0xFF33FF8D), // Mint Green - const Color(0xFF3361FF), // Bright Indigo -]; +const layerHeight = 180.0; class Layer extends ConsumerWidget { const Layer({ super.key, this.isSelected = false, - required this.id, }); - final int id; final bool isSelected; @override Widget build(BuildContext context, WidgetRef ref) { return GestureDetector( onTap: () { - ref.read(layerMenuStateProvider.notifier).toggleSelection(id); + ref.read(layerMenuStateProvider.notifier).toggleSelection(key); }, child: Container( - height: 80, + height: 180, margin: const EdgeInsets.only(bottom: 8), decoration: BoxDecoration( - color: customColors[id], + color: CustomColors.oceanBlue.withOpacity(isSelected ? 0.5 : 1), borderRadius: BorderRadius.circular(12), - border: isSelected - ? Border.all( - color: Colors.blue, - width: 5.0, - ) - : null, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Column( + children: [ + IconButton( + icon: const Icon(Icons.visibility), + color: Colors.white, + onPressed: () {}, + ), + Expanded( + child: RotatedBox( + quarterTurns: 3, + child: Slider( + value: 0.5, + activeColor: Colors.grey, + inactiveColor: CustomColors.lightGray, + onChanged: (value) {}, + ), + ), + ) + ], + ), + ), + // const Spacer(), + Container( + width: 80, + height: 180 - 32, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + child: Text( + key.toString(), + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + ), + IconButton( + icon: const Icon(Icons.drag_indicator_outlined), + color: Colors.white, + onPressed: () {}, + ), + ], ), ), ); diff --git a/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart b/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart index a8cf0235..bcaacc18 100644 --- a/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart +++ b/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart @@ -15,19 +15,22 @@ class LayerMenu extends ConsumerWidget { final layers = ref.watch( layerMenuStateProvider.select((state) => state.layer), ); - return Positioned( - top: 54, - bottom: 54, - right: 8, + + return Align( + alignment: Alignment.centerRight, child: FadeInOutWidget( isVisible: isVisible, child: Container( width: 200, + constraints: BoxConstraints( + maxHeight: _calculateHeight(layers.length, context), + ), decoration: BoxDecoration( color: Colors.white.withOpacity(0.7), borderRadius: BorderRadius.circular(12), ), child: Column( + mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceAround, @@ -46,9 +49,9 @@ class LayerMenu extends ConsumerWidget { ), ], ), - Expanded( + Flexible( child: ReorderableListView( - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), onReorder: (int oldIndex, int newIndex) { ref.read(layerMenuStateProvider.notifier).reorder( oldIndex, @@ -57,8 +60,7 @@ class LayerMenu extends ConsumerWidget { }, children: List.generate(layers.length, (index) { return Layer( - id: layers[index].id, - key: ValueKey(layers[index].id), + key: layers[index].key, isSelected: layers[index].isSelected, ); }), @@ -70,4 +72,10 @@ class LayerMenu extends ConsumerWidget { ), ); } + + double _calculateHeight(int layerCount, BuildContext context) { + final screenHeight = MediaQuery.of(context).size.height; + final double maxHeight = screenHeight * 0.66; + return (layerCount * layerHeight + 64).clamp(0.0, maxHeight); + } } diff --git a/pubspec.lock b/pubspec.lock index 01c51cd8..e147a59d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,26 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted - version: "61.0.0" + version: "67.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted - version: "5.13.0" + version: "6.4.1" analyzer_plugin: dependency: transitive description: name: analyzer_plugin - sha256: c1d5f167683de03d5ab6c3b53fc9aeefc5d59476e7810ba7bbddff50c6f4392d + sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161" url: "https://pub.dev" source: hosted - version: "0.11.2" + version: "0.11.3" archive: dependency: transitive description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.2" build_resolvers: dependency: transitive description: @@ -93,18 +93,18 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" + sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7" url: "https://pub.dev" source: hosted - version: "2.4.9" + version: "2.4.11" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.3.1" built_collection: dependency: transitive description: @@ -204,50 +204,58 @@ packages: dependency: transitive description: name: cross_file - sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" url: "https://pub.dev" source: hosted - version: "0.3.3+8" + version: "0.3.4+2" crypto: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" custom_lint: dependency: transitive description: name: custom_lint - sha256: "22bd87a362f433ba6aae127a7bac2838645270737f3721b180916d7c5946cb5d" + sha256: "7c0aec12df22f9082146c354692056677f1e70bc43471644d1fdb36c6fdda799" url: "https://pub.dev" source: hosted - version: "0.5.11" + version: "0.6.4" custom_lint_builder: dependency: transitive description: name: custom_lint_builder - sha256: "0d48e002438950f9582e574ef806b2bea5719d8d14c0f9f754fbad729bcf3b19" + sha256: d7dc41e709dde223806660268678be7993559e523eb3164e2a1425fd6f7615a9 url: "https://pub.dev" source: hosted - version: "0.5.14" + version: "0.6.4" custom_lint_core: dependency: transitive description: name: custom_lint_core - sha256: "2952837953022de610dacb464f045594854ced6506ac7f76af28d4a6490e189b" + sha256: a85e8f78f4c52f6c63cdaf8c872eb573db0231dcdf3c3a5906d493c1f8bc20e6 url: "https://pub.dev" source: hosted - version: "0.5.14" + version: "0.6.3" dart_style: dependency: transitive description: name: dart_style - sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.6" + dev_build: + dependency: transitive + description: + name: dev_build + sha256: f526d1fbe68875f6119ffc333f114dfe6aa93ad04439276d53968f7977cc410e + url: "https://pub.dev" + source: hosted + version: "1.0.0+11" device_info_plus: dependency: "direct main" description: @@ -260,10 +268,10 @@ packages: dependency: transitive description: name: device_info_plus_platform_interface - sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 + sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba" url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" equatable: dependency: "direct main" description: @@ -284,10 +292,10 @@ packages: dependency: transitive description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.3" file: dependency: transitive description: @@ -308,18 +316,18 @@ packages: dependency: transitive description: name: file_selector_linux - sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + sha256: "712ce7fab537ba532c8febdb1a8f167b32441e74acd68c3ccb2e36dcb52c4ab2" url: "https://pub.dev" source: hosted - version: "0.9.2+1" + version: "0.9.3" file_selector_macos: dependency: transitive description: name: file_selector_macos - sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" url: "https://pub.dev" source: hosted - version: "0.9.4" + version: "0.9.4+2" file_selector_platform_interface: dependency: transitive description: @@ -332,10 +340,10 @@ packages: dependency: transitive description: name: file_selector_windows - sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 + sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4" url: "https://pub.dev" source: hosted - version: "0.9.3+1" + version: "0.9.3+3" filesize: dependency: "direct main" description: @@ -356,26 +364,34 @@ packages: dependency: "direct main" description: name: floor - sha256: "52a8eac2c8d274e7c0c54251226f59786bb5b749365a2d8537d8095aa5132d92" + sha256: c1b06023912b5b8e49deb6a9d867863c535ae1a232d991c3582bba3ee8687867 url: "https://pub.dev" source: hosted - version: "1.4.2" + version: "1.5.0" floor_annotation: dependency: transitive description: name: floor_annotation - sha256: fa3fa4f198cdd1d922a69ceb06e54663fe59256bf1cb3c036eff206b445a6960 + sha256: a40949580a7ab0eee572686e2d3b1638fd6bd6a753e661d792ab4236b365b23b url: "https://pub.dev" source: hosted - version: "1.4.2" + version: "1.5.0" + floor_common: + dependency: transitive + description: + name: floor_common + sha256: "41c9914862f83a821815e1b1ffd47a1e1ae2130c35ff882ba2d000a67713ba64" + url: "https://pub.dev" + source: hosted + version: "1.5.0" floor_generator: dependency: "direct dev" description: name: floor_generator - sha256: "40aaf1b619adc03367ce4b7c79161e3198d43b572b5ec9cc99a4a89de27b08d2" + sha256: "1499b3ab878a807e6fbe6f140dc014124845cd1df3090a113aae5fa7577a1e77" url: "https://pub.dev" source: hosted - version: "1.4.2" + version: "1.5.0" flutter: dependency: "direct main" description: flutter @@ -419,10 +435,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f" + sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda" url: "https://pub.dev" source: hosted - version: "2.0.19" + version: "2.0.22" flutter_riverpod: dependency: "direct main" description: @@ -461,10 +477,10 @@ packages: dependency: "direct main" description: name: freezed_annotation - sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.4" frontend_server_client: dependency: transitive description: @@ -490,10 +506,10 @@ packages: dependency: transitive description: name: graphs - sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" hotreloader: dependency: transitive description: @@ -506,10 +522,10 @@ packages: dependency: transitive description: name: http - sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.2" http_multi_server: dependency: transitive description: @@ -546,10 +562,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "844c6da4e4f2829dffdab97816bca09d0e0977e8dcef7450864aba4e07967a58" + sha256: "8c5abf0dcc24fe6e8e0b4a5c0b51a5cf30cefdf6407a3213dae61edc75a70f56" url: "https://pub.dev" source: hosted - version: "0.8.9+6" + version: "0.8.12+12" image_picker_for_web: dependency: transitive description: @@ -562,10 +578,10 @@ packages: dependency: transitive description: name: image_picker_ios - sha256: fadafce49e8569257a0cad56d24438a6fa1f0cbd7ee0af9b631f7492818a4ca3 + sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" url: "https://pub.dev" source: hosted - version: "0.8.9+1" + version: "0.8.12" image_picker_linux: dependency: transitive description: @@ -586,10 +602,10 @@ packages: dependency: transitive description: name: image_picker_platform_interface - sha256: fa4e815e6fcada50e35718727d83ba1c92f1edf95c0b4436554cec301b56233b + sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" url: "https://pub.dev" source: hosted - version: "2.9.3" + version: "2.10.0" image_picker_windows: dependency: transitive description: @@ -623,10 +639,10 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.1" json_annotation: dependency: "direct main" description: @@ -727,10 +743,10 @@ packages: dependency: transitive description: name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.0.6" mockito: dependency: "direct dev" description: @@ -799,26 +815,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d + sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.2.10" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -839,10 +855,10 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" permission_handler: dependency: "direct main" description: @@ -923,6 +939,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.2" + process_run: + dependency: transitive + description: + name: process_run + sha256: c917dfb5f7afad4c7485bc00a4df038621248fce046105020cea276d1a87c820 + url: "https://pub.dev" + source: hosted + version: "1.1.0" pub_semver: dependency: transitive description: @@ -951,10 +975,10 @@ packages: dependency: transitive description: name: riverpod_analyzer_utils - sha256: d72d7096964baf288b55619fe48100001fc4564ab7923ed0a7f5c7650e03c0d6 + sha256: "8b71f03fc47ae27d13769496a1746332df4cec43918aeba9aff1e232783a780f" url: "https://pub.dev" source: hosted - version: "0.3.4" + version: "0.5.1" riverpod_annotation: dependency: "direct main" description: @@ -967,18 +991,18 @@ packages: dependency: "direct dev" description: name: riverpod_generator - sha256: "5b36ad2f2b562cffb37212e8d59390b25499bf045b732276e30a207b16a25f61" + sha256: d451608bf17a372025fc36058863737636625dfdb7e3cbf6142e0dfeb366ab22 url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.0" riverpod_lint: dependency: "direct dev" description: name: riverpod_lint - sha256: "70198738c3047ae4f6517ef1a2011a8514a980a52576c7f629a3a08810319a02" + sha256: "3c67c14ccd16f0c9d53e35ef70d06cd9d072e2fb14557326886bbde903b230a5" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.3.10" rxdart: dependency: transitive description: @@ -991,58 +1015,58 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 + sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.2" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" + sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.2" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.5.3" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.1" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.1" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.4.2" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.1" shelf: dependency: transitive description: @@ -1055,10 +1079,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.0.0" sky_engine: dependency: transitive description: flutter @@ -1068,10 +1092,10 @@ packages: dependency: "direct main" description: name: smooth_page_indicator - sha256: "725bc638d5e79df0c84658e1291449996943f93bacbc2cec49963dbbab48d8ae" + sha256: "3b28b0c545fa67ed9e5997d9f9720d486f54c0c607e056a1094544e36934dff3" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0+3" source_gen: dependency: transitive description: @@ -1108,42 +1132,50 @@ packages: dependency: "direct main" description: name: sqflite - sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6 + sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.3+1" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5" + sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4" url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.4" sqflite_common_ffi: dependency: transitive description: name: sqflite_common_ffi - sha256: "754927d82de369a6b9e760fb60640aa81da650f35ffd468d5a992814d6022908" + sha256: "4d6137c29e930d6e4a8ff373989dd9de7bac12e3bc87bce950f6e844e8ad3bb5" url: "https://pub.dev" source: hosted - version: "2.3.2+1" + version: "2.3.3" + sqflite_common_ffi_web: + dependency: transitive + description: + name: sqflite_common_ffi_web + sha256: e9d1cb35a5ff7c43072968ed734e0a1a859564fd2b2c8654e0c6244a57dc82a8 + url: "https://pub.dev" + source: hosted + version: "0.4.4" sqlite3: dependency: transitive description: name: sqlite3 - sha256: "072128763f1547e3e9b4735ce846bfd226d68019ccda54db4cd427b12dfdedc9" + sha256: "45f168ae2213201b54e09429ed0c593dc2c88c924a1488d6f9c523a255d567cb" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.6" sqlparser: dependency: transitive description: name: sqlparser - sha256: "91f47610aa54d8abf9d795a7b4e49b2a788f65d7493d5a68fbf180c3cbcc6f38" + sha256: "7b20045d1ccfb7bc1df7e8f9fee5ae58673fce6ff62cefbb0e0fd7214e90e5a0" url: "https://pub.dev" source: hosted - version: "0.27.0" + version: "0.34.1" stack_trace: dependency: transitive description: @@ -1188,10 +1220,10 @@ packages: dependency: transitive description: name: strings - sha256: "5af86299505c299640f5564e187c1a2ee9d6308c540e8d65f6385f5c67019122" + sha256: "052836499f03897d3860a603b330c1ea3c8a14177b21f34b15a1295f36024aae" url: "https://pub.dev" source: hosted - version: "0.2.2" + version: "3.1.2" sync_http: dependency: transitive description: @@ -1260,42 +1292,42 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.1" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "17cd5e205ea615e2c6ea7a77323a11712dffa0720a8a90540db57a01347f9ad9" + sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79 url: "https://pub.dev" source: hosted - version: "6.3.2" + version: "6.3.9" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e url: "https://pub.dev" source: hosted - version: "6.2.4" + version: "6.3.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.2.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" + sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" url_launcher_platform_interface: dependency: transitive description: @@ -1308,26 +1340,26 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 + sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" uuid: - dependency: transitive + dependency: "direct main" description: name: uuid - sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.5.1" vector_math: dependency: transitive description: @@ -1356,18 +1388,26 @@ packages: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "1.1.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "3.0.1" webdriver: dependency: transitive description: @@ -1380,26 +1420,26 @@ packages: dependency: transitive description: name: win32 - sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" + sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" url: "https://pub.dev" source: hosted - version: "5.2.0" + version: "5.5.4" win32_registry: dependency: transitive description: name: win32_registry - sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a" + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.5" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.0" xml: dependency: transitive description: @@ -1417,5 +1457,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index c267575f..552b38d3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,6 +42,7 @@ dependencies: sqflite: ^2.3.0 colorpicker: path: packages/colorpicker + uuid: ^4.5.1 dev_dependencies: flutter_test: From 6eb0be0fe4298b6ceacdf6f55293576c8e3e352c Mon Sep 17 00:00:00 2001 From: Lenkomotive Date: Sat, 12 Oct 2024 15:26:28 +0200 Subject: [PATCH 09/16] PAINTROID-454: Flutter: Add Layers - add more functionality --- ios/Podfile.lock | 2 +- .../state/layer_menu_state_provider.dart | 26 ++++++++- .../state/layer_menu_state_provider.g.dart | 2 +- .../providers/state/layer_state_data.dart | 2 + .../state/layer_state_data.freezed.dart | 56 ++++++++++++++++--- .../components/bottom_bar/bottom_nav_bar.dart | 2 +- .../components/layer_menu/layer.dart | 20 +++++-- .../components/layer_menu/layer_menu.dart | 2 + 8 files changed, 97 insertions(+), 15 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 7cef8453..565ec9e2 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -135,4 +135,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 303789365c3a8d7bc562e5e65d7e8e15218ec5c6 -COCOAPODS: 1.15.2 +COCOAPODS: 1.15.0 diff --git a/lib/core/providers/state/layer_menu_state_provider.dart b/lib/core/providers/state/layer_menu_state_provider.dart index 716dc824..36fd32cd 100644 --- a/lib/core/providers/state/layer_menu_state_provider.dart +++ b/lib/core/providers/state/layer_menu_state_provider.dart @@ -18,12 +18,14 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { LayerStateData( key: ValueKey(uuid.v4()), isSelected: false, + isVisible: true, + opacity: 1.0, ), ], ); } - void toggleVisibility() => + void toggleMenuVisibility() => state = state.copyWith(isVisible: !state.isVisible); void hide() => state = state.copyWith(isVisible: false); @@ -62,10 +64,32 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { state = state.copyWith(layer: updatedLayerList); } + void toggleLayerVisibility(Key? layerKey) { + final updatedLayerList = state.layer.map((layer) { + if (layer.key == layerKey) { + return layer.copyWith(isVisible: !layer.isVisible); + } + return layer; + }).toList(); + state = state.copyWith(layer: updatedLayerList); + } + + void updateLayerOpacity(Key? layerKey, double opacity) { + final updatedLayerList = state.layer.map((layer) { + if (layer.key == layerKey) { + return layer.copyWith(opacity: opacity); + } + return layer; + }).toList(); + state = state.copyWith(layer: updatedLayerList); + } + void addLayer() { final newLayer = LayerStateData( key: ValueKey(uuid.v4()), isSelected: false, + isVisible: true, + opacity: 1.0, ); state = state.copyWith(layer: [...state.layer, newLayer]); } diff --git a/lib/core/providers/state/layer_menu_state_provider.g.dart b/lib/core/providers/state/layer_menu_state_provider.g.dart index 7ed88c9d..1a7f8636 100644 --- a/lib/core/providers/state/layer_menu_state_provider.g.dart +++ b/lib/core/providers/state/layer_menu_state_provider.g.dart @@ -7,7 +7,7 @@ part of 'layer_menu_state_provider.dart'; // ************************************************************************** String _$layerMenuStateProviderHash() => - r'e78261920cf20ae52a3349a3efaa6a1f30ced750'; + r'3eaab9a0920685912132cf280494901ed234a754'; /// See also [LayerMenuStateProvider]. @ProviderFor(LayerMenuStateProvider) diff --git a/lib/core/providers/state/layer_state_data.dart b/lib/core/providers/state/layer_state_data.dart index d9555693..3cb2c09e 100644 --- a/lib/core/providers/state/layer_state_data.dart +++ b/lib/core/providers/state/layer_state_data.dart @@ -9,5 +9,7 @@ class LayerStateData with _$LayerStateData { const factory LayerStateData({ required ValueKey key, required bool isSelected, + required bool isVisible, + required double opacity, }) = _LayerStateData; } diff --git a/lib/core/providers/state/layer_state_data.freezed.dart b/lib/core/providers/state/layer_state_data.freezed.dart index e12e5780..ca99377f 100644 --- a/lib/core/providers/state/layer_state_data.freezed.dart +++ b/lib/core/providers/state/layer_state_data.freezed.dart @@ -18,6 +18,8 @@ final _privateConstructorUsedError = UnsupportedError( mixin _$LayerStateData { ValueKey get key => throw _privateConstructorUsedError; bool get isSelected => throw _privateConstructorUsedError; + bool get isVisible => throw _privateConstructorUsedError; + double get opacity => throw _privateConstructorUsedError; @JsonKey(ignore: true) $LayerStateDataCopyWith get copyWith => @@ -30,7 +32,8 @@ abstract class $LayerStateDataCopyWith<$Res> { LayerStateData value, $Res Function(LayerStateData) then) = _$LayerStateDataCopyWithImpl<$Res, LayerStateData>; @useResult - $Res call({ValueKey key, bool isSelected}); + $Res call( + {ValueKey key, bool isSelected, bool isVisible, double opacity}); } /// @nodoc @@ -48,6 +51,8 @@ class _$LayerStateDataCopyWithImpl<$Res, $Val extends LayerStateData> $Res call({ Object? key = null, Object? isSelected = null, + Object? isVisible = null, + Object? opacity = null, }) { return _then(_value.copyWith( key: null == key @@ -58,6 +63,14 @@ class _$LayerStateDataCopyWithImpl<$Res, $Val extends LayerStateData> ? _value.isSelected : isSelected // ignore: cast_nullable_to_non_nullable as bool, + isVisible: null == isVisible + ? _value.isVisible + : isVisible // ignore: cast_nullable_to_non_nullable + as bool, + opacity: null == opacity + ? _value.opacity + : opacity // ignore: cast_nullable_to_non_nullable + as double, ) as $Val); } } @@ -70,7 +83,8 @@ abstract class _$$LayerStateDataImplCopyWith<$Res> __$$LayerStateDataImplCopyWithImpl<$Res>; @override @useResult - $Res call({ValueKey key, bool isSelected}); + $Res call( + {ValueKey key, bool isSelected, bool isVisible, double opacity}); } /// @nodoc @@ -86,6 +100,8 @@ class __$$LayerStateDataImplCopyWithImpl<$Res> $Res call({ Object? key = null, Object? isSelected = null, + Object? isVisible = null, + Object? opacity = null, }) { return _then(_$LayerStateDataImpl( key: null == key @@ -96,6 +112,14 @@ class __$$LayerStateDataImplCopyWithImpl<$Res> ? _value.isSelected : isSelected // ignore: cast_nullable_to_non_nullable as bool, + isVisible: null == isVisible + ? _value.isVisible + : isVisible // ignore: cast_nullable_to_non_nullable + as bool, + opacity: null == opacity + ? _value.opacity + : opacity // ignore: cast_nullable_to_non_nullable + as double, )); } } @@ -103,16 +127,24 @@ class __$$LayerStateDataImplCopyWithImpl<$Res> /// @nodoc class _$LayerStateDataImpl implements _LayerStateData { - const _$LayerStateDataImpl({required this.key, required this.isSelected}); + const _$LayerStateDataImpl( + {required this.key, + required this.isSelected, + required this.isVisible, + required this.opacity}); @override final ValueKey key; @override final bool isSelected; + @override + final bool isVisible; + @override + final double opacity; @override String toString() { - return 'LayerStateData(key: $key, isSelected: $isSelected)'; + return 'LayerStateData(key: $key, isSelected: $isSelected, isVisible: $isVisible, opacity: $opacity)'; } @override @@ -122,11 +154,15 @@ class _$LayerStateDataImpl implements _LayerStateData { other is _$LayerStateDataImpl && (identical(other.key, key) || other.key == key) && (identical(other.isSelected, isSelected) || - other.isSelected == isSelected)); + other.isSelected == isSelected) && + (identical(other.isVisible, isVisible) || + other.isVisible == isVisible) && + (identical(other.opacity, opacity) || other.opacity == opacity)); } @override - int get hashCode => Object.hash(runtimeType, key, isSelected); + int get hashCode => + Object.hash(runtimeType, key, isSelected, isVisible, opacity); @JsonKey(ignore: true) @override @@ -139,13 +175,19 @@ class _$LayerStateDataImpl implements _LayerStateData { abstract class _LayerStateData implements LayerStateData { const factory _LayerStateData( {required final ValueKey key, - required final bool isSelected}) = _$LayerStateDataImpl; + required final bool isSelected, + required final bool isVisible, + required final double opacity}) = _$LayerStateDataImpl; @override ValueKey get key; @override bool get isSelected; @override + bool get isVisible; + @override + double get opacity; + @override @JsonKey(ignore: true) _$$LayerStateDataImplCopyWith<_$LayerStateDataImpl> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/ui/pages/workspace_page/components/bottom_bar/bottom_nav_bar.dart b/lib/ui/pages/workspace_page/components/bottom_bar/bottom_nav_bar.dart index 4f408397..37ed08c4 100644 --- a/lib/ui/pages/workspace_page/components/bottom_bar/bottom_nav_bar.dart +++ b/lib/ui/pages/workspace_page/components/bottom_bar/bottom_nav_bar.dart @@ -137,6 +137,6 @@ class BottomNavBar extends ConsumerWidget { } void _showLayerMenu(WidgetRef ref) { - ref.read(layerMenuStateProvider.notifier).toggleVisibility(); + ref.read(layerMenuStateProvider.notifier).toggleMenuVisibility(); } } diff --git a/lib/ui/pages/workspace_page/components/layer_menu/layer.dart b/lib/ui/pages/workspace_page/components/layer_menu/layer.dart index ac0d678f..79559106 100644 --- a/lib/ui/pages/workspace_page/components/layer_menu/layer.dart +++ b/lib/ui/pages/workspace_page/components/layer_menu/layer.dart @@ -9,9 +9,13 @@ class Layer extends ConsumerWidget { const Layer({ super.key, this.isSelected = false, + this.isVisible = true, + this.opacity = 1.0, }); final bool isSelected; + final bool isVisible; + final double opacity; @override Widget build(BuildContext context, WidgetRef ref) { @@ -34,17 +38,25 @@ class Layer extends ConsumerWidget { children: [ IconButton( icon: const Icon(Icons.visibility), - color: Colors.white, - onPressed: () {}, + color: isVisible ? Colors.yellow : Colors.white, + onPressed: () { + ref + .read(layerMenuStateProvider.notifier) + .toggleLayerVisibility(key); + }, ), Expanded( child: RotatedBox( quarterTurns: 3, child: Slider( - value: 0.5, + value: opacity, activeColor: Colors.grey, inactiveColor: CustomColors.lightGray, - onChanged: (value) {}, + onChanged: (value) { + ref + .read(layerMenuStateProvider.notifier) + .updateLayerOpacity(key, value); + }, ), ), ) diff --git a/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart b/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart index bcaacc18..1cb57666 100644 --- a/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart +++ b/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart @@ -62,6 +62,8 @@ class LayerMenu extends ConsumerWidget { return Layer( key: layers[index].key, isSelected: layers[index].isSelected, + isVisible: layers[index].isVisible, + opacity: layers[index].opacity, ); }), ), From 8190c0358a98bbe613dda0a1ca14d8f089d403cd Mon Sep 17 00:00:00 2001 From: Lenkomotive Date: Sat, 19 Oct 2024 17:20:56 +0200 Subject: [PATCH 10/16] PAINTROID-454: Flutter: Add Layers - select only one --- .../state/layer_menu_state_data.freezed.dart | 17 +++++--- .../state/layer_menu_state_provider.dart | 43 +++++++++---------- .../components/layer_menu/layer_menu.dart | 3 +- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/lib/core/providers/state/layer_menu_state_data.freezed.dart b/lib/core/providers/state/layer_menu_state_data.freezed.dart index 02214554..6f8b938d 100644 --- a/lib/core/providers/state/layer_menu_state_data.freezed.dart +++ b/lib/core/providers/state/layer_menu_state_data.freezed.dart @@ -17,7 +17,8 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$LayerMenuStateData { bool get isVisible => throw _privateConstructorUsedError; - List get layer => throw _privateConstructorUsedError; + + List get layers => throw _privateConstructorUsedError; @JsonKey(ignore: true) $LayerMenuStateDataCopyWith get copyWith => @@ -29,6 +30,7 @@ abstract class $LayerMenuStateDataCopyWith<$Res> { factory $LayerMenuStateDataCopyWith( LayerMenuStateData value, $Res Function(LayerMenuStateData) then) = _$LayerMenuStateDataCopyWithImpl<$Res, LayerMenuStateData>; + @useResult $Res call({bool isVisible, List layer}); } @@ -40,6 +42,7 @@ class _$LayerMenuStateDataCopyWithImpl<$Res, $Val extends LayerMenuStateData> // ignore: unused_field final $Val _value; + // ignore: unused_field final $Res Function($Val) _then; @@ -55,7 +58,7 @@ class _$LayerMenuStateDataCopyWithImpl<$Res, $Val extends LayerMenuStateData> : isVisible // ignore: cast_nullable_to_non_nullable as bool, layer: null == layer - ? _value.layer + ? _value.layers : layer // ignore: cast_nullable_to_non_nullable as List, ) as $Val); @@ -68,6 +71,7 @@ abstract class _$$LayerMenuStateDataImplCopyWith<$Res> factory _$$LayerMenuStateDataImplCopyWith(_$LayerMenuStateDataImpl value, $Res Function(_$LayerMenuStateDataImpl) then) = __$$LayerMenuStateDataImplCopyWithImpl<$Res>; + @override @useResult $Res call({bool isVisible, List layer}); @@ -110,8 +114,9 @@ class _$LayerMenuStateDataImpl implements _LayerMenuStateData { @override final bool isVisible; final List _layer; + @override - List get layer { + List get layers { if (_layer is EqualUnmodifiableListView) return _layer; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_layer); @@ -119,7 +124,7 @@ class _$LayerMenuStateDataImpl implements _LayerMenuStateData { @override String toString() { - return 'LayerMenuStateData(isVisible: $isVisible, layer: $layer)'; + return 'LayerMenuStateData(isVisible: $isVisible, layer: $layers)'; } @override @@ -151,8 +156,10 @@ abstract class _LayerMenuStateData implements LayerMenuStateData { @override bool get isVisible; + @override - List get layer; + List get layers; + @override @JsonKey(ignore: true) _$$LayerMenuStateDataImplCopyWith<_$LayerMenuStateDataImpl> get copyWith => diff --git a/lib/core/providers/state/layer_menu_state_provider.dart b/lib/core/providers/state/layer_menu_state_provider.dart index 36fd32cd..bf4a5591 100644 --- a/lib/core/providers/state/layer_menu_state_provider.dart +++ b/lib/core/providers/state/layer_menu_state_provider.dart @@ -17,7 +17,7 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { layer: [ LayerStateData( key: ValueKey(uuid.v4()), - isSelected: false, + isSelected: true, isVisible: true, opacity: 1.0, ), @@ -31,7 +31,7 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { void hide() => state = state.copyWith(isVisible: false); void reorder(int oldIndex, int newIndex) { - List layerList = List.from(state.layer); + List layerList = List.from(state.layers); if (oldIndex < newIndex) { newIndex -= 1; } @@ -41,31 +41,18 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { } void toggleSelection(Key? layerKey) { - final layers = state.layer; - - final selectedCount = layers.where((layer) => layer.isSelected).length; - final unselectedCount = layers.length - selectedCount; - - final updatedLayerList = layers.map((layer) { + final updatedLayerList = state.layers.map((layer) { if (layer.key == layerKey) { - if (!layer.isSelected) { - if (unselectedCount <= 1) { - return layer; - } else { - return layer.copyWith(isSelected: true); - } - } else { - return layer.copyWith(isSelected: false); - } + return layer.copyWith(isSelected: true); } - return layer; + return layer.copyWith(isSelected: false); }).toList(); state = state.copyWith(layer: updatedLayerList); } void toggleLayerVisibility(Key? layerKey) { - final updatedLayerList = state.layer.map((layer) { + final updatedLayerList = state.layers.map((layer) { if (layer.key == layerKey) { return layer.copyWith(isVisible: !layer.isVisible); } @@ -75,7 +62,7 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { } void updateLayerOpacity(Key? layerKey, double opacity) { - final updatedLayerList = state.layer.map((layer) { + final updatedLayerList = state.layers.map((layer) { if (layer.key == layerKey) { return layer.copyWith(opacity: opacity); } @@ -85,18 +72,28 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { } void addLayer() { + // deselect all layers + final updatedLayerList = state.layers.map((layer) { + return layer.copyWith(isSelected: false); + }).toList(); final newLayer = LayerStateData( key: ValueKey(uuid.v4()), - isSelected: false, + isSelected: true, isVisible: true, opacity: 1.0, ); - state = state.copyWith(layer: [...state.layer, newLayer]); + updatedLayerList.add(newLayer); + state = state.copyWith(layer: updatedLayerList); } void deleteLayer() { + if (state.layers.length == 1) return; final updatedLayerList = - state.layer.where((layer) => !layer.isSelected).toList(); + state.layers.where((layer) => !layer.isSelected).toList(); + final lastIndex = updatedLayerList.length - 1; + updatedLayerList[lastIndex] = + updatedLayerList[lastIndex].copyWith(isSelected: true); + state = state.copyWith(layer: updatedLayerList); } } diff --git a/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart b/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart index 1cb57666..554f981b 100644 --- a/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart +++ b/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart @@ -13,7 +13,7 @@ class LayerMenu extends ConsumerWidget { layerMenuStateProvider.select((state) => state.isVisible), ); final layers = ref.watch( - layerMenuStateProvider.select((state) => state.layer), + layerMenuStateProvider.select((state) => state.layers), ); return Align( @@ -51,6 +51,7 @@ class LayerMenu extends ConsumerWidget { ), Flexible( child: ReorderableListView( + reverse: true, padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), onReorder: (int oldIndex, int newIndex) { ref.read(layerMenuStateProvider.notifier).reorder( From 4eec72b761593c19b3e8bd7c68738c3de3bde303 Mon Sep 17 00:00:00 2001 From: Lenkomotive Date: Sat, 19 Oct 2024 17:24:07 +0200 Subject: [PATCH 11/16] PAINTROID-454: Flutter: Add Layers - add image to layer --- .../state/layer_menu_state_data.dart | 2 +- .../state/layer_menu_state_data.freezed.dart | 43 ++++++++----------- .../state/layer_menu_state_provider.dart | 14 +++--- .../state/layer_menu_state_provider.g.dart | 2 +- .../providers/state/layer_state_data.dart | 3 ++ .../state/layer_state_data.freezed.dart | 40 ++++++++++++++--- .../components/layer_menu/layer.dart | 2 +- 7 files changed, 64 insertions(+), 42 deletions(-) diff --git a/lib/core/providers/state/layer_menu_state_data.dart b/lib/core/providers/state/layer_menu_state_data.dart index e0e8b1a3..3019fed8 100644 --- a/lib/core/providers/state/layer_menu_state_data.dart +++ b/lib/core/providers/state/layer_menu_state_data.dart @@ -8,6 +8,6 @@ part 'layer_menu_state_data.freezed.dart'; class LayerMenuStateData with _$LayerMenuStateData { const factory LayerMenuStateData({ required bool isVisible, - required List layer, + required List layers, }) = _LayerMenuStateData; } diff --git a/lib/core/providers/state/layer_menu_state_data.freezed.dart b/lib/core/providers/state/layer_menu_state_data.freezed.dart index 6f8b938d..5d159e83 100644 --- a/lib/core/providers/state/layer_menu_state_data.freezed.dart +++ b/lib/core/providers/state/layer_menu_state_data.freezed.dart @@ -17,7 +17,6 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$LayerMenuStateData { bool get isVisible => throw _privateConstructorUsedError; - List get layers => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -30,9 +29,8 @@ abstract class $LayerMenuStateDataCopyWith<$Res> { factory $LayerMenuStateDataCopyWith( LayerMenuStateData value, $Res Function(LayerMenuStateData) then) = _$LayerMenuStateDataCopyWithImpl<$Res, LayerMenuStateData>; - @useResult - $Res call({bool isVisible, List layer}); + $Res call({bool isVisible, List layers}); } /// @nodoc @@ -42,7 +40,6 @@ class _$LayerMenuStateDataCopyWithImpl<$Res, $Val extends LayerMenuStateData> // ignore: unused_field final $Val _value; - // ignore: unused_field final $Res Function($Val) _then; @@ -50,16 +47,16 @@ class _$LayerMenuStateDataCopyWithImpl<$Res, $Val extends LayerMenuStateData> @override $Res call({ Object? isVisible = null, - Object? layer = null, + Object? layers = null, }) { return _then(_value.copyWith( isVisible: null == isVisible ? _value.isVisible : isVisible // ignore: cast_nullable_to_non_nullable as bool, - layer: null == layer + layers: null == layers ? _value.layers - : layer // ignore: cast_nullable_to_non_nullable + : layers // ignore: cast_nullable_to_non_nullable as List, ) as $Val); } @@ -71,10 +68,9 @@ abstract class _$$LayerMenuStateDataImplCopyWith<$Res> factory _$$LayerMenuStateDataImplCopyWith(_$LayerMenuStateDataImpl value, $Res Function(_$LayerMenuStateDataImpl) then) = __$$LayerMenuStateDataImplCopyWithImpl<$Res>; - @override @useResult - $Res call({bool isVisible, List layer}); + $Res call({bool isVisible, List layers}); } /// @nodoc @@ -89,16 +85,16 @@ class __$$LayerMenuStateDataImplCopyWithImpl<$Res> @override $Res call({ Object? isVisible = null, - Object? layer = null, + Object? layers = null, }) { return _then(_$LayerMenuStateDataImpl( isVisible: null == isVisible ? _value.isVisible : isVisible // ignore: cast_nullable_to_non_nullable as bool, - layer: null == layer - ? _value._layer - : layer // ignore: cast_nullable_to_non_nullable + layers: null == layers + ? _value._layers + : layers // ignore: cast_nullable_to_non_nullable as List, )); } @@ -108,23 +104,22 @@ class __$$LayerMenuStateDataImplCopyWithImpl<$Res> class _$LayerMenuStateDataImpl implements _LayerMenuStateData { const _$LayerMenuStateDataImpl( - {required this.isVisible, required final List layer}) - : _layer = layer; + {required this.isVisible, required final List layers}) + : _layers = layers; @override final bool isVisible; - final List _layer; - + final List _layers; @override List get layers { - if (_layer is EqualUnmodifiableListView) return _layer; + if (_layers is EqualUnmodifiableListView) return _layers; // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_layer); + return EqualUnmodifiableListView(_layers); } @override String toString() { - return 'LayerMenuStateData(isVisible: $isVisible, layer: $layers)'; + return 'LayerMenuStateData(isVisible: $isVisible, layers: $layers)'; } @override @@ -134,12 +129,12 @@ class _$LayerMenuStateDataImpl implements _LayerMenuStateData { other is _$LayerMenuStateDataImpl && (identical(other.isVisible, isVisible) || other.isVisible == isVisible) && - const DeepCollectionEquality().equals(other._layer, _layer)); + const DeepCollectionEquality().equals(other._layers, _layers)); } @override int get hashCode => Object.hash( - runtimeType, isVisible, const DeepCollectionEquality().hash(_layer)); + runtimeType, isVisible, const DeepCollectionEquality().hash(_layers)); @JsonKey(ignore: true) @override @@ -152,14 +147,12 @@ class _$LayerMenuStateDataImpl implements _LayerMenuStateData { abstract class _LayerMenuStateData implements LayerMenuStateData { const factory _LayerMenuStateData( {required final bool isVisible, - required final List layer}) = _$LayerMenuStateDataImpl; + required final List layers}) = _$LayerMenuStateDataImpl; @override bool get isVisible; - @override List get layers; - @override @JsonKey(ignore: true) _$$LayerMenuStateDataImplCopyWith<_$LayerMenuStateDataImpl> get copyWith => diff --git a/lib/core/providers/state/layer_menu_state_provider.dart b/lib/core/providers/state/layer_menu_state_provider.dart index bf4a5591..6678af72 100644 --- a/lib/core/providers/state/layer_menu_state_provider.dart +++ b/lib/core/providers/state/layer_menu_state_provider.dart @@ -14,7 +14,7 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { LayerMenuStateData build() { return LayerMenuStateData( isVisible: false, - layer: [ + layers: [ LayerStateData( key: ValueKey(uuid.v4()), isSelected: true, @@ -37,7 +37,7 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { } final movedLayer = layerList.removeAt(oldIndex); layerList.insert(newIndex, movedLayer); - state = state.copyWith(layer: layerList); + state = state.copyWith(layers: layerList); } void toggleSelection(Key? layerKey) { @@ -48,7 +48,7 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { return layer.copyWith(isSelected: false); }).toList(); - state = state.copyWith(layer: updatedLayerList); + state = state.copyWith(layers: updatedLayerList); } void toggleLayerVisibility(Key? layerKey) { @@ -58,7 +58,7 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { } return layer; }).toList(); - state = state.copyWith(layer: updatedLayerList); + state = state.copyWith(layers: updatedLayerList); } void updateLayerOpacity(Key? layerKey, double opacity) { @@ -68,7 +68,7 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { } return layer; }).toList(); - state = state.copyWith(layer: updatedLayerList); + state = state.copyWith(layers: updatedLayerList); } void addLayer() { @@ -83,7 +83,7 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { opacity: 1.0, ); updatedLayerList.add(newLayer); - state = state.copyWith(layer: updatedLayerList); + state = state.copyWith(layers: updatedLayerList); } void deleteLayer() { @@ -94,6 +94,6 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { updatedLayerList[lastIndex] = updatedLayerList[lastIndex].copyWith(isSelected: true); - state = state.copyWith(layer: updatedLayerList); + state = state.copyWith(layers: updatedLayerList); } } diff --git a/lib/core/providers/state/layer_menu_state_provider.g.dart b/lib/core/providers/state/layer_menu_state_provider.g.dart index 1a7f8636..65567ad5 100644 --- a/lib/core/providers/state/layer_menu_state_provider.g.dart +++ b/lib/core/providers/state/layer_menu_state_provider.g.dart @@ -7,7 +7,7 @@ part of 'layer_menu_state_provider.dart'; // ************************************************************************** String _$layerMenuStateProviderHash() => - r'3eaab9a0920685912132cf280494901ed234a754'; + r'8f6b852570118fb777ad0bd0069dc93ec4027637'; /// See also [LayerMenuStateProvider]. @ProviderFor(LayerMenuStateProvider) diff --git a/lib/core/providers/state/layer_state_data.dart b/lib/core/providers/state/layer_state_data.dart index 3cb2c09e..9ee53db8 100644 --- a/lib/core/providers/state/layer_state_data.dart +++ b/lib/core/providers/state/layer_state_data.dart @@ -1,3 +1,5 @@ +import 'dart:ui' as ui; + import 'package:flutter/cupertino.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -11,5 +13,6 @@ class LayerStateData with _$LayerStateData { required bool isSelected, required bool isVisible, required double opacity, + ui.Image? image, }) = _LayerStateData; } diff --git a/lib/core/providers/state/layer_state_data.freezed.dart b/lib/core/providers/state/layer_state_data.freezed.dart index ca99377f..1bad662a 100644 --- a/lib/core/providers/state/layer_state_data.freezed.dart +++ b/lib/core/providers/state/layer_state_data.freezed.dart @@ -20,6 +20,7 @@ mixin _$LayerStateData { bool get isSelected => throw _privateConstructorUsedError; bool get isVisible => throw _privateConstructorUsedError; double get opacity => throw _privateConstructorUsedError; + ui.Image? get image => throw _privateConstructorUsedError; @JsonKey(ignore: true) $LayerStateDataCopyWith get copyWith => @@ -33,7 +34,11 @@ abstract class $LayerStateDataCopyWith<$Res> { _$LayerStateDataCopyWithImpl<$Res, LayerStateData>; @useResult $Res call( - {ValueKey key, bool isSelected, bool isVisible, double opacity}); + {ValueKey key, + bool isSelected, + bool isVisible, + double opacity, + ui.Image? image}); } /// @nodoc @@ -53,6 +58,7 @@ class _$LayerStateDataCopyWithImpl<$Res, $Val extends LayerStateData> Object? isSelected = null, Object? isVisible = null, Object? opacity = null, + Object? image = freezed, }) { return _then(_value.copyWith( key: null == key @@ -71,6 +77,10 @@ class _$LayerStateDataCopyWithImpl<$Res, $Val extends LayerStateData> ? _value.opacity : opacity // ignore: cast_nullable_to_non_nullable as double, + image: freezed == image + ? _value.image + : image // ignore: cast_nullable_to_non_nullable + as ui.Image?, ) as $Val); } } @@ -84,7 +94,11 @@ abstract class _$$LayerStateDataImplCopyWith<$Res> @override @useResult $Res call( - {ValueKey key, bool isSelected, bool isVisible, double opacity}); + {ValueKey key, + bool isSelected, + bool isVisible, + double opacity, + ui.Image? image}); } /// @nodoc @@ -102,6 +116,7 @@ class __$$LayerStateDataImplCopyWithImpl<$Res> Object? isSelected = null, Object? isVisible = null, Object? opacity = null, + Object? image = freezed, }) { return _then(_$LayerStateDataImpl( key: null == key @@ -120,6 +135,10 @@ class __$$LayerStateDataImplCopyWithImpl<$Res> ? _value.opacity : opacity // ignore: cast_nullable_to_non_nullable as double, + image: freezed == image + ? _value.image + : image // ignore: cast_nullable_to_non_nullable + as ui.Image?, )); } } @@ -131,7 +150,8 @@ class _$LayerStateDataImpl implements _LayerStateData { {required this.key, required this.isSelected, required this.isVisible, - required this.opacity}); + required this.opacity, + this.image}); @override final ValueKey key; @@ -141,10 +161,12 @@ class _$LayerStateDataImpl implements _LayerStateData { final bool isVisible; @override final double opacity; + @override + final ui.Image? image; @override String toString() { - return 'LayerStateData(key: $key, isSelected: $isSelected, isVisible: $isVisible, opacity: $opacity)'; + return 'LayerStateData(key: $key, isSelected: $isSelected, isVisible: $isVisible, opacity: $opacity, image: $image)'; } @override @@ -157,12 +179,13 @@ class _$LayerStateDataImpl implements _LayerStateData { other.isSelected == isSelected) && (identical(other.isVisible, isVisible) || other.isVisible == isVisible) && - (identical(other.opacity, opacity) || other.opacity == opacity)); + (identical(other.opacity, opacity) || other.opacity == opacity) && + (identical(other.image, image) || other.image == image)); } @override int get hashCode => - Object.hash(runtimeType, key, isSelected, isVisible, opacity); + Object.hash(runtimeType, key, isSelected, isVisible, opacity, image); @JsonKey(ignore: true) @override @@ -177,7 +200,8 @@ abstract class _LayerStateData implements LayerStateData { {required final ValueKey key, required final bool isSelected, required final bool isVisible, - required final double opacity}) = _$LayerStateDataImpl; + required final double opacity, + final ui.Image? image}) = _$LayerStateDataImpl; @override ValueKey get key; @@ -188,6 +212,8 @@ abstract class _LayerStateData implements LayerStateData { @override double get opacity; @override + ui.Image? get image; + @override @JsonKey(ignore: true) _$$LayerStateDataImplCopyWith<_$LayerStateDataImpl> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/ui/pages/workspace_page/components/layer_menu/layer.dart b/lib/ui/pages/workspace_page/components/layer_menu/layer.dart index 79559106..d15f4357 100644 --- a/lib/ui/pages/workspace_page/components/layer_menu/layer.dart +++ b/lib/ui/pages/workspace_page/components/layer_menu/layer.dart @@ -74,7 +74,7 @@ class Layer extends ConsumerWidget { child: Text( key.toString(), textAlign: TextAlign.center, - style: TextStyle( + style: const TextStyle( color: Colors.black, fontWeight: FontWeight.bold, ), From d5297c0ddfc3484e991df4834780919c82d57106 Mon Sep 17 00:00:00 2001 From: Lenkomotive Date: Sat, 19 Oct 2024 18:35:09 +0200 Subject: [PATCH 12/16] PAINTROID-454: Flutter: Add Layers - combining all images --- .../state/canvas_state_provider.dart | 21 ++++++++--- .../state/layer_menu_state_provider.dart | 37 ++++++++++++++++++- .../components/layer_menu/layer.dart | 14 +++---- .../components/layer_menu/layer_menu.dart | 1 + 4 files changed, 59 insertions(+), 14 deletions(-) diff --git a/lib/core/providers/state/canvas_state_provider.dart b/lib/core/providers/state/canvas_state_provider.dart index de139f2f..f8c58d54 100644 --- a/lib/core/providers/state/canvas_state_provider.dart +++ b/lib/core/providers/state/canvas_state_provider.dart @@ -2,14 +2,13 @@ import 'dart:ui'; import 'package:flutter/painting.dart'; import 'package:flutter/widgets.dart' as widgets; - -import 'package:riverpod_annotation/riverpod_annotation.dart'; - import 'package:paintroid/core/commands/command_implementation/command.dart'; import 'package:paintroid/core/commands/command_manager/command_manager_provider.dart'; import 'package:paintroid/core/commands/graphic_factory/graphic_factory_provider.dart'; import 'package:paintroid/core/providers/object/device_service.dart'; import 'package:paintroid/core/providers/state/canvas_state_data.dart'; +import 'package:paintroid/core/providers/state/layer_menu_state_provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'canvas_state_provider.g.dart'; @@ -47,11 +46,15 @@ class CanvasStateProvider extends _$CanvasStateProvider { final canvas = state.graphicFactory.createCanvasWithRecorder(recorder); final size = state.size; final bounds = Rect.fromLTWH(0, 0, size.width, size.height); - if (state.cachedImage != null) { + + final currentLayer = + ref.read(layerMenuStateProvider.notifier).getSelectedLayer(); + + if (currentLayer.image != null) { paintImage( canvas: canvas, rect: bounds, - image: state.cachedImage!, + image: currentLayer.image!, fit: BoxFit.fill, filterQuality: FilterQuality.none, ); @@ -60,7 +63,13 @@ class CanvasStateProvider extends _$CanvasStateProvider { state.commandManager.executeLastCommand(canvas); final picture = recorder.endRecording(); final img = await picture.toImage(size.width.toInt(), size.height.toInt()); - state = state.copyWith(cachedImage: img); + ref.read(layerMenuStateProvider.notifier).updateImageOfLayer(img); + + final mergedImage = await ref + .read(layerMenuStateProvider.notifier) + .getMergedImageOfVisibleLayers(img); + + state = state.copyWith(cachedImage: mergedImage); } Future resetCanvasWithNewCommands(Iterable commands) async { diff --git a/lib/core/providers/state/layer_menu_state_provider.dart b/lib/core/providers/state/layer_menu_state_provider.dart index 6678af72..efdc7c92 100644 --- a/lib/core/providers/state/layer_menu_state_provider.dart +++ b/lib/core/providers/state/layer_menu_state_provider.dart @@ -1,4 +1,8 @@ +import 'dart:ui' as ui; + import 'package:flutter/cupertino.dart'; +import 'package:paintroid/core/commands/graphic_factory/graphic_factory_provider.dart'; +import 'package:paintroid/core/providers/state/canvas_state_provider.dart'; import 'package:paintroid/core/providers/state/layer_menu_state_data.dart'; import 'package:paintroid/core/providers/state/layer_state_data.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -59,6 +63,7 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { return layer; }).toList(); state = state.copyWith(layers: updatedLayerList); + ref.read(canvasStateProvider.notifier).updateCachedImage(); } void updateLayerOpacity(Key? layerKey, double opacity) { @@ -69,10 +74,10 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { return layer; }).toList(); state = state.copyWith(layers: updatedLayerList); + ref.read(canvasStateProvider.notifier).updateCachedImage(); } void addLayer() { - // deselect all layers final updatedLayerList = state.layers.map((layer) { return layer.copyWith(isSelected: false); }).toList(); @@ -95,5 +100,35 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { updatedLayerList[lastIndex].copyWith(isSelected: true); state = state.copyWith(layers: updatedLayerList); + ref.read(canvasStateProvider.notifier).updateCachedImage(); + } + + LayerStateData getSelectedLayer() => + state.layers.firstWhere((layer) => layer.isSelected); + + void updateImageOfLayer(ui.Image image) { + final updatedLayerList = state.layers.map((layer) { + if (layer.isSelected) { + return layer.copyWith(image: image); + } + return layer; + }).toList(); + state = state.copyWith(layers: updatedLayerList); + } + + Future getMergedImageOfVisibleLayers(ui.Image image) async { + final recorder = ref.read(graphicFactoryProvider).createPictureRecorder(); + final canvas = + ref.read(graphicFactoryProvider).createCanvasWithRecorder(recorder); + + for (final layer in state.layers) { + final image = layer.image; + if (layer.isVisible && image != null) { + canvas.drawImage(image, Offset.zero, Paint()); + } + } + + final picture = recorder.endRecording(); + return await picture.toImage(image.width, image.height); } } diff --git a/lib/ui/pages/workspace_page/components/layer_menu/layer.dart b/lib/ui/pages/workspace_page/components/layer_menu/layer.dart index d15f4357..36433df1 100644 --- a/lib/ui/pages/workspace_page/components/layer_menu/layer.dart +++ b/lib/ui/pages/workspace_page/components/layer_menu/layer.dart @@ -1,3 +1,5 @@ +import 'dart:ui' as ui; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:paintroid/core/providers/state/layer_menu_state_provider.dart'; @@ -11,11 +13,13 @@ class Layer extends ConsumerWidget { this.isSelected = false, this.isVisible = true, this.opacity = 1.0, + this.image, }); final bool isSelected; final bool isVisible; final double opacity; + final ui.Image? image; @override Widget build(BuildContext context, WidgetRef ref) { @@ -71,13 +75,9 @@ class Layer extends ConsumerWidget { color: Colors.white, borderRadius: BorderRadius.circular(12), ), - child: Text( - key.toString(), - textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - ), + child: RawImage( + image: image, + fit: BoxFit.contain, ), ), IconButton( diff --git a/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart b/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart index 554f981b..a2f157d6 100644 --- a/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart +++ b/lib/ui/pages/workspace_page/components/layer_menu/layer_menu.dart @@ -65,6 +65,7 @@ class LayerMenu extends ConsumerWidget { isSelected: layers[index].isSelected, isVisible: layers[index].isVisible, opacity: layers[index].opacity, + image: layers[index].image, ); }), ), From f15d742dfa1da37332c92df8ba13378cded3879f Mon Sep 17 00:00:00 2001 From: Lenkomotive Date: Fri, 24 Jan 2025 15:12:04 +0100 Subject: [PATCH 13/16] PAINTROID-454: Flutter: Add Layers - revert layer combination --- .../providers/state/canvas_state_provider.dart | 18 ++++-------------- .../drawing_surface/drawing_canvas.dart | 2 -- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/lib/core/providers/state/canvas_state_provider.dart b/lib/core/providers/state/canvas_state_provider.dart index f8c58d54..272cbe1f 100644 --- a/lib/core/providers/state/canvas_state_provider.dart +++ b/lib/core/providers/state/canvas_state_provider.dart @@ -7,7 +7,6 @@ import 'package:paintroid/core/commands/command_manager/command_manager_provider import 'package:paintroid/core/commands/graphic_factory/graphic_factory_provider.dart'; import 'package:paintroid/core/providers/object/device_service.dart'; import 'package:paintroid/core/providers/state/canvas_state_data.dart'; -import 'package:paintroid/core/providers/state/layer_menu_state_provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'canvas_state_provider.g.dart'; @@ -46,15 +45,11 @@ class CanvasStateProvider extends _$CanvasStateProvider { final canvas = state.graphicFactory.createCanvasWithRecorder(recorder); final size = state.size; final bounds = Rect.fromLTWH(0, 0, size.width, size.height); - - final currentLayer = - ref.read(layerMenuStateProvider.notifier).getSelectedLayer(); - - if (currentLayer.image != null) { + if (state.cachedImage != null) { paintImage( canvas: canvas, rect: bounds, - image: currentLayer.image!, + image: state.cachedImage!, fit: BoxFit.fill, filterQuality: FilterQuality.none, ); @@ -63,16 +58,11 @@ class CanvasStateProvider extends _$CanvasStateProvider { state.commandManager.executeLastCommand(canvas); final picture = recorder.endRecording(); final img = await picture.toImage(size.width.toInt(), size.height.toInt()); - ref.read(layerMenuStateProvider.notifier).updateImageOfLayer(img); - - final mergedImage = await ref - .read(layerMenuStateProvider.notifier) - .getMergedImageOfVisibleLayers(img); - - state = state.copyWith(cachedImage: mergedImage); + state = state.copyWith(cachedImage: img); } Future resetCanvasWithNewCommands(Iterable commands) async { + state.commandManager.clearRedoStack(); state.commandManager.clearUndoStack(newCommands: commands); if (commands.isEmpty) { state = state.copyWith(cachedImage: null); diff --git a/lib/ui/pages/workspace_page/components/drawing_surface/drawing_canvas.dart b/lib/ui/pages/workspace_page/components/drawing_surface/drawing_canvas.dart index a7945b3a..e180430e 100644 --- a/lib/ui/pages/workspace_page/components/drawing_surface/drawing_canvas.dart +++ b/lib/ui/pages/workspace_page/components/drawing_surface/drawing_canvas.dart @@ -4,7 +4,6 @@ import 'package:paintroid/core/enums/tool_types.dart'; import 'package:paintroid/core/providers/object/canvas_painter_provider.dart'; import 'package:paintroid/core/providers/object/device_service.dart'; import 'package:paintroid/core/providers/state/canvas_state_provider.dart'; -import 'package:paintroid/core/providers/state/layer_menu_state_provider.dart'; import 'package:paintroid/core/providers/state/toolbox_state_provider.dart'; import 'package:paintroid/core/providers/state/workspace_state_notifier.dart'; import 'package:paintroid/ui/pages/workspace_page/components/drawing_surface/canvas_painter.dart'; @@ -45,7 +44,6 @@ class _DrawingCanvasState extends ConsumerState { }); void _onPointerDown(PointerDownEvent _) { - ref.read(layerMenuStateProvider.notifier).hide(); _pointersOnScreen++; if (_pointersOnScreen >= 2) { _isZooming = true; From dd165c41f6fd30799c4585f40b9bc3ce294865c9 Mon Sep 17 00:00:00 2001 From: Lenkomotive Date: Fri, 24 Jan 2025 15:17:35 +0100 Subject: [PATCH 14/16] PAINTROID-454: Flutter: Add Layers - revert layer combination --- .../state/canvas_state_provider.g.dart | 2 +- .../state/layer_menu_state_provider.g.dart | 2 +- .../state/spray_tool_provider.g.dart | 2 +- .../state/toolbox_state_provider.g.dart | 2 +- pubspec.lock | 30 ++----------------- 5 files changed, 7 insertions(+), 31 deletions(-) diff --git a/lib/core/providers/state/canvas_state_provider.g.dart b/lib/core/providers/state/canvas_state_provider.g.dart index 11b7204e..14ba8467 100644 --- a/lib/core/providers/state/canvas_state_provider.g.dart +++ b/lib/core/providers/state/canvas_state_provider.g.dart @@ -7,7 +7,7 @@ part of 'canvas_state_provider.dart'; // ************************************************************************** String _$canvasStateProviderHash() => - r'53f80e4e8c5f1813f23ed266571071c988eac172'; + r'679bba9b579d049bcfbf4cc5231ae14a2d5baeab'; /// See also [CanvasStateProvider]. @ProviderFor(CanvasStateProvider) diff --git a/lib/core/providers/state/layer_menu_state_provider.g.dart b/lib/core/providers/state/layer_menu_state_provider.g.dart index 65567ad5..84f6dfd7 100644 --- a/lib/core/providers/state/layer_menu_state_provider.g.dart +++ b/lib/core/providers/state/layer_menu_state_provider.g.dart @@ -7,7 +7,7 @@ part of 'layer_menu_state_provider.dart'; // ************************************************************************** String _$layerMenuStateProviderHash() => - r'8f6b852570118fb777ad0bd0069dc93ec4027637'; + r'620b4cdbb95e062945527a6f32a27efb0f7a4f12'; /// See also [LayerMenuStateProvider]. @ProviderFor(LayerMenuStateProvider) diff --git a/lib/core/providers/state/spray_tool_provider.g.dart b/lib/core/providers/state/spray_tool_provider.g.dart index 398115a0..8dfd0956 100644 --- a/lib/core/providers/state/spray_tool_provider.g.dart +++ b/lib/core/providers/state/spray_tool_provider.g.dart @@ -6,7 +6,7 @@ part of 'spray_tool_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$sprayToolProviderHash() => r'4b0bfa7d0b54e859a229fdbf6fa028c1fd9707ff'; +String _$sprayToolProviderHash() => r'7f2f003751d63db37267264ce921a1ed35e4cdd1'; /// See also [SprayToolProvider]. @ProviderFor(SprayToolProvider) diff --git a/lib/core/providers/state/toolbox_state_provider.g.dart b/lib/core/providers/state/toolbox_state_provider.g.dart index 113ad284..f018294b 100644 --- a/lib/core/providers/state/toolbox_state_provider.g.dart +++ b/lib/core/providers/state/toolbox_state_provider.g.dart @@ -7,7 +7,7 @@ part of 'toolbox_state_provider.dart'; // ************************************************************************** String _$toolBoxStateProviderHash() => - r'9d4d72e01cae0978f298dd60219e83af48d79239'; + r'23e3ddde3194c0acc46abe79fe6d0b0b4bf77ec6'; /// See also [ToolBoxStateProvider]. @ProviderFor(ToolBoxStateProvider) diff --git a/pubspec.lock b/pubspec.lock index 2b6b436f..e147a59d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -691,30 +691,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" - url: "https://pub.dev" - source: hosted - version: "10.0.4" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" - url: "https://pub.dev" - source: hosted - version: "3.0.3" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.dev" - source: hosted - version: "3.0.1" lints: dependency: transitive description: @@ -970,7 +946,7 @@ packages: sha256: c917dfb5f7afad4c7485bc00a4df038621248fce046105020cea276d1a87c820 url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "1.1.0" pub_semver: dependency: transitive description: @@ -1481,5 +1457,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" From f074728758b70ab5d3c596fe7c626a5df1d6cae9 Mon Sep 17 00:00:00 2001 From: Lenkomotive Date: Fri, 24 Jan 2025 18:47:06 +0100 Subject: [PATCH 15/16] PAINTROID-454: Flutter: Add Layers - associate commands with layers --- ios/Podfile.lock | 12 ++++---- .../command_factory/command_factory.dart | 24 ++++++++++----- .../graphic/graphic_command.dart | 9 ++++-- .../graphic/line_command.dart | 3 +- .../graphic/line_command.g.dart | 3 ++ .../graphic/path_command.dart | 6 ++-- .../graphic/path_command.g.dart | 3 ++ .../graphic/shape/circle_shape_command.dart | 2 ++ .../graphic/shape/circle_shape_command.g.dart | 3 ++ .../graphic/shape/shape_command.dart | 2 +- .../graphic/shape/square_shape_command.dart | 2 ++ .../graphic/shape/square_shape_command.g.dart | 3 ++ .../graphic/spray_command.dart | 4 ++- .../graphic/spray_command.g.dart | 3 ++ .../command_manager/command_manager.dart | 7 +++-- .../converter/value_key_converter.dart | 19 ++++++++++++ .../object/tools/brush_tool_provider.dart | 7 +++-- .../object/tools/brush_tool_provider.g.dart | 2 +- .../object/tools/eraser_tool_provider.dart | 6 ++-- .../object/tools/eraser_tool_provider.g.dart | 2 +- .../object/tools/hand_tool_provider.dart | 17 ++++++----- .../object/tools/hand_tool_provider.g.dart | 2 +- .../object/tools/line_tool_provider.dart | 25 ++++++++-------- .../object/tools/line_tool_provider.g.dart | 2 +- .../object/tools/shapes_tool_provider.dart | 8 +++-- .../object/tools/shapes_tool_provider.g.dart | 2 +- .../state/layer_menu_state_provider.dart | 8 ++++- .../state/layer_menu_state_provider.g.dart | 2 +- .../providers/state/spray_tool_provider.dart | 3 ++ .../state/spray_tool_provider.g.dart | 2 +- lib/core/tools/implementation/brush_tool.dart | 8 +++-- .../tools/implementation/eraser_tool.dart | 1 + lib/core/tools/implementation/hand_tool.dart | 1 + .../shapes_tool/shapes_tool.dart | 4 +++ lib/core/tools/implementation/spray_tool.dart | 7 ++++- lib/core/tools/line_tool/line_tool.dart | 2 ++ lib/core/tools/tool.dart | 11 +++++-- .../components/layer_menu/layer.dart | 15 +++++++--- test/unit/command/command_factory_test.dart | 9 +++--- test/unit/command/draw_path_command_test.dart | 7 ++--- test/unit/command/shape_command_test.dart | 11 +++++-- .../utils/dummy_command_factory.dart | 29 ++++++++++++++----- test/unit/tools/brush_tool_test.dart | 5 ++-- test/unit/tools/eraser_tool_test.dart | 3 +- test/unit/tools/hand_tool_test.dart | 3 +- test/unit/tools/line_tool_test.dart | 4 +-- test/unit/tools/shapes_tool_test.dart | 3 +- test/unit/tools/spray_tool_test.dart | 4 +-- 48 files changed, 219 insertions(+), 101 deletions(-) create mode 100644 lib/core/json_serialization/converter/value_key_converter.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b56a26e3..df74dceb 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -121,18 +121,18 @@ SPEC CHECKSUMS: file_picker: ce3938a0df3cc1ef404671531facef740d03f920 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_localization: f43b18844a2b3d2c71fd64f04ffd6b1e64dd54d4 - image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425 - integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 + image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 + integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4 launch_review: 75d5a956ba8eaa493e9c9d4bf4c05e505e8d5ed0 package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 - path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 SDWebImage: dfe95b2466a9823cf9f0c6d01217c06550d7b29a - shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 - url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe PODFILE CHECKSUM: 303789365c3a8d7bc562e5e65d7e8e15218ec5c6 -COCOAPODS: 1.15.0 +COCOAPODS: 1.16.2 diff --git a/lib/core/commands/command_factory/command_factory.dart b/lib/core/commands/command_factory/command_factory.dart index e26bf3d0..377f8632 100644 --- a/lib/core/commands/command_factory/command_factory.dart +++ b/lib/core/commands/command_factory/command_factory.dart @@ -1,5 +1,4 @@ -import 'dart:ui'; - +import 'package:flutter/cupertino.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/line_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/path_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/circle_shape_command.dart'; @@ -13,16 +12,18 @@ class CommandFactory { PathCommand createPathCommand( PathWithActionHistory path, Paint paint, + ValueKey layerKey, ) => - PathCommand(path, paint); + PathCommand(path, paint, layerKey); LineCommand createLineCommand( PathWithActionHistory path, Paint paint, Offset startPoint, Offset endPoint, + ValueKey layerKey, ) => - LineCommand(path, paint, startPoint, endPoint); + LineCommand(path, paint, layerKey, startPoint, endPoint); SquareShapeCommand createSquareShapeCommand( Paint paint, @@ -30,17 +31,24 @@ class CommandFactory { Offset topRight, Offset bottomLeft, Offset bottomRight, + ValueKey layerKey, ) => - SquareShapeCommand(paint, topLeft, topRight, bottomLeft, bottomRight); + SquareShapeCommand( + paint, layerKey, topLeft, topRight, bottomLeft, bottomRight); CircleShapeCommand createCircleShapeCommand( Paint paint, double radius, Offset center, + ValueKey layerKey, ) => - CircleShapeCommand(paint, radius, center); + CircleShapeCommand(paint, layerKey, radius, center); - SprayCommand createSprayCommand(List points, Paint paint) { - return SprayCommand(points, paint); + SprayCommand createSprayCommand( + List points, + Paint paint, + ValueKey layerKey, + ) { + return SprayCommand(points, paint, layerKey); } } diff --git a/lib/core/commands/command_implementation/graphic/graphic_command.dart b/lib/core/commands/command_implementation/graphic/graphic_command.dart index 54fc2de7..5bc7db2d 100644 --- a/lib/core/commands/command_implementation/graphic/graphic_command.dart +++ b/lib/core/commands/command_implementation/graphic/graphic_command.dart @@ -1,13 +1,16 @@ -import 'dart:ui'; - +import 'package:flutter/cupertino.dart'; import 'package:paintroid/core/commands/command_implementation/command.dart'; import 'package:paintroid/core/json_serialization/converter/paint_converter.dart'; +import 'package:paintroid/core/json_serialization/converter/value_key_converter.dart'; abstract class GraphicCommand extends Command { - const GraphicCommand(this.paint); + const GraphicCommand(this.paint, this.layerKey); @PaintConverter() final Paint paint; + @ValueKeyConverter() + final ValueKey layerKey; + void call(Canvas canvas); } diff --git a/lib/core/commands/command_implementation/graphic/line_command.dart b/lib/core/commands/command_implementation/graphic/line_command.dart index 71da190a..06a0a48a 100644 --- a/lib/core/commands/command_implementation/graphic/line_command.dart +++ b/lib/core/commands/command_implementation/graphic/line_command.dart @@ -1,12 +1,12 @@ import 'dart:ui'; import 'package:freezed_annotation/freezed_annotation.dart'; - import 'package:paintroid/core/commands/command_implementation/graphic/graphic_command.dart'; import 'package:paintroid/core/commands/path_with_action_history.dart'; import 'package:paintroid/core/json_serialization/converter/offset_converter.dart'; import 'package:paintroid/core/json_serialization/converter/paint_converter.dart'; import 'package:paintroid/core/json_serialization/converter/path_with_action_history_converter.dart'; +import 'package:paintroid/core/json_serialization/converter/value_key_converter.dart'; import 'package:paintroid/core/json_serialization/versioning/serializer_version.dart'; import 'package:paintroid/core/json_serialization/versioning/version_strategy.dart'; @@ -30,6 +30,7 @@ class LineCommand extends GraphicCommand { LineCommand( this.path, super.paint, + super.layerKey, this.startPoint, this.endPoint, { this.type = SerializerType.LINE_COMMAND, diff --git a/lib/core/commands/command_implementation/graphic/line_command.g.dart b/lib/core/commands/command_implementation/graphic/line_command.g.dart index 9e41a807..bec0ab9a 100644 --- a/lib/core/commands/command_implementation/graphic/line_command.g.dart +++ b/lib/core/commands/command_implementation/graphic/line_command.g.dart @@ -10,6 +10,8 @@ LineCommand _$LineCommandFromJson(Map json) => LineCommand( const PathWithActionHistoryConverter() .fromJson(json['path'] as Map), const PaintConverter().fromJson(json['paint'] as Map), + const ValueKeyConverter() + .fromJson(json['layerKey'] as Map), const OffsetConverter() .fromJson(json['startPoint'] as Map), const OffsetConverter() @@ -21,6 +23,7 @@ LineCommand _$LineCommandFromJson(Map json) => LineCommand( Map _$LineCommandToJson(LineCommand instance) => { 'paint': const PaintConverter().toJson(instance.paint), + 'layerKey': const ValueKeyConverter().toJson(instance.layerKey), 'type': instance.type, 'version': instance.version, 'isSourcePath': instance.isSourcePath, diff --git a/lib/core/commands/command_implementation/graphic/path_command.dart b/lib/core/commands/command_implementation/graphic/path_command.dart index a26fff85..d8a7f3be 100644 --- a/lib/core/commands/command_implementation/graphic/path_command.dart +++ b/lib/core/commands/command_implementation/graphic/path_command.dart @@ -1,13 +1,12 @@ import 'dart:ui'; import 'package:flutter/widgets.dart'; - import 'package:json_annotation/json_annotation.dart'; - import 'package:paintroid/core/commands/command_implementation/graphic/graphic_command.dart'; import 'package:paintroid/core/commands/path_with_action_history.dart'; import 'package:paintroid/core/json_serialization/converter/paint_converter.dart'; import 'package:paintroid/core/json_serialization/converter/path_with_action_history_converter.dart'; +import 'package:paintroid/core/json_serialization/converter/value_key_converter.dart'; import 'package:paintroid/core/json_serialization/versioning/serializer_version.dart'; import 'package:paintroid/core/json_serialization/versioning/version_strategy.dart'; @@ -20,7 +19,8 @@ class PathCommand extends GraphicCommand { PathCommand( this.path, - super.paint, { + super.paint, + super.layerKey, { this.type = SerializerType.PATH_COMMAND, int? version, }) : version = diff --git a/lib/core/commands/command_implementation/graphic/path_command.g.dart b/lib/core/commands/command_implementation/graphic/path_command.g.dart index 5711dc2f..7dee3331 100644 --- a/lib/core/commands/command_implementation/graphic/path_command.g.dart +++ b/lib/core/commands/command_implementation/graphic/path_command.g.dart @@ -10,6 +10,8 @@ PathCommand _$PathCommandFromJson(Map json) => PathCommand( const PathWithActionHistoryConverter() .fromJson(json['path'] as Map), const PaintConverter().fromJson(json['paint'] as Map), + const ValueKeyConverter() + .fromJson(json['layerKey'] as Map), type: json['type'] as String? ?? SerializerType.PATH_COMMAND, version: (json['version'] as num?)?.toInt(), ); @@ -17,6 +19,7 @@ PathCommand _$PathCommandFromJson(Map json) => PathCommand( Map _$PathCommandToJson(PathCommand instance) => { 'paint': const PaintConverter().toJson(instance.paint), + 'layerKey': const ValueKeyConverter().toJson(instance.layerKey), 'type': instance.type, 'version': instance.version, 'path': const PathWithActionHistoryConverter().toJson(instance.path), diff --git a/lib/core/commands/command_implementation/graphic/shape/circle_shape_command.dart b/lib/core/commands/command_implementation/graphic/shape/circle_shape_command.dart index 679a02e5..a79e74ad 100644 --- a/lib/core/commands/command_implementation/graphic/shape/circle_shape_command.dart +++ b/lib/core/commands/command_implementation/graphic/shape/circle_shape_command.dart @@ -4,6 +4,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/shape_command.dart'; import 'package:paintroid/core/json_serialization/converter/offset_converter.dart'; import 'package:paintroid/core/json_serialization/converter/paint_converter.dart'; +import 'package:paintroid/core/json_serialization/converter/value_key_converter.dart'; import 'package:paintroid/core/json_serialization/versioning/serializer_version.dart'; import 'package:paintroid/core/json_serialization/versioning/version_strategy.dart'; @@ -20,6 +21,7 @@ class CircleShapeCommand extends ShapeCommand { CircleShapeCommand( super.paint, + super.layerKey, this.radius, this.center, { int? version, diff --git a/lib/core/commands/command_implementation/graphic/shape/circle_shape_command.g.dart b/lib/core/commands/command_implementation/graphic/shape/circle_shape_command.g.dart index afd294fb..c42cf1d1 100644 --- a/lib/core/commands/command_implementation/graphic/shape/circle_shape_command.g.dart +++ b/lib/core/commands/command_implementation/graphic/shape/circle_shape_command.g.dart @@ -9,6 +9,8 @@ part of 'circle_shape_command.dart'; CircleShapeCommand _$CircleShapeCommandFromJson(Map json) => CircleShapeCommand( const PaintConverter().fromJson(json['paint'] as Map), + const ValueKeyConverter() + .fromJson(json['layerKey'] as Map), (json['radius'] as num).toDouble(), const OffsetConverter().fromJson(json['center'] as Map), version: (json['version'] as num?)?.toInt(), @@ -18,6 +20,7 @@ CircleShapeCommand _$CircleShapeCommandFromJson(Map json) => Map _$CircleShapeCommandToJson(CircleShapeCommand instance) => { 'paint': const PaintConverter().toJson(instance.paint), + 'layerKey': const ValueKeyConverter().toJson(instance.layerKey), 'radius': instance.radius, 'center': const OffsetConverter().toJson(instance.center), 'version': instance.version, diff --git a/lib/core/commands/command_implementation/graphic/shape/shape_command.dart b/lib/core/commands/command_implementation/graphic/shape/shape_command.dart index 767e970c..c7e2a99a 100644 --- a/lib/core/commands/command_implementation/graphic/shape/shape_command.dart +++ b/lib/core/commands/command_implementation/graphic/shape/shape_command.dart @@ -1,5 +1,5 @@ import 'package:paintroid/core/commands/command_implementation/graphic/graphic_command.dart'; abstract class ShapeCommand extends GraphicCommand { - ShapeCommand(super.paint); + ShapeCommand(super.paint, super.layerKey); } diff --git a/lib/core/commands/command_implementation/graphic/shape/square_shape_command.dart b/lib/core/commands/command_implementation/graphic/shape/square_shape_command.dart index 837bf0f0..84618eda 100644 --- a/lib/core/commands/command_implementation/graphic/shape/square_shape_command.dart +++ b/lib/core/commands/command_implementation/graphic/shape/square_shape_command.dart @@ -4,6 +4,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/shape_command.dart'; import 'package:paintroid/core/json_serialization/converter/offset_converter.dart'; import 'package:paintroid/core/json_serialization/converter/paint_converter.dart'; +import 'package:paintroid/core/json_serialization/converter/value_key_converter.dart'; import 'package:paintroid/core/json_serialization/versioning/serializer_version.dart'; import 'package:paintroid/core/json_serialization/versioning/version_strategy.dart'; @@ -32,6 +33,7 @@ class SquareShapeCommand extends ShapeCommand { SquareShapeCommand( super.paint, + super.layerKey, this.topLeft, this.topRight, this.bottomLeft, diff --git a/lib/core/commands/command_implementation/graphic/shape/square_shape_command.g.dart b/lib/core/commands/command_implementation/graphic/shape/square_shape_command.g.dart index 490755ff..c6821757 100644 --- a/lib/core/commands/command_implementation/graphic/shape/square_shape_command.g.dart +++ b/lib/core/commands/command_implementation/graphic/shape/square_shape_command.g.dart @@ -9,6 +9,8 @@ part of 'square_shape_command.dart'; SquareShapeCommand _$SquareShapeCommandFromJson(Map json) => SquareShapeCommand( const PaintConverter().fromJson(json['paint'] as Map), + const ValueKeyConverter() + .fromJson(json['layerKey'] as Map), const OffsetConverter().fromJson(json['topLeft'] as Map), const OffsetConverter() .fromJson(json['topRight'] as Map), @@ -23,6 +25,7 @@ SquareShapeCommand _$SquareShapeCommandFromJson(Map json) => Map _$SquareShapeCommandToJson(SquareShapeCommand instance) => { 'paint': const PaintConverter().toJson(instance.paint), + 'layerKey': const ValueKeyConverter().toJson(instance.layerKey), 'topLeft': const OffsetConverter().toJson(instance.topLeft), 'topRight': const OffsetConverter().toJson(instance.topRight), 'bottomLeft': const OffsetConverter().toJson(instance.bottomLeft), diff --git a/lib/core/commands/command_implementation/graphic/spray_command.dart b/lib/core/commands/command_implementation/graphic/spray_command.dart index 7ef3835c..dd9d1e29 100644 --- a/lib/core/commands/command_implementation/graphic/spray_command.dart +++ b/lib/core/commands/command_implementation/graphic/spray_command.dart @@ -4,6 +4,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/graphic_command.dart'; import 'package:paintroid/core/json_serialization/converter/offset_converter.dart'; import 'package:paintroid/core/json_serialization/converter/paint_converter.dart'; +import 'package:paintroid/core/json_serialization/converter/value_key_converter.dart'; import 'package:paintroid/core/json_serialization/versioning/serializer_version.dart'; import 'package:paintroid/core/json_serialization/versioning/version_strategy.dart'; @@ -19,7 +20,8 @@ class SprayCommand extends GraphicCommand { SprayCommand( this.points, - super.paint, { + super.paint, + super.layerKey, { this.type = SerializerType.SPRAY_COMMAND, int? version, }) : version = diff --git a/lib/core/commands/command_implementation/graphic/spray_command.g.dart b/lib/core/commands/command_implementation/graphic/spray_command.g.dart index df1ae9e7..082516e2 100644 --- a/lib/core/commands/command_implementation/graphic/spray_command.g.dart +++ b/lib/core/commands/command_implementation/graphic/spray_command.g.dart @@ -12,6 +12,8 @@ SprayCommand _$SprayCommandFromJson(Map json) => SprayCommand( const OffsetConverter().fromJson(e as Map)) .toList(), const PaintConverter().fromJson(json['paint'] as Map), + const ValueKeyConverter() + .fromJson(json['layerKey'] as Map), type: json['type'] as String? ?? SerializerType.SPRAY_COMMAND, version: (json['version'] as num?)?.toInt(), ); @@ -19,6 +21,7 @@ SprayCommand _$SprayCommandFromJson(Map json) => SprayCommand( Map _$SprayCommandToJson(SprayCommand instance) => { 'paint': const PaintConverter().toJson(instance.paint), + 'layerKey': const ValueKeyConverter().toJson(instance.layerKey), 'type': instance.type, 'version': instance.version, 'points': instance.points.map(const OffsetConverter().toJson).toList(), diff --git a/lib/core/commands/command_manager/command_manager.dart b/lib/core/commands/command_manager/command_manager.dart index 9f33aa18..747872b4 100644 --- a/lib/core/commands/command_manager/command_manager.dart +++ b/lib/core/commands/command_manager/command_manager.dart @@ -5,10 +5,10 @@ import 'package:paintroid/core/commands/command_implementation/graphic/graphic_c import 'package:paintroid/core/commands/command_implementation/graphic/line_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/circle_shape_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/square_shape_command.dart'; +import 'package:paintroid/core/commands/command_implementation/graphic/spray_command.dart'; import 'package:paintroid/core/tools/line_tool/vertex.dart'; import 'package:paintroid/core/tools/line_tool/vertex_stack.dart'; import 'package:paintroid/core/tools/tool_data.dart'; -import 'package:paintroid/core/commands/command_implementation/graphic/spray_command.dart'; enum ActionType { UNDO, REDO } @@ -38,9 +38,11 @@ class CommandManager { void executeAllCommands(Canvas canvas) { for (final command in _undoStack) { if (command is GraphicCommand) { + print(command.layerKey); command.call(canvas); } } + print('-----------------------'); } void discardLastCommand() { @@ -110,8 +112,7 @@ class CommandManager { return ToolData.SHAPES; } else if (command.runtimeType == CircleShapeCommand) { return ToolData.SHAPES; - } - else if (command.runtimeType == SprayCommand) { + } else if (command.runtimeType == SprayCommand) { return ToolData.SPRAY; } else { return ToolData.BRUSH; diff --git a/lib/core/json_serialization/converter/value_key_converter.dart b/lib/core/json_serialization/converter/value_key_converter.dart new file mode 100644 index 00000000..d7366671 --- /dev/null +++ b/lib/core/json_serialization/converter/value_key_converter.dart @@ -0,0 +1,19 @@ +import 'package:flutter/cupertino.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +class ValueKeyConverter + implements JsonConverter> { + const ValueKeyConverter(); + + @override + ValueKey fromJson(Map json) { + return ValueKey(json['value']); + } + + @override + Map toJson(ValueKey key) { + return { + 'value': key.value, + }; + } +} diff --git a/lib/core/providers/object/tools/brush_tool_provider.dart b/lib/core/providers/object/tools/brush_tool_provider.dart index 32d2d685..40b3ad2a 100644 --- a/lib/core/providers/object/tools/brush_tool_provider.dart +++ b/lib/core/providers/object/tools/brush_tool_provider.dart @@ -1,11 +1,10 @@ - -import 'package:riverpod_annotation/riverpod_annotation.dart'; - import 'package:paintroid/core/commands/command_factory/command_factory_provider.dart'; import 'package:paintroid/core/commands/command_manager/command_manager_provider.dart'; import 'package:paintroid/core/commands/graphic_factory/graphic_factory_provider.dart'; import 'package:paintroid/core/enums/tool_types.dart'; +import 'package:paintroid/core/providers/state/layer_menu_state_provider.dart'; import 'package:paintroid/core/tools/implementation/brush_tool.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'brush_tool_provider.g.dart'; @@ -17,6 +16,8 @@ class BrushToolProvider extends _$BrushToolProvider { commandManager: ref.watch(commandManagerProvider), commandFactory: ref.watch(commandFactoryProvider), graphicFactory: ref.watch(graphicFactoryProvider), + layerKey: + ref.watch(layerMenuStateProvider.notifier).getSelectedLayerKey(), type: ToolType.BRUSH, ); } diff --git a/lib/core/providers/object/tools/brush_tool_provider.g.dart b/lib/core/providers/object/tools/brush_tool_provider.g.dart index 0af93977..1d6f5890 100644 --- a/lib/core/providers/object/tools/brush_tool_provider.g.dart +++ b/lib/core/providers/object/tools/brush_tool_provider.g.dart @@ -6,7 +6,7 @@ part of 'brush_tool_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$brushToolProviderHash() => r'097ab3e3003d7fd63210aba5980917eb3a449173'; +String _$brushToolProviderHash() => r'bb157a98f70f2a8a3b5a2bf7a5da8ed834518ab0'; /// See also [BrushToolProvider]. @ProviderFor(BrushToolProvider) diff --git a/lib/core/providers/object/tools/eraser_tool_provider.dart b/lib/core/providers/object/tools/eraser_tool_provider.dart index 4a7f4aaa..14b7fd04 100644 --- a/lib/core/providers/object/tools/eraser_tool_provider.dart +++ b/lib/core/providers/object/tools/eraser_tool_provider.dart @@ -1,11 +1,10 @@ - -import 'package:riverpod_annotation/riverpod_annotation.dart'; - import 'package:paintroid/core/commands/command_factory/command_factory_provider.dart'; import 'package:paintroid/core/commands/command_manager/command_manager_provider.dart'; import 'package:paintroid/core/commands/graphic_factory/graphic_factory_provider.dart'; import 'package:paintroid/core/enums/tool_types.dart'; +import 'package:paintroid/core/providers/state/layer_menu_state_provider.dart'; import 'package:paintroid/core/tools/implementation/eraser_tool.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'eraser_tool_provider.g.dart'; @@ -17,6 +16,7 @@ class EraserToolProvider extends _$EraserToolProvider { commandManager: ref.watch(commandManagerProvider), commandFactory: ref.watch(commandFactoryProvider), graphicFactory: ref.watch(graphicFactoryProvider), + layerKey: ref.read(layerMenuStateProvider.notifier).getSelectedLayerKey(), type: ToolType.ERASER, ); } diff --git a/lib/core/providers/object/tools/eraser_tool_provider.g.dart b/lib/core/providers/object/tools/eraser_tool_provider.g.dart index 7db176b0..f0583ae9 100644 --- a/lib/core/providers/object/tools/eraser_tool_provider.g.dart +++ b/lib/core/providers/object/tools/eraser_tool_provider.g.dart @@ -7,7 +7,7 @@ part of 'eraser_tool_provider.dart'; // ************************************************************************** String _$eraserToolProviderHash() => - r'b66c9a0d7cc9a79cd50bcc4e320a47383d12e851'; + r'e9dbf719d8e260201526cecb2602ae10df891ecb'; /// See also [EraserToolProvider]. @ProviderFor(EraserToolProvider) diff --git a/lib/core/providers/object/tools/hand_tool_provider.dart b/lib/core/providers/object/tools/hand_tool_provider.dart index 7eaf53da..b9662534 100644 --- a/lib/core/providers/object/tools/hand_tool_provider.dart +++ b/lib/core/providers/object/tools/hand_tool_provider.dart @@ -1,10 +1,9 @@ - -import 'package:riverpod_annotation/riverpod_annotation.dart'; - import 'package:paintroid/core/commands/command_factory/command_factory_provider.dart'; import 'package:paintroid/core/commands/command_manager/command_manager_provider.dart'; import 'package:paintroid/core/enums/tool_types.dart'; +import 'package:paintroid/core/providers/state/layer_menu_state_provider.dart'; import 'package:paintroid/core/tools/implementation/hand_tool.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'hand_tool_provider.g.dart'; @@ -13,9 +12,11 @@ class HandToolProvider extends _$HandToolProvider { @override HandTool build() { return HandTool( - commandManager: ref.watch(commandManagerProvider), - commandFactory: ref.watch(commandFactoryProvider), - type: ToolType.HAND, - ); + commandManager: ref.watch(commandManagerProvider), + commandFactory: ref.watch(commandFactoryProvider), + layerKey: + ref.watch(layerMenuStateProvider.notifier).getSelectedLayerKey(), + type: ToolType.HAND, + ); } -} \ No newline at end of file +} diff --git a/lib/core/providers/object/tools/hand_tool_provider.g.dart b/lib/core/providers/object/tools/hand_tool_provider.g.dart index c584ffbd..1daca5fa 100644 --- a/lib/core/providers/object/tools/hand_tool_provider.g.dart +++ b/lib/core/providers/object/tools/hand_tool_provider.g.dart @@ -6,7 +6,7 @@ part of 'hand_tool_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$handToolProviderHash() => r'7cd05f4d63f9a6790dfb4a4248db7859e5c44596'; +String _$handToolProviderHash() => r'f278abe2fd6d9f65454dea7e51c1e7602117e006'; /// See also [HandToolProvider]. @ProviderFor(HandToolProvider) diff --git a/lib/core/providers/object/tools/line_tool_provider.dart b/lib/core/providers/object/tools/line_tool_provider.dart index adf1f5e5..5c7f6d75 100644 --- a/lib/core/providers/object/tools/line_tool_provider.dart +++ b/lib/core/providers/object/tools/line_tool_provider.dart @@ -1,12 +1,11 @@ - -import 'package:riverpod_annotation/riverpod_annotation.dart'; - import 'package:paintroid/core/commands/command_factory/command_factory_provider.dart'; import 'package:paintroid/core/commands/command_manager/command_manager_provider.dart'; import 'package:paintroid/core/commands/graphic_factory/graphic_factory_provider.dart'; import 'package:paintroid/core/enums/tool_types.dart'; import 'package:paintroid/core/providers/state/canvas_state_provider.dart'; +import 'package:paintroid/core/providers/state/layer_menu_state_provider.dart'; import 'package:paintroid/core/tools/line_tool/line_tool.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'line_tool_provider.g.dart'; @@ -15,13 +14,15 @@ class LineToolProvider extends _$LineToolProvider { @override LineTool build() { return LineTool( - type: ToolType.LINE, - commandManager: ref.watch(commandManagerProvider), - commandFactory: ref.watch(commandFactoryProvider), - graphicFactory: ref.watch(graphicFactoryProvider), - drawingSurfaceSize: ref.watch( - canvasStateProvider.select((state) => state.size), - ), - ); + type: ToolType.LINE, + commandManager: ref.watch(commandManagerProvider), + commandFactory: ref.watch(commandFactoryProvider), + graphicFactory: ref.watch(graphicFactoryProvider), + layerKey: + ref.watch(layerMenuStateProvider.notifier).getSelectedLayerKey(), + drawingSurfaceSize: ref.watch( + canvasStateProvider.select((state) => state.size), + ), + ); } -} \ No newline at end of file +} diff --git a/lib/core/providers/object/tools/line_tool_provider.g.dart b/lib/core/providers/object/tools/line_tool_provider.g.dart index c6867a58..3b11428a 100644 --- a/lib/core/providers/object/tools/line_tool_provider.g.dart +++ b/lib/core/providers/object/tools/line_tool_provider.g.dart @@ -6,7 +6,7 @@ part of 'line_tool_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$lineToolProviderHash() => r'ddd41ee46e06c9c30f63aa9b00d3ddecfa18f9ea'; +String _$lineToolProviderHash() => r'548e10ed548ccf56b29adc94e28b700416fbc3e3'; /// See also [LineToolProvider]. @ProviderFor(LineToolProvider) diff --git a/lib/core/providers/object/tools/shapes_tool_provider.dart b/lib/core/providers/object/tools/shapes_tool_provider.dart index f82cbcaa..43debe2e 100644 --- a/lib/core/providers/object/tools/shapes_tool_provider.dart +++ b/lib/core/providers/object/tools/shapes_tool_provider.dart @@ -1,11 +1,12 @@ import 'package:flutter/painting.dart'; +import 'package:paintroid/core/commands/command_factory/command_factory_provider.dart'; +import 'package:paintroid/core/commands/command_manager/command_manager_provider.dart'; +import 'package:paintroid/core/enums/tool_types.dart'; import 'package:paintroid/core/providers/state/canvas_state_provider.dart'; +import 'package:paintroid/core/providers/state/layer_menu_state_provider.dart'; import 'package:paintroid/core/tools/implementation/shapes_tool/bounding_box.dart'; import 'package:paintroid/core/tools/implementation/shapes_tool/shapes_tool.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:paintroid/core/commands/command_factory/command_factory_provider.dart'; -import 'package:paintroid/core/commands/command_manager/command_manager_provider.dart'; -import 'package:paintroid/core/enums/tool_types.dart'; part 'shapes_tool_provider.g.dart'; @@ -22,6 +23,7 @@ class ShapesToolProvider extends _$ShapesToolProvider { commandManager: ref.watch(commandManagerProvider), commandFactory: ref.watch(commandFactoryProvider), type: ToolType.SHAPES, + layerKey: ref.read(layerMenuStateProvider.notifier).getSelectedLayerKey(), boundingBox: BoundingBox( initialBoundingBox.topLeft, initialBoundingBox.topRight, diff --git a/lib/core/providers/object/tools/shapes_tool_provider.g.dart b/lib/core/providers/object/tools/shapes_tool_provider.g.dart index d3339e7d..25fefab6 100644 --- a/lib/core/providers/object/tools/shapes_tool_provider.g.dart +++ b/lib/core/providers/object/tools/shapes_tool_provider.g.dart @@ -7,7 +7,7 @@ part of 'shapes_tool_provider.dart'; // ************************************************************************** String _$shapesToolProviderHash() => - r'bc04ece278549c5a00ded19c08be50f360979776'; + r'3b80e29d665a54f3fa04c505048552c2aeb94a40'; /// See also [ShapesToolProvider]. @ProviderFor(ShapesToolProvider) diff --git a/lib/core/providers/state/layer_menu_state_provider.dart b/lib/core/providers/state/layer_menu_state_provider.dart index efdc7c92..742bad6b 100644 --- a/lib/core/providers/state/layer_menu_state_provider.dart +++ b/lib/core/providers/state/layer_menu_state_provider.dart @@ -5,6 +5,7 @@ import 'package:paintroid/core/commands/graphic_factory/graphic_factory_provider import 'package:paintroid/core/providers/state/canvas_state_provider.dart'; import 'package:paintroid/core/providers/state/layer_menu_state_data.dart'; import 'package:paintroid/core/providers/state/layer_state_data.dart'; +import 'package:paintroid/core/providers/state/toolbox_state_provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:uuid/uuid.dart'; @@ -34,6 +35,9 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { void hide() => state = state.copyWith(isVisible: false); + ValueKey getSelectedLayerKey() => + state.layers.firstWhere((layer) => layer.isSelected).key; + void reorder(int oldIndex, int newIndex) { List layerList = List.from(state.layers); if (oldIndex < newIndex) { @@ -44,7 +48,7 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { state = state.copyWith(layers: layerList); } - void toggleSelection(Key? layerKey) { + void toggleSelection(ValueKey layerKey) { final updatedLayerList = state.layers.map((layer) { if (layer.key == layerKey) { return layer.copyWith(isSelected: true); @@ -52,6 +56,8 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { return layer.copyWith(isSelected: false); }).toList(); + ref.read(toolBoxStateProvider).currentTool.updateLayerKey(layerKey); + print(ref.read(toolBoxStateProvider).currentTool.layerKey); state = state.copyWith(layers: updatedLayerList); } diff --git a/lib/core/providers/state/layer_menu_state_provider.g.dart b/lib/core/providers/state/layer_menu_state_provider.g.dart index 84f6dfd7..ba8a4e66 100644 --- a/lib/core/providers/state/layer_menu_state_provider.g.dart +++ b/lib/core/providers/state/layer_menu_state_provider.g.dart @@ -7,7 +7,7 @@ part of 'layer_menu_state_provider.dart'; // ************************************************************************** String _$layerMenuStateProviderHash() => - r'620b4cdbb95e062945527a6f32a27efb0f7a4f12'; + r'058d9788c8c265c4b6a1db7d17910ce15e2b4151'; /// See also [LayerMenuStateProvider]. @ProviderFor(LayerMenuStateProvider) diff --git a/lib/core/providers/state/spray_tool_provider.dart b/lib/core/providers/state/spray_tool_provider.dart index a9179b9b..b80dbc5d 100644 --- a/lib/core/providers/state/spray_tool_provider.dart +++ b/lib/core/providers/state/spray_tool_provider.dart @@ -3,6 +3,7 @@ import 'package:paintroid/core/commands/command_manager/command_manager_provider import 'package:paintroid/core/commands/graphic_factory/graphic_factory_provider.dart'; import 'package:paintroid/core/enums/tool_types.dart'; import 'package:paintroid/core/providers/state/canvas_state_provider.dart'; +import 'package:paintroid/core/providers/state/layer_menu_state_provider.dart'; import 'package:paintroid/core/tools/implementation/spray_tool.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -17,6 +18,8 @@ class SprayToolProvider extends _$SprayToolProvider { commandManager: ref.watch(commandManagerProvider), commandFactory: ref.watch(commandFactoryProvider), graphicFactory: ref.watch(graphicFactoryProvider), + layerKey: + ref.watch(layerMenuStateProvider.notifier).getSelectedLayerKey(), drawingSurfaceSize: ref.watch(canvasStateProvider.select((state) => state.size)), ); diff --git a/lib/core/providers/state/spray_tool_provider.g.dart b/lib/core/providers/state/spray_tool_provider.g.dart index 8dfd0956..92f5c1ef 100644 --- a/lib/core/providers/state/spray_tool_provider.g.dart +++ b/lib/core/providers/state/spray_tool_provider.g.dart @@ -6,7 +6,7 @@ part of 'spray_tool_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$sprayToolProviderHash() => r'7f2f003751d63db37267264ce921a1ed35e4cdd1'; +String _$sprayToolProviderHash() => r'6c0dc9abef757c489132325297576818f938eed5'; /// See also [SprayToolProvider]. @ProviderFor(SprayToolProvider) diff --git a/lib/core/tools/implementation/brush_tool.dart b/lib/core/tools/implementation/brush_tool.dart index 3d7ade79..302ecdee 100644 --- a/lib/core/tools/implementation/brush_tool.dart +++ b/lib/core/tools/implementation/brush_tool.dart @@ -1,7 +1,6 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; - import 'package:paintroid/core/commands/graphic_factory/graphic_factory.dart'; import 'package:paintroid/core/commands/path_with_action_history.dart'; import 'package:paintroid/core/tools/tool.dart'; @@ -17,6 +16,7 @@ class BrushTool extends Tool { required super.commandManager, required this.graphicFactory, required super.type, + required super.layerKey, super.hasAddFunctionality = false, super.hasFinalizeFunctionality = false, }); @@ -26,7 +26,11 @@ class BrushTool extends Tool { pathToDraw = graphicFactory.createPathWithActionHistory() ..moveTo(point.dx, point.dy); Paint savedPaint = graphicFactory.copyPaint(paint); - final command = commandFactory.createPathCommand(pathToDraw, savedPaint); + final command = commandFactory.createPathCommand( + pathToDraw, + savedPaint, + layerKey, + ); commandManager.addGraphicCommand(command); } diff --git a/lib/core/tools/implementation/eraser_tool.dart b/lib/core/tools/implementation/eraser_tool.dart index e174abfb..559aa544 100644 --- a/lib/core/tools/implementation/eraser_tool.dart +++ b/lib/core/tools/implementation/eraser_tool.dart @@ -6,6 +6,7 @@ class EraserTool extends BrushTool { required super.commandFactory, required super.commandManager, required super.graphicFactory, + required super.layerKey, required super.type, }); diff --git a/lib/core/tools/implementation/hand_tool.dart b/lib/core/tools/implementation/hand_tool.dart index be505c17..b9ea88ff 100644 --- a/lib/core/tools/implementation/hand_tool.dart +++ b/lib/core/tools/implementation/hand_tool.dart @@ -7,6 +7,7 @@ class HandTool extends Tool { required super.commandFactory, required super.commandManager, required super.type, + required super.layerKey, super.hasAddFunctionality = false, super.hasFinalizeFunctionality = false, }); diff --git a/lib/core/tools/implementation/shapes_tool/shapes_tool.dart b/lib/core/tools/implementation/shapes_tool/shapes_tool.dart index ac1fda68..3da18c74 100644 --- a/lib/core/tools/implementation/shapes_tool/shapes_tool.dart +++ b/lib/core/tools/implementation/shapes_tool/shapes_tool.dart @@ -1,5 +1,6 @@ import 'dart:math'; import 'dart:ui'; + import 'package:flutter/widgets.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/shape_command.dart'; import 'package:paintroid/core/commands/graphic_factory/graphic_factory.dart'; @@ -17,6 +18,7 @@ class ShapesTool extends Tool { required super.commandManager, required super.type, required this.boundingBox, + required super.layerKey, this.isRotating = false, this.shapeType = ShapeType.square, super.hasAddFunctionality = false, @@ -49,6 +51,7 @@ class ShapesTool extends Tool { boundingBox.getPaddedTopRight(padding: padding), boundingBox.getPaddedBottomLeft(padding: padding), boundingBox.getPaddedBottomRight(padding: padding), + layerKey, ); break; case ShapeType.circle: @@ -57,6 +60,7 @@ class ShapesTool extends Tool { paint, radius, boundingBox.center, + layerKey, ); break; } diff --git a/lib/core/tools/implementation/spray_tool.dart b/lib/core/tools/implementation/spray_tool.dart index a61a1a94..4e7db26c 100644 --- a/lib/core/tools/implementation/spray_tool.dart +++ b/lib/core/tools/implementation/spray_tool.dart @@ -21,6 +21,7 @@ class SprayTool extends Tool { required super.commandManager, required this.graphicFactory, required super.type, + required super.layerKey, required this.drawingSurfaceSize, super.hasAddFunctionality = false, super.hasFinalizeFunctionality = false, @@ -33,7 +34,11 @@ class SprayTool extends Tool { void onDown(Offset point, Paint paint) { this.paint = graphicFactory.copyPaint(paint); final initialPoints = _generateSprayPoints(point); - sprayCommand = commandFactory.createSprayCommand(initialPoints, this.paint); + sprayCommand = commandFactory.createSprayCommand( + initialPoints, + this.paint, + layerKey, + ); commandManager.addGraphicCommand(sprayCommand); } diff --git a/lib/core/tools/line_tool/line_tool.dart b/lib/core/tools/line_tool/line_tool.dart index df2189a5..8722dbdf 100644 --- a/lib/core/tools/line_tool/line_tool.dart +++ b/lib/core/tools/line_tool/line_tool.dart @@ -16,6 +16,7 @@ class LineTool extends Tool with EquatableMixin { required this.graphicFactory, required this.drawingSurfaceSize, required super.type, + required super.layerKey, super.hasAddFunctionality = true, super.hasFinalizeFunctionality = true, }); @@ -246,6 +247,7 @@ class LineTool extends Tool with EquatableMixin { paint, startPoint, endPoint, + layerKey, ); return command; } diff --git a/lib/core/tools/tool.dart b/lib/core/tools/tool.dart index 31cd3280..99b13999 100644 --- a/lib/core/tools/tool.dart +++ b/lib/core/tools/tool.dart @@ -1,5 +1,4 @@ -import 'dart:ui'; - +import 'package:flutter/cupertino.dart'; import 'package:paintroid/core/commands/command_factory/command_factory.dart'; import 'package:paintroid/core/commands/command_manager/command_manager.dart'; import 'package:paintroid/core/enums/tool_types.dart'; @@ -10,15 +9,21 @@ abstract class Tool { final CommandFactory commandFactory; final bool hasAddFunctionality; final bool hasFinalizeFunctionality; + ValueKey layerKey; - const Tool({ + Tool({ required this.commandManager, required this.commandFactory, required this.type, required this.hasAddFunctionality, required this.hasFinalizeFunctionality, + required this.layerKey, }); + void updateLayerKey(ValueKey newKey) { + layerKey = newKey; + } + void onDown(Offset point, Paint paint); void onDrag(Offset point, Paint paint); diff --git a/lib/ui/pages/workspace_page/components/layer_menu/layer.dart b/lib/ui/pages/workspace_page/components/layer_menu/layer.dart index 36433df1..7c0928a5 100644 --- a/lib/ui/pages/workspace_page/components/layer_menu/layer.dart +++ b/lib/ui/pages/workspace_page/components/layer_menu/layer.dart @@ -25,7 +25,9 @@ class Layer extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return GestureDetector( onTap: () { - ref.read(layerMenuStateProvider.notifier).toggleSelection(key); + ref + .read(layerMenuStateProvider.notifier) + .toggleSelection(key as ValueKey); }, child: Container( height: 180, @@ -75,9 +77,14 @@ class Layer extends ConsumerWidget { color: Colors.white, borderRadius: BorderRadius.circular(12), ), - child: RawImage( - image: image, - fit: BoxFit.contain, + child: Stack( + children: [ + RawImage( + image: image, + fit: BoxFit.contain, + ), + Text(this.key.toString()), + ], ), ), IconButton( diff --git a/test/unit/command/command_factory_test.dart b/test/unit/command/command_factory_test.dart index 95f952dd..5b449c41 100644 --- a/test/unit/command/command_factory_test.dart +++ b/test/unit/command/command_factory_test.dart @@ -1,7 +1,5 @@ -import 'dart:ui'; - +import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; - import 'package:paintroid/core/commands/command_factory/command_factory.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/path_command.dart'; import 'package:paintroid/core/commands/path_with_action_history.dart'; @@ -18,8 +16,9 @@ void main() { }); test('Should return a valid instance of PathCommand', () { - final expected = PathCommand(testPath, testPaint); - final command = sut.createPathCommand(testPath, testPaint); + final expected = PathCommand(testPath, testPaint, const ValueKey(0)); + final command = + sut.createPathCommand(testPath, testPaint, const ValueKey(0)); expect(command, isA()); expect(command, equals(expected)); }); diff --git a/test/unit/command/draw_path_command_test.dart b/test/unit/command/draw_path_command_test.dart index 46570349..b8eabcbe 100644 --- a/test/unit/command/draw_path_command_test.dart +++ b/test/unit/command/draw_path_command_test.dart @@ -1,11 +1,10 @@ -import 'dart:ui'; - +import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; - import 'package:paintroid/core/commands/command_implementation/graphic/path_command.dart'; import 'package:paintroid/core/commands/path_with_action_history.dart'; + import 'draw_path_command_test.mocks.dart'; @GenerateMocks([Canvas]) @@ -22,7 +21,7 @@ void main() { () { final testPath = PathWithActionHistory(); final testPaint = Paint(); - drawPath = PathCommand(testPath, testPaint); + drawPath = PathCommand(testPath, testPaint, const ValueKey(0)); when(mockCanvas.drawPath(testPath.path, testPaint)).thenReturn(null); drawPath.call(mockCanvas); verify(mockCanvas.drawPath(testPath.path, testPaint)); diff --git a/test/unit/command/shape_command_test.dart b/test/unit/command/shape_command_test.dart index 0b12c22f..5130e672 100644 --- a/test/unit/command/shape_command_test.dart +++ b/test/unit/command/shape_command_test.dart @@ -1,9 +1,10 @@ -import 'dart:ui'; +import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/circle_shape_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/square_shape_command.dart'; + import 'shape_command_test.mocks.dart'; @GenerateMocks([Canvas]) @@ -13,6 +14,7 @@ void main() { final squareShapeCommand = SquareShapeCommand( testPaint, + const ValueKey(0), const Offset(0, 0), const Offset(200, 0), const Offset(0, 200), @@ -21,7 +23,12 @@ void main() { const radius = 5.0; const center = Offset(200, 200); - final circleShapeCommand = CircleShapeCommand(testPaint, radius, center); + final circleShapeCommand = CircleShapeCommand( + testPaint, + const ValueKey(0), + radius, + center, + ); setUp(() => mockCanvas = MockCanvas()); diff --git a/test/unit/serialization/utils/dummy_command_factory.dart b/test/unit/serialization/utils/dummy_command_factory.dart index af32175b..5068394e 100644 --- a/test/unit/serialization/utils/dummy_command_factory.dart +++ b/test/unit/serialization/utils/dummy_command_factory.dart @@ -1,5 +1,4 @@ -import 'dart:ui'; - +import 'package:flutter/cupertino.dart'; import 'package:paintroid/core/commands/command_factory/command_factory.dart'; import 'package:paintroid/core/commands/command_implementation/command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/line_command.dart'; @@ -9,6 +8,7 @@ import 'package:paintroid/core/commands/command_implementation/graphic/shape/squ import 'package:paintroid/core/commands/path_with_action_history.dart'; import 'package:paintroid/core/json_serialization/versioning/serializer_version.dart'; import 'package:paintroid/core/json_serialization/versioning/version_strategy.dart'; + import 'dummy_paint_factory.dart'; import 'dummy_path_factory.dart'; import 'dummy_version_strategy.dart'; @@ -26,8 +26,11 @@ class DummyCommandFactory { PathWithActionHistory originalPath = DummyPathFactory.createPathWithActionHistory(i * numberOfCommands); Paint originalPaint = DummyPaintFactory.createPaint(); - PathCommand command = - commandFactory.createPathCommand(originalPath, originalPaint); + PathCommand command = commandFactory.createPathCommand( + originalPath, + originalPaint, + const ValueKey(0), + ); commands.add(command); } return commands; @@ -40,7 +43,7 @@ class DummyCommandFactory { }) { VersionStrategyManager.setStrategy( DummyVersionStrategy(pathCommandVersion: version)); - return commandFactory.createPathCommand(path, paint); + return commandFactory.createPathCommand(path, paint, const ValueKey(0)); } static LineCommand createLineCommand( @@ -52,7 +55,13 @@ class DummyCommandFactory { }) { VersionStrategyManager.setStrategy( DummyVersionStrategy(lineCommandVersion: version)); - return commandFactory.createLineCommand(path, paint, startPoint, endPoint); + return commandFactory.createLineCommand( + path, + paint, + startPoint, + endPoint, + const ValueKey(0), + ); } static SquareShapeCommand createSquareShapeCommand( @@ -72,6 +81,7 @@ class DummyCommandFactory { topRight, bottomLeft, bottomRight, + const ValueKey(0), ); } @@ -84,7 +94,12 @@ class DummyCommandFactory { VersionStrategyManager.setStrategy( DummyVersionStrategy(circleShapeCommandVersion: version), ); - return commandFactory.createCircleShapeCommand(paint, radius, center); + return commandFactory.createCircleShapeCommand( + paint, + radius, + center, + const ValueKey(0), + ); } static bool compareCommandLists( diff --git a/test/unit/tools/brush_tool_test.dart b/test/unit/tools/brush_tool_test.dart index ed516d63..acb0a140 100644 --- a/test/unit/tools/brush_tool_test.dart +++ b/test/unit/tools/brush_tool_test.dart @@ -1,7 +1,5 @@ -import 'dart:ui'; - +import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; - import 'package:paintroid/core/commands/command_factory/command_factory.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/path_command.dart'; import 'package:paintroid/core/commands/command_manager/command_manager.dart'; @@ -24,6 +22,7 @@ void main() { commandManager: CommandManager(), graphicFactory: const GraphicFactory(), type: ToolType.BRUSH, + layerKey: const ValueKey(0), ); }); diff --git a/test/unit/tools/eraser_tool_test.dart b/test/unit/tools/eraser_tool_test.dart index 3cb7c8ac..252c958b 100644 --- a/test/unit/tools/eraser_tool_test.dart +++ b/test/unit/tools/eraser_tool_test.dart @@ -1,5 +1,5 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; - import 'package:paintroid/core/commands/command_factory/command_factory.dart'; import 'package:paintroid/core/commands/command_manager/command_manager.dart'; import 'package:paintroid/core/commands/graphic_factory/graphic_factory.dart'; @@ -15,6 +15,7 @@ void main() { commandFactory: const CommandFactory(), commandManager: CommandManager(), graphicFactory: const GraphicFactory(), + layerKey: const ValueKey(0), ); }); diff --git a/test/unit/tools/hand_tool_test.dart b/test/unit/tools/hand_tool_test.dart index c65d674a..6a803256 100644 --- a/test/unit/tools/hand_tool_test.dart +++ b/test/unit/tools/hand_tool_test.dart @@ -1,5 +1,5 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; - import 'package:paintroid/core/commands/command_factory/command_factory.dart'; import 'package:paintroid/core/commands/command_manager/command_manager.dart'; import 'package:paintroid/core/enums/tool_types.dart'; @@ -12,6 +12,7 @@ void main() { type: ToolType.HAND, commandFactory: const CommandFactory(), commandManager: CommandManager(), + layerKey: const ValueKey(0), ); }); diff --git a/test/unit/tools/line_tool_test.dart b/test/unit/tools/line_tool_test.dart index 69e619b8..71186b5b 100644 --- a/test/unit/tools/line_tool_test.dart +++ b/test/unit/tools/line_tool_test.dart @@ -1,5 +1,4 @@ -import 'dart:ui'; - +import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:paintroid/core/commands/command_factory/command_factory.dart'; import 'package:paintroid/core/commands/command_manager/command_manager.dart'; @@ -23,6 +22,7 @@ void main() { commandManager: CommandManager(), graphicFactory: const GraphicFactory(), drawingSurfaceSize: const Size(1000, 1000), + layerKey: const ValueKey(0), ); }); diff --git a/test/unit/tools/shapes_tool_test.dart b/test/unit/tools/shapes_tool_test.dart index 428ff370..daa0ab91 100644 --- a/test/unit/tools/shapes_tool_test.dart +++ b/test/unit/tools/shapes_tool_test.dart @@ -1,4 +1,4 @@ -import 'dart:ui'; +import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:paintroid/core/commands/command_factory/command_factory.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/shape/circle_shape_command.dart'; @@ -28,6 +28,7 @@ void main() { boundingBox: boundingBox, isRotating: false, shapeType: ShapeType.square, + layerKey: const ValueKey(0), ); }); diff --git a/test/unit/tools/spray_tool_test.dart b/test/unit/tools/spray_tool_test.dart index 70983ce1..09f65489 100644 --- a/test/unit/tools/spray_tool_test.dart +++ b/test/unit/tools/spray_tool_test.dart @@ -1,5 +1,4 @@ -import 'dart:ui'; - +import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:paintroid/core/commands/command_factory/command_factory.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/spray_command.dart'; @@ -23,6 +22,7 @@ void main() { graphicFactory: const GraphicFactory(), type: ToolType.SPRAY, drawingSurfaceSize: const Size(1000, 1000), + layerKey: const ValueKey(0), ); }); From 2c6c8ae05b44327f36ed971f11e588c20101e781 Mon Sep 17 00:00:00 2001 From: Lenkomotive Date: Sat, 25 Jan 2025 18:22:22 +0100 Subject: [PATCH 16/16] PAINTROID-454: Flutter: Add Layers - remove commands associated with layer --- lib/core/commands/command_manager/command_manager.dart | 10 ++++++++-- .../providers/state/layer_menu_state_provider.dart | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/core/commands/command_manager/command_manager.dart b/lib/core/commands/command_manager/command_manager.dart index 747872b4..b46e52ef 100644 --- a/lib/core/commands/command_manager/command_manager.dart +++ b/lib/core/commands/command_manager/command_manager.dart @@ -1,5 +1,4 @@ -import 'dart:ui'; - +import 'package:flutter/cupertino.dart'; import 'package:paintroid/core/commands/command_implementation/command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/graphic_command.dart'; import 'package:paintroid/core/commands/command_implementation/graphic/line_command.dart'; @@ -130,4 +129,11 @@ class CommandManager { return lineCommands.reversed.toList(); } + + void removeCommandsOfLayer(ValueKey layerKey) { + _undoStack.removeWhere((command) => + (command is GraphicCommand && command.layerKey == layerKey)); + _redoStack.removeWhere((command) => + (command is GraphicCommand && command.layerKey == layerKey)); + } } diff --git a/lib/core/providers/state/layer_menu_state_provider.dart b/lib/core/providers/state/layer_menu_state_provider.dart index 742bad6b..bab64d89 100644 --- a/lib/core/providers/state/layer_menu_state_provider.dart +++ b/lib/core/providers/state/layer_menu_state_provider.dart @@ -1,6 +1,7 @@ import 'dart:ui' as ui; import 'package:flutter/cupertino.dart'; +import 'package:paintroid/core/commands/command_manager/command_manager_provider.dart'; import 'package:paintroid/core/commands/graphic_factory/graphic_factory_provider.dart'; import 'package:paintroid/core/providers/state/canvas_state_provider.dart'; import 'package:paintroid/core/providers/state/layer_menu_state_data.dart'; @@ -94,11 +95,16 @@ class LayerMenuStateProvider extends _$LayerMenuStateProvider { opacity: 1.0, ); updatedLayerList.add(newLayer); + ref.read(toolBoxStateProvider).currentTool.updateLayerKey(newLayer.key); state = state.copyWith(layers: updatedLayerList); } void deleteLayer() { if (state.layers.length == 1) return; + + final layerToDelete = state.layers.firstWhere((layer) => layer.isSelected); + ref.read(commandManagerProvider).removeCommandsOfLayer(layerToDelete.key); + final updatedLayerList = state.layers.where((layer) => !layer.isSelected).toList(); final lastIndex = updatedLayerList.length - 1;