diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..b252778 Binary files /dev/null and b/.DS_Store differ diff --git a/.dart_tool/flutter_build/dart_plugin_registrant.dart b/.dart_tool/flutter_build/dart_plugin_registrant.dart index f6d96d4..4ebc6f4 100644 --- a/.dart_tool/flutter_build/dart_plugin_registrant.dart +++ b/.dart_tool/flutter_build/dart_plugin_registrant.dart @@ -3,7 +3,7 @@ // This file is generated from template in file `flutter_tools/lib/src/flutter_plugins.dart`. // -// @dart = 3.0 +// @dart = 3.7 import 'dart:io'; // flutter_ignore: dart_io_import. import 'package:file_picker/file_picker.dart'; diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json index eb7083e..c22753a 100644 --- a/.dart_tool/package_config.json +++ b/.dart_tool/package_config.json @@ -3,109 +3,109 @@ "packages": [ { "name": "args", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/args-2.7.0", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/args-2.7.0", "packageUri": "lib/", "languageVersion": "3.3" }, { "name": "async", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/async-2.13.0", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/async-2.12.0", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "boolean_selector", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/boolean_selector-2.1.2", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/boolean_selector-2.1.2", "packageUri": "lib/", "languageVersion": "3.1" }, { "name": "characters", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/characters-1.4.0", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/characters-1.4.0", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "clock", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/clock-1.1.2", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/clock-1.1.2", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "collection", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/collection-1.19.1", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/collection-1.19.1", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "cross_file", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/cross_file-0.3.4+2", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/cross_file-0.3.4+2", "packageUri": "lib/", "languageVersion": "3.3" }, { "name": "crypto", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/crypto-3.0.6", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/crypto-3.0.6", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "cupertino_icons", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/cupertino_icons-1.0.8", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/cupertino_icons-1.0.8", "packageUri": "lib/", "languageVersion": "3.1" }, { "name": "fake_async", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/fake_async-1.3.3", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/fake_async-1.3.2", "packageUri": "lib/", "languageVersion": "3.3" }, { "name": "ffi", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/ffi-2.1.4", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/ffi-2.1.4", "packageUri": "lib/", "languageVersion": "3.7" }, { "name": "file_picker", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/file_picker-10.2.0", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/file_picker-10.2.0", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "fixnum", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/fixnum-1.1.1", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/fixnum-1.1.1", "packageUri": "lib/", "languageVersion": "3.1" }, { "name": "flutter", - "rootUri": "file:///home/tba/flutter/flutter/packages/flutter", + "rootUri": "file:///Users/rujindevkota/Devlopment/flutter/packages/flutter", "packageUri": "lib/", "languageVersion": "3.7" }, { "name": "flutter_colorpicker", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/flutter_colorpicker-1.1.0", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/flutter_colorpicker-1.1.0", "packageUri": "lib/", "languageVersion": "2.14" }, { "name": "flutter_lints", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/flutter_lints-3.0.2", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/flutter_lints-3.0.2", "packageUri": "lib/", "languageVersion": "3.1" }, { "name": "flutter_plugin_android_lifecycle", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.28", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.28", "packageUri": "lib/", "languageVersion": "3.6" }, { "name": "flutter_riverpod", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/flutter_riverpod-2.6.1", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/flutter_riverpod-2.6.1", "packageUri": "lib/", "languageVersion": "2.17" }, @@ -117,242 +117,243 @@ }, { "name": "flutter_test", - "rootUri": "file:///home/tba/flutter/flutter/packages/flutter_test", + "rootUri": "file:///Users/rujindevkota/Devlopment/flutter/packages/flutter_test", "packageUri": "lib/", "languageVersion": "3.7" }, { "name": "flutter_web_plugins", - "rootUri": "file:///home/tba/flutter/flutter/packages/flutter_web_plugins", + "rootUri": "file:///Users/rujindevkota/Devlopment/flutter/packages/flutter_web_plugins", "packageUri": "lib/", "languageVersion": "3.7" }, { "name": "go_router", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/go_router-12.1.3", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/go_router-12.1.3", "packageUri": "lib/", "languageVersion": "3.0" }, { "name": "google_identity_services_web", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/google_identity_services_web-0.3.3+1", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/google_identity_services_web-0.3.3+1", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "googleapis_auth", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/googleapis_auth-2.0.0", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/googleapis_auth-2.0.0", "packageUri": "lib/", "languageVersion": "3.6" }, { "name": "grpc", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/grpc-4.1.0", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/grpc-4.1.0", "packageUri": "lib/", "languageVersion": "3.5" }, { "name": "http", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/http-1.4.0", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/http-1.4.0", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "http2", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/http2-2.3.1", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/http2-2.3.1", "packageUri": "lib/", "languageVersion": "3.2" }, { "name": "http_parser", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/http_parser-4.1.2", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/http_parser-4.1.2", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "leak_tracker", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/leak_tracker-10.0.9", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/leak_tracker-10.0.8", "packageUri": "lib/", "languageVersion": "3.2" }, { "name": "leak_tracker_flutter_testing", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.9", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.9", "packageUri": "lib/", "languageVersion": "3.2" }, { "name": "leak_tracker_testing", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/leak_tracker_testing-3.0.1", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/leak_tracker_testing-3.0.1", "packageUri": "lib/", "languageVersion": "3.2" }, { "name": "lints", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/lints-3.0.0", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/lints-3.0.0", "packageUri": "lib/", "languageVersion": "3.0" }, { "name": "logging", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/logging-1.3.0", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/logging-1.3.0", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "matcher", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/matcher-0.12.17", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/matcher-0.12.17", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "material_color_utilities", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/material_color_utilities-0.11.1", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/material_color_utilities-0.11.1", "packageUri": "lib/", "languageVersion": "2.17" }, { "name": "meta", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/meta-1.16.0", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/meta-1.16.0", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "nested", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/nested-1.0.0", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/nested-1.0.0", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "path", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/path-1.9.1", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/path-1.9.1", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "plugin_platform_interface", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/plugin_platform_interface-2.1.8", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/plugin_platform_interface-2.1.8", "packageUri": "lib/", "languageVersion": "3.0" }, { "name": "protobuf", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/protobuf-4.1.0", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/protobuf-4.1.0", "packageUri": "lib/", "languageVersion": "3.6" }, { "name": "provider", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/provider-6.1.5", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/provider-6.1.5", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "riverpod", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/riverpod-2.6.1", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/riverpod-2.6.1", "packageUri": "lib/", "languageVersion": "2.17" }, { "name": "sky_engine", - "rootUri": "file:///home/tba/flutter/flutter/bin/cache/pkg/sky_engine", + "rootUri": "file:///Users/rujindevkota/Devlopment/flutter/bin/cache/pkg/sky_engine", "packageUri": "lib/", "languageVersion": "3.7" }, { "name": "source_span", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/source_span-1.10.1", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/source_span-1.10.1", "packageUri": "lib/", "languageVersion": "3.1" }, { "name": "sprintf", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/sprintf-7.0.0", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/sprintf-7.0.0", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "stack_trace", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/stack_trace-1.12.1", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/stack_trace-1.12.1", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "state_notifier", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/state_notifier-1.0.0", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/state_notifier-1.0.0", "packageUri": "lib/", "languageVersion": "2.12" }, { "name": "stream_channel", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/stream_channel-2.1.4", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/stream_channel-2.1.4", "packageUri": "lib/", "languageVersion": "3.3" }, { "name": "string_scanner", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/string_scanner-1.4.1", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/string_scanner-1.4.1", "packageUri": "lib/", "languageVersion": "3.1" }, { "name": "term_glyph", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/term_glyph-1.2.2", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/term_glyph-1.2.2", "packageUri": "lib/", "languageVersion": "3.1" }, { "name": "test_api", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/test_api-0.7.4", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/test_api-0.7.4", "packageUri": "lib/", "languageVersion": "3.5" }, { "name": "typed_data", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/typed_data-1.4.0", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/typed_data-1.4.0", "packageUri": "lib/", "languageVersion": "3.5" }, { "name": "uuid", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/uuid-4.5.1", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/uuid-4.5.1", "packageUri": "lib/", "languageVersion": "3.0" }, { "name": "vector_math", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/vector_math-2.1.4", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/vector_math-2.1.4", "packageUri": "lib/", "languageVersion": "2.14" }, { "name": "vm_service", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/vm_service-15.0.0", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/vm_service-14.3.1", "packageUri": "lib/", "languageVersion": "3.3" }, { "name": "web", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/web-1.1.1", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/web-1.1.1", "packageUri": "lib/", "languageVersion": "3.4" }, { "name": "win32", - "rootUri": "file:///home/tba/.pub-cache/hosted/pub.dev/win32-5.14.0", + "rootUri": "file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/win32-5.13.0", "packageUri": "lib/", - "languageVersion": "3.8" + "languageVersion": "3.7" }, { "name": "flutter_sdui_frontend", "rootUri": "../", "packageUri": "lib/", - "languageVersion": "3.0" + "languageVersion": "3.7" } ], + "generated": "2025-09-30T12:47:06.679896Z", "generator": "pub", - "generatorVersion": "3.8.0", - "flutterRoot": "file:///home/tba/flutter/flutter", - "flutterVersion": "3.32.0", - "pubCache": "file:///home/tba/.pub-cache" + "generatorVersion": "3.7.2", + "flutterRoot": "file:///Users/rujindevkota/Devlopment/flutter", + "flutterVersion": "3.29.2", + "pubCache": "file:///Users/rujindevkota/.pub-cache" } diff --git a/.dart_tool/package_config_subset b/.dart_tool/package_config_subset index f881bc8..cac79bd 100644 --- a/.dart_tool/package_config_subset +++ b/.dart_tool/package_config_subset @@ -1,233 +1,233 @@ args 3.3 -file:///home/tba/.pub-cache/hosted/pub.dev/args-2.7.0/ -file:///home/tba/.pub-cache/hosted/pub.dev/args-2.7.0/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/args-2.7.0/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/args-2.7.0/lib/ async 3.4 -file:///home/tba/.pub-cache/hosted/pub.dev/async-2.13.0/ -file:///home/tba/.pub-cache/hosted/pub.dev/async-2.13.0/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/async-2.12.0/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/async-2.12.0/lib/ boolean_selector 3.1 -file:///home/tba/.pub-cache/hosted/pub.dev/boolean_selector-2.1.2/ -file:///home/tba/.pub-cache/hosted/pub.dev/boolean_selector-2.1.2/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/boolean_selector-2.1.2/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/boolean_selector-2.1.2/lib/ characters 3.4 -file:///home/tba/.pub-cache/hosted/pub.dev/characters-1.4.0/ -file:///home/tba/.pub-cache/hosted/pub.dev/characters-1.4.0/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/characters-1.4.0/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/characters-1.4.0/lib/ clock 3.4 -file:///home/tba/.pub-cache/hosted/pub.dev/clock-1.1.2/ -file:///home/tba/.pub-cache/hosted/pub.dev/clock-1.1.2/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/clock-1.1.2/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/clock-1.1.2/lib/ collection 3.4 -file:///home/tba/.pub-cache/hosted/pub.dev/collection-1.19.1/ -file:///home/tba/.pub-cache/hosted/pub.dev/collection-1.19.1/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/collection-1.19.1/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/collection-1.19.1/lib/ cross_file 3.3 -file:///home/tba/.pub-cache/hosted/pub.dev/cross_file-0.3.4+2/ -file:///home/tba/.pub-cache/hosted/pub.dev/cross_file-0.3.4+2/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/cross_file-0.3.4+2/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/cross_file-0.3.4+2/lib/ crypto 3.4 -file:///home/tba/.pub-cache/hosted/pub.dev/crypto-3.0.6/ -file:///home/tba/.pub-cache/hosted/pub.dev/crypto-3.0.6/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/crypto-3.0.6/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/crypto-3.0.6/lib/ cupertino_icons 3.1 -file:///home/tba/.pub-cache/hosted/pub.dev/cupertino_icons-1.0.8/ -file:///home/tba/.pub-cache/hosted/pub.dev/cupertino_icons-1.0.8/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/cupertino_icons-1.0.8/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/cupertino_icons-1.0.8/lib/ fake_async 3.3 -file:///home/tba/.pub-cache/hosted/pub.dev/fake_async-1.3.3/ -file:///home/tba/.pub-cache/hosted/pub.dev/fake_async-1.3.3/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/fake_async-1.3.2/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/fake_async-1.3.2/lib/ ffi 3.7 -file:///home/tba/.pub-cache/hosted/pub.dev/ffi-2.1.4/ -file:///home/tba/.pub-cache/hosted/pub.dev/ffi-2.1.4/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/ffi-2.1.4/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/ffi-2.1.4/lib/ file_picker 3.4 -file:///home/tba/.pub-cache/hosted/pub.dev/file_picker-10.2.0/ -file:///home/tba/.pub-cache/hosted/pub.dev/file_picker-10.2.0/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/file_picker-10.2.0/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/file_picker-10.2.0/lib/ fixnum 3.1 -file:///home/tba/.pub-cache/hosted/pub.dev/fixnum-1.1.1/ -file:///home/tba/.pub-cache/hosted/pub.dev/fixnum-1.1.1/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/fixnum-1.1.1/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/fixnum-1.1.1/lib/ flutter_colorpicker 2.14 -file:///home/tba/.pub-cache/hosted/pub.dev/flutter_colorpicker-1.1.0/ -file:///home/tba/.pub-cache/hosted/pub.dev/flutter_colorpicker-1.1.0/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/flutter_colorpicker-1.1.0/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/flutter_colorpicker-1.1.0/lib/ flutter_lints 3.1 -file:///home/tba/.pub-cache/hosted/pub.dev/flutter_lints-3.0.2/ -file:///home/tba/.pub-cache/hosted/pub.dev/flutter_lints-3.0.2/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/flutter_lints-3.0.2/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/flutter_lints-3.0.2/lib/ flutter_plugin_android_lifecycle 3.6 -file:///home/tba/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.28/ -file:///home/tba/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.28/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.28/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.28/lib/ flutter_riverpod 2.17 -file:///home/tba/.pub-cache/hosted/pub.dev/flutter_riverpod-2.6.1/ -file:///home/tba/.pub-cache/hosted/pub.dev/flutter_riverpod-2.6.1/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/flutter_riverpod-2.6.1/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/flutter_riverpod-2.6.1/lib/ go_router 3.0 -file:///home/tba/.pub-cache/hosted/pub.dev/go_router-12.1.3/ -file:///home/tba/.pub-cache/hosted/pub.dev/go_router-12.1.3/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/go_router-12.1.3/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/go_router-12.1.3/lib/ google_identity_services_web 3.4 -file:///home/tba/.pub-cache/hosted/pub.dev/google_identity_services_web-0.3.3+1/ -file:///home/tba/.pub-cache/hosted/pub.dev/google_identity_services_web-0.3.3+1/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/google_identity_services_web-0.3.3+1/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/google_identity_services_web-0.3.3+1/lib/ googleapis_auth 3.6 -file:///home/tba/.pub-cache/hosted/pub.dev/googleapis_auth-2.0.0/ -file:///home/tba/.pub-cache/hosted/pub.dev/googleapis_auth-2.0.0/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/googleapis_auth-2.0.0/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/googleapis_auth-2.0.0/lib/ grpc 3.5 -file:///home/tba/.pub-cache/hosted/pub.dev/grpc-4.1.0/ -file:///home/tba/.pub-cache/hosted/pub.dev/grpc-4.1.0/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/grpc-4.1.0/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/grpc-4.1.0/lib/ http 3.4 -file:///home/tba/.pub-cache/hosted/pub.dev/http-1.4.0/ -file:///home/tba/.pub-cache/hosted/pub.dev/http-1.4.0/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/http-1.4.0/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/http-1.4.0/lib/ http2 3.2 -file:///home/tba/.pub-cache/hosted/pub.dev/http2-2.3.1/ -file:///home/tba/.pub-cache/hosted/pub.dev/http2-2.3.1/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/http2-2.3.1/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/http2-2.3.1/lib/ http_parser 3.4 -file:///home/tba/.pub-cache/hosted/pub.dev/http_parser-4.1.2/ -file:///home/tba/.pub-cache/hosted/pub.dev/http_parser-4.1.2/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/http_parser-4.1.2/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/http_parser-4.1.2/lib/ leak_tracker 3.2 -file:///home/tba/.pub-cache/hosted/pub.dev/leak_tracker-10.0.9/ -file:///home/tba/.pub-cache/hosted/pub.dev/leak_tracker-10.0.9/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/leak_tracker-10.0.8/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/leak_tracker-10.0.8/lib/ leak_tracker_flutter_testing 3.2 -file:///home/tba/.pub-cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.9/ -file:///home/tba/.pub-cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.9/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.9/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.9/lib/ leak_tracker_testing 3.2 -file:///home/tba/.pub-cache/hosted/pub.dev/leak_tracker_testing-3.0.1/ -file:///home/tba/.pub-cache/hosted/pub.dev/leak_tracker_testing-3.0.1/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/leak_tracker_testing-3.0.1/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/leak_tracker_testing-3.0.1/lib/ lints 3.0 -file:///home/tba/.pub-cache/hosted/pub.dev/lints-3.0.0/ -file:///home/tba/.pub-cache/hosted/pub.dev/lints-3.0.0/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/lints-3.0.0/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/lints-3.0.0/lib/ logging 3.4 -file:///home/tba/.pub-cache/hosted/pub.dev/logging-1.3.0/ -file:///home/tba/.pub-cache/hosted/pub.dev/logging-1.3.0/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/logging-1.3.0/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/logging-1.3.0/lib/ matcher 3.4 -file:///home/tba/.pub-cache/hosted/pub.dev/matcher-0.12.17/ -file:///home/tba/.pub-cache/hosted/pub.dev/matcher-0.12.17/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/matcher-0.12.17/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/matcher-0.12.17/lib/ material_color_utilities 2.17 -file:///home/tba/.pub-cache/hosted/pub.dev/material_color_utilities-0.11.1/ -file:///home/tba/.pub-cache/hosted/pub.dev/material_color_utilities-0.11.1/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/material_color_utilities-0.11.1/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/material_color_utilities-0.11.1/lib/ meta 2.12 -file:///home/tba/.pub-cache/hosted/pub.dev/meta-1.16.0/ -file:///home/tba/.pub-cache/hosted/pub.dev/meta-1.16.0/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/meta-1.16.0/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/meta-1.16.0/lib/ nested 2.12 -file:///home/tba/.pub-cache/hosted/pub.dev/nested-1.0.0/ -file:///home/tba/.pub-cache/hosted/pub.dev/nested-1.0.0/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/nested-1.0.0/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/nested-1.0.0/lib/ path 3.4 -file:///home/tba/.pub-cache/hosted/pub.dev/path-1.9.1/ -file:///home/tba/.pub-cache/hosted/pub.dev/path-1.9.1/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/path-1.9.1/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/path-1.9.1/lib/ plugin_platform_interface 3.0 -file:///home/tba/.pub-cache/hosted/pub.dev/plugin_platform_interface-2.1.8/ -file:///home/tba/.pub-cache/hosted/pub.dev/plugin_platform_interface-2.1.8/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/plugin_platform_interface-2.1.8/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/plugin_platform_interface-2.1.8/lib/ protobuf 3.6 -file:///home/tba/.pub-cache/hosted/pub.dev/protobuf-4.1.0/ -file:///home/tba/.pub-cache/hosted/pub.dev/protobuf-4.1.0/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/protobuf-4.1.0/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/protobuf-4.1.0/lib/ provider 2.12 -file:///home/tba/.pub-cache/hosted/pub.dev/provider-6.1.5/ -file:///home/tba/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/provider-6.1.5/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/ riverpod 2.17 -file:///home/tba/.pub-cache/hosted/pub.dev/riverpod-2.6.1/ -file:///home/tba/.pub-cache/hosted/pub.dev/riverpod-2.6.1/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/riverpod-2.6.1/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/riverpod-2.6.1/lib/ source_span 3.1 -file:///home/tba/.pub-cache/hosted/pub.dev/source_span-1.10.1/ -file:///home/tba/.pub-cache/hosted/pub.dev/source_span-1.10.1/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/source_span-1.10.1/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/source_span-1.10.1/lib/ sprintf 2.12 -file:///home/tba/.pub-cache/hosted/pub.dev/sprintf-7.0.0/ -file:///home/tba/.pub-cache/hosted/pub.dev/sprintf-7.0.0/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/sprintf-7.0.0/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/sprintf-7.0.0/lib/ stack_trace 3.4 -file:///home/tba/.pub-cache/hosted/pub.dev/stack_trace-1.12.1/ -file:///home/tba/.pub-cache/hosted/pub.dev/stack_trace-1.12.1/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/stack_trace-1.12.1/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/stack_trace-1.12.1/lib/ state_notifier 2.12 -file:///home/tba/.pub-cache/hosted/pub.dev/state_notifier-1.0.0/ -file:///home/tba/.pub-cache/hosted/pub.dev/state_notifier-1.0.0/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/state_notifier-1.0.0/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/state_notifier-1.0.0/lib/ stream_channel 3.3 -file:///home/tba/.pub-cache/hosted/pub.dev/stream_channel-2.1.4/ -file:///home/tba/.pub-cache/hosted/pub.dev/stream_channel-2.1.4/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/stream_channel-2.1.4/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/stream_channel-2.1.4/lib/ string_scanner 3.1 -file:///home/tba/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/ -file:///home/tba/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/ term_glyph 3.1 -file:///home/tba/.pub-cache/hosted/pub.dev/term_glyph-1.2.2/ -file:///home/tba/.pub-cache/hosted/pub.dev/term_glyph-1.2.2/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/term_glyph-1.2.2/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/term_glyph-1.2.2/lib/ test_api 3.5 -file:///home/tba/.pub-cache/hosted/pub.dev/test_api-0.7.4/ -file:///home/tba/.pub-cache/hosted/pub.dev/test_api-0.7.4/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/test_api-0.7.4/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/test_api-0.7.4/lib/ typed_data 3.5 -file:///home/tba/.pub-cache/hosted/pub.dev/typed_data-1.4.0/ -file:///home/tba/.pub-cache/hosted/pub.dev/typed_data-1.4.0/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/typed_data-1.4.0/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/typed_data-1.4.0/lib/ uuid 3.0 -file:///home/tba/.pub-cache/hosted/pub.dev/uuid-4.5.1/ -file:///home/tba/.pub-cache/hosted/pub.dev/uuid-4.5.1/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/uuid-4.5.1/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/uuid-4.5.1/lib/ vector_math 2.14 -file:///home/tba/.pub-cache/hosted/pub.dev/vector_math-2.1.4/ -file:///home/tba/.pub-cache/hosted/pub.dev/vector_math-2.1.4/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/vector_math-2.1.4/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/vector_math-2.1.4/lib/ vm_service 3.3 -file:///home/tba/.pub-cache/hosted/pub.dev/vm_service-15.0.0/ -file:///home/tba/.pub-cache/hosted/pub.dev/vm_service-15.0.0/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/vm_service-14.3.1/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/vm_service-14.3.1/lib/ web 3.4 -file:///home/tba/.pub-cache/hosted/pub.dev/web-1.1.1/ -file:///home/tba/.pub-cache/hosted/pub.dev/web-1.1.1/lib/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/web-1.1.1/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/web-1.1.1/lib/ win32 -3.8 -file:///home/tba/.pub-cache/hosted/pub.dev/win32-5.14.0/ -file:///home/tba/.pub-cache/hosted/pub.dev/win32-5.14.0/lib/ +3.7 +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/win32-5.13.0/ +file:///Users/rujindevkota/.pub-cache/hosted/pub.dev/win32-5.13.0/lib/ sky_engine 3.7 -file:///home/tba/flutter/flutter/bin/cache/pkg/sky_engine/ -file:///home/tba/flutter/flutter/bin/cache/pkg/sky_engine/lib/ +file:///Users/rujindevkota/Devlopment/flutter/bin/cache/pkg/sky_engine/ +file:///Users/rujindevkota/Devlopment/flutter/bin/cache/pkg/sky_engine/lib/ flutter 3.7 -file:///home/tba/flutter/flutter/packages/flutter/ -file:///home/tba/flutter/flutter/packages/flutter/lib/ +file:///Users/rujindevkota/Devlopment/flutter/packages/flutter/ +file:///Users/rujindevkota/Devlopment/flutter/packages/flutter/lib/ flutter_test 3.7 -file:///home/tba/flutter/flutter/packages/flutter_test/ -file:///home/tba/flutter/flutter/packages/flutter_test/lib/ +file:///Users/rujindevkota/Devlopment/flutter/packages/flutter_test/ +file:///Users/rujindevkota/Devlopment/flutter/packages/flutter_test/lib/ flutter_web_plugins 3.7 -file:///home/tba/flutter/flutter/packages/flutter_web_plugins/ -file:///home/tba/flutter/flutter/packages/flutter_web_plugins/lib/ +file:///Users/rujindevkota/Devlopment/flutter/packages/flutter_web_plugins/ +file:///Users/rujindevkota/Devlopment/flutter/packages/flutter_web_plugins/lib/ flutter_sdui_frontend -3.0 -file:///media/tba/files/projects/flutter-sdui-frontend/ -file:///media/tba/files/projects/flutter-sdui-frontend/lib/ +3.7 +file:///Users/rujindevkota/Documents/projects/GDSC/flutter-sdui-frontend/ +file:///Users/rujindevkota/Documents/projects/GDSC/flutter-sdui-frontend/lib/ flutter_sdui 3.0 -file:///media/tba/files/projects/flutter-sdui-package/ -file:///media/tba/files/projects/flutter-sdui-package/lib/ +file:///Users/rujindevkota/Documents/projects/GDSC/flutter-sdui-package/ +file:///Users/rujindevkota/Documents/projects/GDSC/flutter-sdui-package/lib/ 2 diff --git a/.dart_tool/version b/.dart_tool/version index 40fc726..b658d1f 100644 --- a/.dart_tool/version +++ b/.dart_tool/version @@ -1 +1 @@ -3.32.0 \ No newline at end of file +3.29.2 \ No newline at end of file diff --git a/.flutter-plugins b/.flutter-plugins new file mode 100644 index 0000000..e31d7b0 --- /dev/null +++ b/.flutter-plugins @@ -0,0 +1,3 @@ +# This is a generated file; do not edit or check into version control. +file_picker=/Users/rujindevkota/.pub-cache/hosted/pub.dev/file_picker-10.2.0/ +flutter_plugin_android_lifecycle=/Users/rujindevkota/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.28/ diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies index 1395fc0..8cc0f8d 100644 --- a/.flutter-plugins-dependencies +++ b/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"file_picker","path":"/home/tba/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"file_picker","path":"/home/tba/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"],"dev_dependency":false},{"name":"flutter_plugin_android_lifecycle","path":"/home/tba/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.28/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"file_picker","path":"/home/tba/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"file_picker","path":"/home/tba/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":false,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"file_picker","path":"/home/tba/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":false,"dependencies":[],"dev_dependency":false}],"web":[{"name":"file_picker","path":"/home/tba/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"file_picker","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]}],"date_created":"2025-07-01 15:05:43.952431","version":"3.32.0","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"file_picker","path":"/Users/rujindevkota/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"file_picker","path":"/Users/rujindevkota/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":true,"dependencies":["flutter_plugin_android_lifecycle"],"dev_dependency":false},{"name":"flutter_plugin_android_lifecycle","path":"/Users/rujindevkota/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.28/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"file_picker","path":"/Users/rujindevkota/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"file_picker","path":"/Users/rujindevkota/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":false,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"file_picker","path":"/Users/rujindevkota/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","native_build":false,"dependencies":[],"dev_dependency":false}],"web":[{"name":"file_picker","path":"/Users/rujindevkota/.pub-cache/hosted/pub.dev/file_picker-10.2.0/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"file_picker","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]}],"date_created":"2025-10-01 11:52:47.449537","version":"3.29.2","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f325a05 --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +.macos/ +.metadata +macos/ + + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/lib/models/widget_registry_data.dart b/lib/models/widget_registry_data.dart index fc35b63..0c86981 100644 --- a/lib/models/widget_registry_data.dart +++ b/lib/models/widget_registry_data.dart @@ -5,7 +5,7 @@ import 'widget_registry_entry.dart'; final List widgetRegistryEntries = [ // ... all WidgetRegistryEntry definitions for SDUI widgets ... - WidgetRegistryEntry( + const WidgetRegistryEntry( type: 'SduiColumn', label: 'SduiColumn', icon: Icons.view_column, @@ -53,7 +53,7 @@ final List widgetRegistryEntries = [ defaultSize: const Size(200, 300), ), - WidgetRegistryEntry( + const WidgetRegistryEntry( type: 'SduiRow', label: 'SduiRow', icon: Icons.view_stream, @@ -101,7 +101,7 @@ final List widgetRegistryEntries = [ defaultSize: const Size(200, 300), ), - WidgetRegistryEntry( + const WidgetRegistryEntry( type: 'SduiContainer', label: 'SduiContainer', icon: Icons.crop_square, @@ -110,39 +110,41 @@ final List widgetRegistryEntries = [ maxChildren: 1, canHaveChildren: true, propertyDefinitions: [ - PropertyDefinition( - 'mainAxisAlignment', 'Main Axis Alignment', PropertyType.dropdown, + PropertyDefinition('width', 'Width', PropertyType.number), + PropertyDefinition('height', 'Height', PropertyType.number), + PropertyDefinition('color', 'Color', PropertyType.color), + PropertyDefinition('padding', 'Padding', PropertyType.number), + PropertyDefinition('margin', 'Margin', PropertyType.number), + PropertyDefinition('alignment', 'Alignment', PropertyType.dropdown, options: [ - 'start', - 'end', + 'topLeft', + 'topCenter', + 'topRight', + 'centerLeft', 'center', - 'spaceBetween', - 'spaceAround', - 'spaceEvenly' + 'centerRight', + 'bottomLeft', + 'bottomCenter', + 'bottomRight' ]), + PropertyDefinition( + 'borderRadius', 'Border Radius', PropertyType.number), PropertyDefinition( - 'crossAxisAlignment', 'Cross Axis Alignment', PropertyType.dropdown, - options: ['start', 'end', 'center', 'stretch', 'baseline']), - PropertyDefinition( - 'mainAxisSize', 'Main Axis Size', PropertyType.dropdown, - options: ['min', 'max']), - PropertyDefinition( - 'textDirection', 'Text Direction', PropertyType.dropdown, - options: ['ltr', 'rtl']), - PropertyDefinition( - 'verticalDirection', 'Vertical Direction', PropertyType.dropdown, - options: ['down', 'up']), + 'borderColor', 'Border Color', PropertyType.color), + PropertyDefinition( + 'borderWidth', 'Border Width', PropertyType.number), PropertyDefinition( - 'textBaseline', 'Text Baseline', PropertyType.dropdown, - options: ['alphabetic', 'ideographic']), + 'shadowColor', 'Shadow Color', PropertyType.color), + PropertyDefinition( + 'shadowOffsetX', 'Shadow Offset X', PropertyType.number), + PropertyDefinition( + 'shadowOffsetY', 'Shadow Offset Y', PropertyType.number), + PropertyDefinition( + 'shadowBlurRadius', 'Shadow Blur Radius', PropertyType.number), + PropertyDefinition( + 'shadowSpreadRadius', 'Shadow Spread Radius', PropertyType.number), ], defaultProperties: { - 'mainAxisAlignment': 'start', - 'mainAxisSize': 'max', - 'crossAxisAlignment': 'center', - 'textDirection': null, - 'verticalDirection': 'down', - 'textBaseline': null, 'width': 200.0, 'height': 100.0, 'color': 0xFFFFFFFF, @@ -152,12 +154,16 @@ final List widgetRegistryEntries = [ 'borderRadius': 0.0, 'borderColor': 0xFF232526, 'borderWidth': 0.0, - 'boxShadow': null, + 'shadowColor': 0x00000000, + 'shadowOffsetX': 0.0, + 'shadowOffsetY': 0.0, + 'shadowBlurRadius': 0.0, + 'shadowSpreadRadius': 0.0, }, defaultSize: const Size(200, 100), ), - WidgetRegistryEntry( + const WidgetRegistryEntry( type: 'SduiScaffold', label: 'SduiScaffold', icon: Icons.web_asset, @@ -166,6 +172,10 @@ final List widgetRegistryEntries = [ maxChildren: 1, canHaveChildren: true, propertyDefinitions: [ + PropertyDefinition('showAppBar', 'Show AppBar', PropertyType.boolean), + PropertyDefinition('appBarTitle', 'AppBar Title', PropertyType.text), + PropertyDefinition( + 'appBarBackgroundColor', 'AppBar Background Color', PropertyType.color), PropertyDefinition( 'backgroundColor', 'Background Color', PropertyType.color), PropertyDefinition('resizeToAvoidBottomInset', @@ -187,6 +197,9 @@ final List widgetRegistryEntries = [ 'End Drawer Enable Open Drag Gesture', PropertyType.boolean), ], defaultProperties: { + 'showAppBar': true, + 'appBarTitle': 'AppBar', + 'appBarBackgroundColor': null, 'backgroundColor': null, 'resizeToAvoidBottomInset': null, 'primary': true, @@ -201,7 +214,7 @@ final List widgetRegistryEntries = [ defaultSize: const Size(200, 300), ), - WidgetRegistryEntry( + const WidgetRegistryEntry( type: 'SduiSizedBox', label: 'SduiSizedBox', icon: Icons.crop_16_9, @@ -220,7 +233,7 @@ final List widgetRegistryEntries = [ defaultSize: const Size(100, 40), ), - WidgetRegistryEntry( + const WidgetRegistryEntry( type: 'SduiSpacer', label: 'SduiSpacer', icon: Icons.space_bar, @@ -237,7 +250,7 @@ final List widgetRegistryEntries = [ defaultSize: const Size(0, 0), ), - WidgetRegistryEntry( + const WidgetRegistryEntry( type: 'SduiText', label: 'SduiText', icon: Icons.text_fields, @@ -268,13 +281,17 @@ final List widgetRegistryEntries = [ 'textAlign', 'Text Align', PropertyType.dropdown, options: ['left', 'right', 'center', 'justify']), PropertyDefinition( - 'fontFamily', 'Font Family', PropertyType.dropdown, options: [ - 'Roboto', - 'Arial', - 'Helvetica', - 'Times New Roman', - 'Courier New' - ]), + 'fontFamily', 'Font Family', PropertyType.dropdown, + options: [ + 'Roboto', + 'Arial', + 'Helvetica', + 'Times New Roman', + 'Courier New', + 'Montserrat', + 'Lato', + 'Poppins' + ]), PropertyDefinition( 'letterSpacing', 'Letter Spacing', PropertyType.number), PropertyDefinition( @@ -310,7 +327,7 @@ final List widgetRegistryEntries = [ defaultSize: const Size(100, 60), ), - WidgetRegistryEntry( + const WidgetRegistryEntry( type: 'SduiImage', label: 'SduiImage', icon: Icons.image, @@ -332,6 +349,8 @@ final List widgetRegistryEntries = [ 'none', 'scaleDown' ]), + PropertyDefinition( + 'borderRadius', 'Border Radius', PropertyType.number), PropertyDefinition( 'alignment', 'Alignment', PropertyType.dropdown, options: [ @@ -418,7 +437,7 @@ final List widgetRegistryEntries = [ defaultSize: const Size(100, 60), ), - WidgetRegistryEntry( + const WidgetRegistryEntry( type: 'SduiIcon', label: 'SduiIcon', icon: Icons.insert_emoticon, diff --git a/lib/services/code_generator_service.dart b/lib/services/code_generator_service.dart index 20fcf6f..146afcc 100644 --- a/lib/services/code_generator_service.dart +++ b/lib/services/code_generator_service.dart @@ -1,8 +1,6 @@ import '../models/widget_node.dart'; import '../models/app_theme.dart'; -import 'package:flutter_sdui/flutter_sdui.dart'; import '../viewmodels/design_canvas_viewmodel.dart'; -import 'dart:convert'; class CodeGeneratorService { static String generateCode(WidgetNode scaffoldWidget, AppTheme theme) { @@ -45,6 +43,7 @@ class CodeGeneratorService { switch (type) { case 'SduiScaffold': buffer.write('SduiScaffold('); + if (sduiWidget.appBar != null) buffer.write('\n${indentStr}appBar: ${_generateSduiWidgetCode(sduiWidget.appBar, indent + 1)},'); if (sduiWidget.backgroundColor != null) buffer.write('\n${indentStr}backgroundColor: Color(${sduiWidget.backgroundColor.value}),'); if (sduiWidget.body != null) buffer.write('\n${indentStr}body: ${_generateSduiWidgetCode(sduiWidget.body, indent + 1)},'); buffer.write('\n${' ' * (indent - 1)})'); @@ -118,6 +117,21 @@ class CodeGeneratorService { if (sduiWidget.child != null) buffer.write('child: ${_generateSduiWidgetCode(sduiWidget.child, indent + 1)},'); buffer.write(')'); break; + case 'SduiAppBar': + buffer.write('SduiAppBar('); + if (sduiWidget.title != null) buffer.write("title: '${sduiWidget.title}',"); + if (sduiWidget.backgroundColor != null) buffer.write('backgroundColor: Color(${sduiWidget.backgroundColor.value}),'); + if (sduiWidget.foregroundColor != null) buffer.write('foregroundColor: Color(${sduiWidget.foregroundColor.value}),'); + if (sduiWidget.elevation != null) buffer.write('elevation: ${sduiWidget.elevation},'); + if (sduiWidget.centerTitle != null) buffer.write('centerTitle: ${sduiWidget.centerTitle},'); + if (sduiWidget.toolbarHeight != null) buffer.write('toolbarHeight: ${sduiWidget.toolbarHeight},'); + if (sduiWidget.leadingWidth != null) buffer.write('leadingWidth: ${sduiWidget.leadingWidth},'); + if (sduiWidget.automaticallyImplyLeading != null) buffer.write('automaticallyImplyLeading: ${sduiWidget.automaticallyImplyLeading},'); + if (sduiWidget.titleSpacing != null) buffer.write('titleSpacing: ${sduiWidget.titleSpacing},'); + if (sduiWidget.toolbarOpacity != null) buffer.write('toolbarOpacity: ${sduiWidget.toolbarOpacity},'); + if (sduiWidget.bottomOpacity != null) buffer.write('bottomOpacity: ${sduiWidget.bottomOpacity},'); + buffer.write(')'); + break; default: buffer.write('Container()'); } @@ -268,12 +282,4 @@ class CodeGeneratorService { return buffer.toString(); } - static String _prettyPrintJson(dynamic json) { - // Pretty-print JSON for code view - try { - return const JsonEncoder.withIndent(' ').convert(json); - } catch (_) { - return json.toString(); - } - } } \ No newline at end of file diff --git a/lib/viewmodels/design_canvas_viewmodel.dart b/lib/viewmodels/design_canvas_viewmodel.dart index 9ac61e1..d325240 100644 --- a/lib/viewmodels/design_canvas_viewmodel.dart +++ b/lib/viewmodels/design_canvas_viewmodel.dart @@ -3,8 +3,6 @@ import '../models/widget_node.dart'; import '../models/app_theme.dart'; import '../models/widget_data.dart'; import '../services/widget_properties_service.dart'; -import 'dart:convert'; -import 'dart:io'; import 'package:flutter_sdui/flutter_sdui.dart'; import 'package:uuid/uuid.dart'; import 'widget_tree_service.dart'; @@ -186,13 +184,21 @@ class DesignCanvasViewModel extends ChangeNotifier { notifyListeners(); } + void reparentWidgetAtIndex(String nodeId, String newParentId, int insertIndex) { + if (nodeId == _rootWidgetNode.uid || nodeId == newParentId) return; + _rootWidgetNode = WidgetTreeService.reparentAtIndex(_rootWidgetNode, nodeId, newParentId, insertIndex); + notifyListeners(); + } + void importFromSduiJson(Map json) { final sduiWidget = SduiParser.parseJSON(json); - if (sduiWidget == null) return; final widgetNode = SduiConversionService.widgetNodeFromSduiWidget(sduiWidget); _rootWidgetNode = widgetNode; notifyListeners(); } - // Add more high-level coordination as needed... + void setRootWidgetNode(WidgetNode node) { + _rootWidgetNode = node; + notifyListeners(); + } } \ No newline at end of file diff --git a/lib/viewmodels/sdui_conversion_service.dart b/lib/viewmodels/sdui_conversion_service.dart index 3d1af7a..cc89180 100644 --- a/lib/viewmodels/sdui_conversion_service.dart +++ b/lib/viewmodels/sdui_conversion_service.dart @@ -98,7 +98,19 @@ class SduiConversionService { child: children.isNotEmpty ? children.first : null, ); case 'SduiScaffold': + // Create AppBar if showAppBar is true and appBarTitle is provided + SduiAppBar? appBar; + final showAppBar = node.properties['showAppBar'] as bool? ?? true; + final appBarTitle = node.properties['appBarTitle']?.toString(); + if (showAppBar && appBarTitle != null && appBarTitle.isNotEmpty) { + appBar = SduiAppBar( + title: appBarTitle, + backgroundColor: _parseColor(node.properties['appBarBackgroundColor']), + ); + } + return SduiScaffold( + appBar: appBar, backgroundColor: _parseColor(node.properties['backgroundColor']), resizeToAvoidBottomInset: node.properties['resizeToAvoidBottomInset'] as bool?, primary: node.properties['primary'] as bool? ?? true, @@ -189,6 +201,18 @@ class SduiConversionService { if (sduiWidget.extendBodyBehindAppBar != null) properties['extendBodyBehindAppBar'] = sduiWidget.extendBodyBehindAppBar; if (sduiWidget.drawerEnableOpenDragGesture != null) properties['drawerEnableOpenDragGesture'] = sduiWidget.drawerEnableOpenDragGesture; if (sduiWidget.endDrawerEnableOpenDragGesture != null) properties['endDrawerEnableOpenDragGesture'] = sduiWidget.endDrawerEnableOpenDragGesture; + + // Handle AppBar properties + if (sduiWidget.appBar is SduiAppBar) { + properties['showAppBar'] = true; + final appBar = sduiWidget.appBar as SduiAppBar; + if (appBar.title != null) properties['appBarTitle'] = appBar.title; + if (appBar.backgroundColor != null) { + properties['appBarBackgroundColor'] = '#${appBar.backgroundColor!.value.toRadixString(16).padLeft(8, '0').toUpperCase()}'; + } + } else { + properties['showAppBar'] = false; + } } if (sduiWidget is SduiText) { properties['text'] = sduiWidget.text; diff --git a/lib/viewmodels/widget_tree_service.dart b/lib/viewmodels/widget_tree_service.dart index 636de75..ae5e348 100644 --- a/lib/viewmodels/widget_tree_service.dart +++ b/lib/viewmodels/widget_tree_service.dart @@ -23,9 +23,8 @@ class WidgetTreeService { icon: widgetData.icon, position: _getStaggeredChildPosition(root, newChildren.length), size: (() { - final parentSize = root.size; - if ((widgetData.type == 'SduiColumn' || widgetData.type == 'SduiRow' || widgetData.type == 'SduiContainer') && parentSize != null) { - return Size(parentSize.width * 0.8, parentSize.height * 0.8); + if (widgetData.type == 'SduiColumn' || widgetData.type == 'SduiRow' || widgetData.type == 'SduiContainer') { + return Size(root.size.width * 0.8, root.size.height * 0.8); } return WidgetPropertiesService.getDefaultSize(widgetData.type); })(), @@ -33,32 +32,6 @@ class WidgetTreeService { properties: WidgetPropertiesService.getDefaultProperties(widgetData.type), ); newChildren = [...newChildren, newWidget]; - // ... auto-size logic ... - if (root.type == 'SduiColumn') { - double totalHeight = newChildren.fold(0.0, (sum, child) => sum + (child.size.height)); - double parentHeight = root.size.height; - double parentWidth = root.size.width; - if (totalHeight > parentHeight) { - parentHeight = totalHeight * 2; - } - if (totalHeight > parentHeight && parentConstraints.maxChildren != -1) { - double newChildHeight = parentHeight / newChildren.length; - newChildren = newChildren.map((child) => child.copyWith(size: Size(child.size.width, newChildHeight))).toList(); - } - root = root.copyWith(size: Size(parentWidth, parentHeight)); - } else if (root.type == 'SduiRow') { - double totalWidth = newChildren.fold(0.0, (sum, child) => sum + (child.size.width)); - double parentWidth = root.size.width; - double parentHeight = root.size.height; - if (totalWidth > parentWidth) { - parentWidth = totalWidth * 2; - } - if (totalWidth > parentWidth && parentConstraints.maxChildren != -1) { - double newChildWidth = parentWidth / newChildren.length; - newChildren = newChildren.map((child) => child.copyWith(size: Size(newChildWidth, child.size.height))).toList(); - } - root = root.copyWith(size: Size(parentWidth, parentHeight)); - } WidgetNode resizedNode = root.copyWith(children: newChildren); if (isLayoutWidget) { final Rect bounds = _computeChildrenBounds(newChildren); @@ -137,6 +110,43 @@ class WidgetTreeService { return root.copyWith(children: newChildren); } + static WidgetNode reparentAtIndex(WidgetNode root, String nodeId, String newParentId, int insertIndex) { + final nodeToMove = findWidgetByUid(root, nodeId); + if (nodeToMove == null) return root; + + WidgetNode removedTree = _removeNode(root, nodeId); + return _insertIntoParent(removedTree, nodeToMove, newParentId, insertIndex); + } + + static WidgetNode _removeNode(WidgetNode current, String nodeId) { + if (current.uid == nodeId) { + // Should not remove the root itself here + return current; + } + final newChildren = []; + for (final child in current.children) { + if (child.uid == nodeId) continue; + newChildren.add(_removeNode(child, nodeId)); + } + return current.copyWith(children: newChildren); + } + + static WidgetNode _insertIntoParent(WidgetNode current, WidgetNode node, String parentId, int insertIndex) { + if (current.uid == parentId) { + final constraints = WidgetPropertiesService.getConstraints(current.type); + if (!constraints.canHaveChildren) return current; + List newChildren = List.of(current.children); + if (constraints.maxChildren == 1) { + newChildren = [node]; + } else { + final idx = insertIndex.clamp(0, newChildren.length); + newChildren.insert(idx, node); + } + return current.copyWith(children: newChildren); + } + return current.copyWith(children: current.children.map((c) => _insertIntoParent(c, node, parentId, insertIndex)).toList()); + } + static WidgetNode moveWidget(WidgetNode root, String uid, Offset newPosition) { if (root.uid == uid) { return root.copyWith(position: newPosition); diff --git a/lib/views/design_canvas_screen.dart b/lib/views/design_canvas_screen.dart index 2c39633..445da97 100644 --- a/lib/views/design_canvas_screen.dart +++ b/lib/views/design_canvas_screen.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'dart:math' as math; import 'package:provider/provider.dart'; import '../viewmodels/design_canvas_viewmodel.dart'; import '../services/code_generator_service.dart'; @@ -31,7 +32,7 @@ class _DesignCanvasScreenContentState extends State<_DesignCanvasScreenContent> final GlobalKey _canvasKey = GlobalKey(); double leftWidth = 275; double rightWidth = 300; - final double minPaneWidth = 140; + final double minPaneWidth = 200; bool resizingLeft = false; bool resizingRight = false; Offset? dragStart; @@ -43,111 +44,135 @@ class _DesignCanvasScreenContentState extends State<_DesignCanvasScreenContent> return Consumer( builder: (context, viewModel, child) { return Scaffold( - body: Row( - children: [ - // Narrow Icon Bar - Container( - width: 90, - decoration: const BoxDecoration( - color: Color(0xFF2F2F2F), - border: Border( - right: BorderSide(color: Color(0xFFE0E0E0), width: 1), - ), - ), - child: IconBar( - selectedPane: viewModel.selectedPane, - onPaneSelected: viewModel.setSelectedPane, - ), + backgroundColor: const Color(0xFF1E1E1E), + appBar: AppBar( + backgroundColor: const Color(0xFF2F2F2F), + elevation: 1, + title: const Text( + 'Flutter SDUI', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.white, ), - // Resizable Left Panel - SizedBox( - width: leftWidth, - child: LeftSidebar( - selectedPane: viewModel.selectedPane, - scaffoldWidget: viewModel.widgetRoot, - appTheme: viewModel.appTheme, - onThemeChanged: viewModel.updateTheme, - onWidgetDropped: (widgetData) {}, - selectedWidgetId: viewModel.selectedWidgetId, - onWidgetSelected: viewModel.setSelectedWidget, - onPaletteDragStart: (data, pos) => _canvasKey.currentState?.startPaletteDrag(data, pos), - onPaletteDragUpdate: (pos) => _canvasKey.currentState?.updatePaletteDrag(pos), - onPaletteDragEnd: () => _canvasKey.currentState?.endPaletteDrag(), - ), - ), - // Left Divider - MouseRegion( - cursor: SystemMouseCursors.resizeLeftRight, - child: GestureDetector( - behavior: HitTestBehavior.translucent, - onHorizontalDragStart: (details) { - setState(() { - resizingLeft = true; - dragStart = details.globalPosition; - dragStartLeftWidth = leftWidth; - }); - }, - onHorizontalDragUpdate: (details) { - if (resizingLeft && dragStart != null && dragStartLeftWidth != null) { - setState(() { - leftWidth = (dragStartLeftWidth! + (details.globalPosition.dx - dragStart!.dx)).clamp(minPaneWidth, 400); - }); - } - }, - onHorizontalDragEnd: (_) { - setState(() { - resizingLeft = false; - dragStart = null; - dragStartLeftWidth = null; - }); - }, - child: AnimatedContainer( - duration: const Duration(milliseconds: 120), - width: 4, - color: resizingLeft ? Theme.of(context).colorScheme.primary.withOpacity(0.10) : Colors.transparent, - child: const SizedBox.expand(), + ), + centerTitle: false, + actions: [ + _buildViewToggle(context, 'Design', !viewModel.showPreview, viewModel), + const SizedBox(width: 8), + _buildViewToggle(context, 'Preview', viewModel.showPreview, viewModel), + const SizedBox(width: 16), + if (viewModel.showPreview) + TextButton.icon( + onPressed: () => _showCodeDialog(context, viewModel), + icon: const Icon(Icons.code, color: Color(0xFF4CAF50), size: 16), + label: const Text( + 'View Code', + style: TextStyle(color: Color(0xFF4CAF50)), + ), + style: TextButton.styleFrom( + backgroundColor: const Color(0xFF3C3C3C), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), ), ), - ), - // Main Canvas Area - Expanded( - child: Column( - children: [ - // View Toggle Header - Container( - height: 50, - color: const Color(0xFF2F2F2F), - child: Row( - children: [ - const SizedBox(width: 16), - const Text( - 'View:', - style: TextStyle( - color: Color(0xFFEDF1EE), - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(width: 16), - _buildViewToggle(context, 'Design', !viewModel.showPreview, viewModel), - const SizedBox(width: 8), - _buildViewToggle(context, 'Preview', viewModel.showPreview, viewModel), - const Spacer(), - if (viewModel.showPreview) - TextButton.icon( - onPressed: () => _showCodeDialog(context, viewModel), - icon: const Icon(Icons.code, color: Color(0xFF4CAF50), size: 16), - label: const Text( - 'View Code', - style: TextStyle(color: Color(0xFF4CAF50)), - ), - ), - const SizedBox(width: 16), - ], + const SizedBox(width: 16), + ], + ), + body: LayoutBuilder( + builder: (context, constraints) { + const double iconBarWidth = 60; + const double dividerWidth = 4; + const double minCenterWidth = 300; + + double maxPanelWidth = math.max(minPaneWidth, constraints.maxWidth * 0.4); + double effectiveLeftWidth = leftWidth.clamp(minPaneWidth, maxPanelWidth); + double effectiveRightWidth = rightWidth.clamp(minPaneWidth, maxPanelWidth); + + double fixedSpace = iconBarWidth + (2 * dividerWidth) + effectiveLeftWidth + effectiveRightWidth; + double remaining = constraints.maxWidth - fixedSpace; + + if (remaining < minCenterWidth) { + double targetPanelsTotal = (constraints.maxWidth - iconBarWidth - (2 * dividerWidth) - minCenterWidth).clamp(0, double.infinity); + double currentPanelsTotal = (effectiveLeftWidth + effectiveRightWidth).clamp(1, double.infinity); + double scale = targetPanelsTotal / currentPanelsTotal; + effectiveLeftWidth = (effectiveLeftWidth * scale).clamp(minPaneWidth, maxPanelWidth); + effectiveRightWidth = (effectiveRightWidth * scale).clamp(minPaneWidth, maxPanelWidth); + } + + final double minTotalWidth = iconBarWidth + (2 * dividerWidth) + (2 * minPaneWidth) + minCenterWidth; + + final Widget contentRow = Row( + children: [ + // Narrow Icon Bar + Container( + width: iconBarWidth, + decoration: const BoxDecoration( + color: Color(0xFF2F2F2F), + border: Border( + right: BorderSide(color: Color(0xFF424242), width: 1), ), ), - // Canvas Content - Expanded( + child: IconBar( + selectedPane: viewModel.selectedPane, + onPaneSelected: viewModel.setSelectedPane, + ), + ), + // Resizable Left Panel + SizedBox( + width: effectiveLeftWidth, + child: LeftSidebar( + selectedPane: viewModel.selectedPane, + scaffoldWidget: viewModel.widgetRoot, + appTheme: viewModel.appTheme, + onThemeChanged: viewModel.updateTheme, + onWidgetDropped: (widgetData) {}, + selectedWidgetId: viewModel.selectedWidgetId, + onWidgetSelected: viewModel.setSelectedWidget, + onPaletteDragStart: (data, pos) => _canvasKey.currentState?.startPaletteDrag(data, pos), + onPaletteDragUpdate: (pos) => _canvasKey.currentState?.updatePaletteDrag(pos), + onPaletteDragEnd: () => _canvasKey.currentState?.endPaletteDrag(), + ), + ), + // Left Divider + MouseRegion( + cursor: SystemMouseCursors.resizeLeftRight, + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onHorizontalDragStart: (details) { + setState(() { + resizingLeft = true; + dragStart = details.globalPosition; + dragStartLeftWidth = leftWidth; + }); + }, + onHorizontalDragUpdate: (details) { + if (resizingLeft && dragStart != null && dragStartLeftWidth != null) { + setState(() { + final double upper = math.max(minPaneWidth, constraints.maxWidth * 0.6); + leftWidth = (dragStartLeftWidth! + (details.globalPosition.dx - dragStart!.dx)).clamp(minPaneWidth, upper); + }); + } + }, + onHorizontalDragEnd: (_) { + setState(() { + resizingLeft = false; + dragStart = null; + dragStartLeftWidth = null; + }); + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 120), + width: dividerWidth, + color: resizingLeft ? Theme.of(context).colorScheme.primary.withOpacity(0.10) : Colors.transparent, + child: const SizedBox.expand(), + ), + ), + ), + // Main Canvas Area + Expanded( + child: Container( + color: const Color(0xFF2A2A2A), child: viewModel.showPreview ? PreviewCanvas( widgetRoot: viewModel.widgetRoot, @@ -163,72 +188,76 @@ class _DesignCanvasScreenContentState extends State<_DesignCanvasScreenContent> onWidgetMoved: viewModel.moveWidget, onWidgetResized: viewModel.resizeWidget, onWidgetReparent: viewModel.reparentWidget, + onWidgetReparentAtIndex: viewModel.reparentWidgetAtIndex, ), ), - ], - ), - ), - // Right Divider - MouseRegion( - cursor: SystemMouseCursors.resizeLeftRight, - child: GestureDetector( - behavior: HitTestBehavior.translucent, - onHorizontalDragStart: (details) { - setState(() { - resizingRight = true; - dragStart = details.globalPosition; - dragStartRightWidth = rightWidth; - }); - }, - onHorizontalDragUpdate: (details) { - if (resizingRight && dragStart != null && dragStartRightWidth != null) { - setState(() { - rightWidth = (dragStartRightWidth! - (details.globalPosition.dx - dragStart!.dx)).clamp(minPaneWidth, 400); - }); - } - }, - onHorizontalDragEnd: (_) { - setState(() { - resizingRight = false; - dragStart = null; - dragStartRightWidth = null; - }); - }, - child: AnimatedContainer( - duration: const Duration(milliseconds: 120), - width: 4, - color: resizingRight ? Theme.of(context).colorScheme.primary.withOpacity(0.10) : Colors.transparent, - child: const SizedBox.expand(), ), - ), - ), - // Right Properties Panel - SizedBox( - width: rightWidth, - child: PropertiesPanel( - selectedWidget: viewModel.getSelectedWidget(), - appTheme: viewModel.appTheme, - onPropertyChanged: viewModel.updateWidgetProperty, - onWidgetRemoved: viewModel.removeWidget, - ), - ), - ], + // Right Divider + MouseRegion( + cursor: SystemMouseCursors.resizeLeftRight, + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onHorizontalDragStart: (details) { + setState(() { + resizingRight = true; + dragStart = details.globalPosition; + dragStartRightWidth = rightWidth; + }); + }, + onHorizontalDragUpdate: (details) { + if (resizingRight && dragStart != null && dragStartRightWidth != null) { + setState(() { + final double upper = math.max(minPaneWidth, constraints.maxWidth * 0.6); + rightWidth = (dragStartRightWidth! - (details.globalPosition.dx - dragStart!.dx)).clamp(minPaneWidth, upper); + }); + } + }, + onHorizontalDragEnd: (_) { + setState(() { + resizingRight = false; + dragStart = null; + dragStartRightWidth = null; + }); + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 120), + width: dividerWidth, + color: resizingRight ? Theme.of(context).colorScheme.primary.withOpacity(0.10) : Colors.transparent, + child: const SizedBox.expand(), + ), + ), + ), + // Right Properties Panel + SizedBox( + width: effectiveRightWidth, + child: PropertiesPanel( + selectedWidget: viewModel.getSelectedWidget(), + appTheme: viewModel.appTheme, + onPropertyChanged: viewModel.updateWidgetProperty, + onWidgetRemoved: viewModel.removeWidget, + ), + ), + ], + ); + + if (constraints.maxWidth < minTotalWidth) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: minTotalWidth), + child: contentRow, + ), + ); + } + + return contentRow; + }, ), ); }, ); } - Widget _buildLeftSidebar(DesignCanvasViewModel viewModel) { - // This method is now obsolete, as we use LeftSidebar directly above. - return const SizedBox(); - } - - Widget _buildPropertiesPanel(DesignCanvasViewModel viewModel) { - // This method is now obsolete, as we use PropertiesPanel directly above. - return const SizedBox(); - } - Widget _buildViewToggle(BuildContext context, String label, bool isSelected, DesignCanvasViewModel viewModel) { return GestureDetector( onTap: () { @@ -237,11 +266,8 @@ class _DesignCanvasScreenContentState extends State<_DesignCanvasScreenContent> child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( - color: isSelected ? const Color(0xFF4CAF50) : Colors.transparent, + color: isSelected ? const Color(0xFF4CAF50) : const Color(0xFF3C3C3C), borderRadius: BorderRadius.circular(4), - border: Border.all( - color: isSelected ? const Color(0xFF4CAF50) : const Color(0xFF666666), - ), ), child: Text( label, @@ -265,7 +291,7 @@ class _DesignCanvasScreenContentState extends State<_DesignCanvasScreenContent> 'Generated Flutter Code', style: TextStyle(color: Color(0xFFEDF1EE)), ), - content: Container( + content: SizedBox( width: 600, height: 400, child: SingleChildScrollView( diff --git a/lib/views/widgets/build_pane.dart b/lib/views/widgets/build_pane.dart index c434d31..e774be4 100644 --- a/lib/views/widgets/build_pane.dart +++ b/lib/views/widgets/build_pane.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import '../../models/widget_data.dart'; -import 'package:flutter_sdui/flutter_sdui.dart'; import '../../services/widget_properties_service.dart'; // Set to true to show experimental/legacy widgets in the palette @@ -174,38 +173,50 @@ class _BuildPaneState extends State { onPointerCancel: (event) { widget.onPaletteDragEnd?.call(); }, - child: Container( - decoration: BoxDecoration( - color: const Color(0xFFE0E0E0), - borderRadius: BorderRadius.circular(8), - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(entry.icon, color: const Color(0xFF212121), size: 28), - const SizedBox(height: 4), - Text( - entry.label, - style: const TextStyle( - color: Color(0xFF212121), - fontSize: 12, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 2), - Text( - entry.childrenInfo, - style: const TextStyle( - color: Color(0xFF666666), - fontSize: 10, - ), - textAlign: TextAlign.center, + child: LayoutBuilder( + builder: (context, constraints) { + final double tileWidth = constraints.maxWidth; + final double iconSize = (tileWidth * 0.18).clamp(22, 36); + final double labelFont = (tileWidth * 0.08).clamp(11, 14); + final double infoFont = (tileWidth * 0.07).clamp(9, 12); + final double verticalGap = (tileWidth * 0.02).clamp(2, 8); + return Container( + decoration: BoxDecoration( + color: const Color(0xFFE0E0E0), + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(entry.icon, color: const Color(0xFF212121), size: iconSize), + SizedBox(height: verticalGap), + Text( + entry.label, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: const Color(0xFF212121), + fontSize: labelFont.toDouble(), + fontWeight: FontWeight.w500, + ), + ), + SizedBox(height: (verticalGap * 0.6).clamp(2, 6)), + Text( + entry.childrenInfo, + maxLines: 2, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + style: TextStyle( + color: const Color(0xFF666666), + fontSize: infoFont.toDouble(), + ), + ), + ], ), - ], - ), - ), + ), + ); + }, ), ); } @@ -237,14 +248,22 @@ class _BuildPaneState extends State { ), if (isExpanded) ...[ const SizedBox(height: 12), - GridView.count( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - crossAxisCount: 2, - crossAxisSpacing: 8, - mainAxisSpacing: 8, - childAspectRatio: 0.8, - children: widgets.map((widget) => widget ?? _buildEmptyCard()).toList(), + LayoutBuilder( + builder: (context, constraints) { + // Aim for cards around 160-200px wide and adapt to sidebar width + const double desiredTileWidth = 180; + return GridView( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: desiredTileWidth, + mainAxisSpacing: 8, + crossAxisSpacing: 8, + childAspectRatio: 0.9, + ), + children: widgets.map((w) => w ?? _buildEmptyCard()).toList(), + ); + }, ), ], ], diff --git a/lib/views/widgets/canvas_utils.dart b/lib/views/widgets/canvas_utils.dart index 7ac5b04..95df3ca 100644 --- a/lib/views/widgets/canvas_utils.dart +++ b/lib/views/widgets/canvas_utils.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import '../../models/widget_node.dart'; -import '../../models/widget_data.dart'; // Utility functions for the design canvas. To be moved from design_canvas.dart. @@ -38,18 +37,32 @@ Color parseColor(String colorString) { // Recursively compute the bounding box of all widgets Size computeCanvasSize(WidgetNode node) { + // Start with the current node's bounds + double minX = node.position.dx; + double minY = node.position.dy; double maxX = node.position.dx + node.size.width; double maxY = node.position.dy + node.size.height; + for (final child in node.children) { - final childSize = computeCanvasSize(child); - if (child.position.dx + child.size.width > maxX) { - maxX = child.position.dx + child.size.width; - } - if (child.position.dy + child.size.height > maxY) { - maxY = child.position.dy + child.size.height; - } - if (childSize.width > maxX) maxX = childSize.width; - if (childSize.height > maxY) maxY = childSize.height; + // Recursively compute child bounds + computeCanvasSize(child); + + // Update bounds to include child + final childMinX = child.position.dx; + final childMinY = child.position.dy; + final childMaxX = child.position.dx + child.size.width; + final childMaxY = child.position.dy + child.size.height; + + minX = minX < childMinX ? minX : childMinX; + minY = minY < childMinY ? minY : childMinY; + maxX = maxX > childMaxX ? maxX : childMaxX; + maxY = maxY > childMaxY ? maxY : childMaxY; } - return Size(maxX, maxY); + + // Add padding to ensure all content is visible and scrollable + const double padding = 200; + final double width = (maxX - minX + padding).clamp(1200, double.infinity); + final double height = (maxY - minY + padding).clamp(900, double.infinity); + + return Size(width, height); } \ No newline at end of file diff --git a/lib/views/widgets/design_canvas.dart b/lib/views/widgets/design_canvas.dart index 83e88cd..7988790 100644 --- a/lib/views/widgets/design_canvas.dart +++ b/lib/views/widgets/design_canvas.dart @@ -18,6 +18,7 @@ class DesignCanvas extends StatefulWidget { final Function(String, Offset) onWidgetMoved; final Function(String, Size) onWidgetResized; final Function(String, String) onWidgetReparent; + final Function(String, String, int)? onWidgetReparentAtIndex; const DesignCanvas({ super.key, @@ -29,6 +30,7 @@ class DesignCanvas extends StatefulWidget { required this.onWidgetMoved, required this.onWidgetResized, required this.onWidgetReparent, + this.onWidgetReparentAtIndex, }); @override @@ -46,10 +48,16 @@ class DesignCanvasState extends State { final Map _dropTargetKeys = HashMap(); String? _deepestDropTargetId; + // Per-node scroll controllers for Rows/Columns + final Map _nodeScrollControllers = HashMap(); + + ScrollController _getNodeScrollController(String nodeId) { + return _nodeScrollControllers.putIfAbsent(nodeId, () => ScrollController()); + } + // Custom drag state for palette-to-canvas bool _isDraggingFromPalette = false; WidgetData? _draggedWidgetData; - Offset? _dragPointerPositionGlobal; Offset? _dragPointerPositionLocal; String? _paletteDropTargetId; @@ -71,6 +79,9 @@ class DesignCanvasState extends State { void dispose() { _verticalScrollController.dispose(); _horizontalScrollController.dispose(); + for (final controller in _nodeScrollControllers.values) { + controller.dispose(); + } super.dispose(); } @@ -112,7 +123,6 @@ class DesignCanvasState extends State { setState(() { _isDraggingFromPalette = true; _draggedWidgetData = data; - _dragPointerPositionGlobal = globalPosition; _updatePaletteDragPosition(globalPosition); }); } @@ -120,7 +130,6 @@ class DesignCanvasState extends State { void updatePaletteDrag(Offset globalPosition) { if (_isDraggingFromPalette) { setState(() { - _dragPointerPositionGlobal = globalPosition; _updatePaletteDragPosition(globalPosition); }); } @@ -155,7 +164,6 @@ class DesignCanvasState extends State { setState(() { _isDraggingFromPalette = false; _draggedWidgetData = null; - _dragPointerPositionGlobal = null; _dragPointerPositionLocal = null; _paletteDropTargetId = null; }); @@ -168,7 +176,6 @@ class DesignCanvasState extends State { setState(() { _isDraggingFromPalette = false; _draggedWidgetData = null; - _dragPointerPositionGlobal = null; _dragPointerPositionLocal = null; _paletteDropTargetId = null; }); @@ -189,6 +196,85 @@ class DesignCanvasState extends State { final local = box.globalToLocal(globalPosition); _dragPointerPositionLocal = local; _paletteDropTargetId = _findDeepestDropTargetId(local); + _autoScrollOnDrag(globalPosition); + _autoScrollNodeOnDrag(local); + } + } + + void _autoScrollNodeOnDrag(Offset localPointer) { + // Auto-scroll the currently targeted Row/Column when dragging near its edges + final String? targetId = _paletteDropTargetId ?? _deepestDropTargetId; + if (targetId == null) return; + final key = _dropTargetKeys[targetId]; + if (key == null) return; + final ctx = key.currentContext; + if (ctx == null) return; + final box = ctx.findRenderObject() as RenderBox?; + if (box == null || !box.hasSize) return; + + // Get node and its controller + final node = _findNodeById(widget.widgetRoot, targetId); + if (node == null) return; + final controller = _getNodeScrollController(targetId); + if (!controller.hasClients) return; + + // Convert pointer to this box's local coordinates + final Offset topLeft = box.localToGlobal(Offset.zero, ancestor: context.findRenderObject()); + final Rect rect = topLeft & box.size; + final double px = localPointer.dx; + final double py = localPointer.dy; + + const double edgeThreshold = 56.0; + const double scrollStep = 24.0; + + if (node.type == 'Column Widget' || node.type == 'SduiColumn') { + final double topEdge = rect.top; + final double bottomEdge = rect.bottom; + if (py < topEdge + edgeThreshold) { + final newOffset = (controller.offset - scrollStep).clamp(0.0, controller.position.maxScrollExtent); + controller.jumpTo(newOffset); + } else if (py > bottomEdge - edgeThreshold) { + final newOffset = (controller.offset + scrollStep).clamp(0.0, controller.position.maxScrollExtent); + controller.jumpTo(newOffset); + } + } else if (node.type == 'Row Widget' || node.type == 'SduiRow') { + final double leftEdge = rect.left; + final double rightEdge = rect.right; + if (px < leftEdge + edgeThreshold) { + final newOffset = (controller.offset - scrollStep).clamp(0.0, controller.position.maxScrollExtent); + controller.jumpTo(newOffset); + } else if (px > rightEdge - edgeThreshold) { + final newOffset = (controller.offset + scrollStep).clamp(0.0, controller.position.maxScrollExtent); + controller.jumpTo(newOffset); + } + } + } + + void _autoScrollOnDrag(Offset globalPosition) { + // Auto-scroll outer scroll views when dragging near edges + final renderBox = context.findRenderObject() as RenderBox?; + if (renderBox == null || !_verticalScrollController.hasClients || !_horizontalScrollController.hasClients) return; + + final size = renderBox.size; + const edgeThreshold = 64.0; + const scrollStep = 24.0; + + // Vertical + if (globalPosition.dy < edgeThreshold) { + final newOffset = (_verticalScrollController.offset - scrollStep).clamp(0.0, _verticalScrollController.position.maxScrollExtent); + _verticalScrollController.jumpTo(newOffset); + } else if (globalPosition.dy > size.height - edgeThreshold) { + final newOffset = (_verticalScrollController.offset + scrollStep).clamp(0.0, _verticalScrollController.position.maxScrollExtent); + _verticalScrollController.jumpTo(newOffset); + } + + // Horizontal + if (globalPosition.dx < edgeThreshold) { + final newOffset = (_horizontalScrollController.offset - scrollStep).clamp(0.0, _horizontalScrollController.position.maxScrollExtent); + _horizontalScrollController.jumpTo(newOffset); + } else if (globalPosition.dx > size.width - edgeThreshold) { + final newOffset = (_horizontalScrollController.offset + scrollStep).clamp(0.0, _horizontalScrollController.position.maxScrollExtent); + _horizontalScrollController.jumpTo(newOffset); } } @@ -287,6 +373,14 @@ class DesignCanvasState extends State { setState: setState, buildWidgetNodeWithDnD: widgetNodeDndWrapper, buildNodeContainer: buildNodeContainer, + getNodeScrollController: _getNodeScrollController, + onWidgetReparentAtIndex: (nodeId, parentId, index) { + if (widget.onWidgetReparentAtIndex != null) { + widget.onWidgetReparentAtIndex!(nodeId, parentId, index); + } else { + widget.onWidgetReparent(nodeId, parentId); + } + }, getResizeHandle: (node, visualSize, dx, dy) { final cbs = getResizeCallbacks(node, visualSize, dx, dy); return buildResizeHandle( @@ -302,6 +396,9 @@ class DesignCanvasState extends State { dragFeedback: (node, visualSize) => _isDraggingFromPalette && _draggedWidgetData != null ? buildPaletteDragFeedback(_draggedWidgetData!) : _canvasDragFeedback(node, visualSize), + onQuickAdd: (parentId, data) { + widget.onWidgetAdded(parentId, data); + }, ); } @@ -317,7 +414,6 @@ class DesignCanvasState extends State { children: [ Expanded( child: Container( - color: const Color(0xFF212121), child: Scrollbar( controller: _verticalScrollController, thumbVisibility: true, @@ -336,16 +432,32 @@ class DesignCanvasState extends State { height: canvasHeight, child: Stack( children: [ + // Background grid + Positioned.fill( + child: CustomPaint( + painter: _GridBackgroundPainter( + scale: _canvasScale, + offset: _canvasOffset, + ), + ), + ), // Canvas controls Positioned( - top: 16, + bottom: 16, right: 16, child: Container( - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: const Color(0xFF2F2F2F), - borderRadius: BorderRadius.circular(8), - border: Border.all(color: const Color(0xFF666666)), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: const Color(0xFF424242), width: 1), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], ), child: Row( mainAxisSize: MainAxisSize.min, @@ -353,24 +465,28 @@ class DesignCanvasState extends State { IconButton( onPressed: () { setState(() { - _canvasScale = (_canvasScale * 1.2).clamp(0.5, 3.0); + _canvasScale = (_canvasScale / 1.2).clamp(0.2, 5.0); }); }, - icon: const Icon(Icons.zoom_in, color: Color(0xFFEDF1EE), size: 20), - tooltip: 'Zoom In', + icon: const Icon(Icons.remove, color: Color(0xFFEDF1EE), size: 16), + tooltip: 'Zoom Out', ), - Text( - '${(_canvasScale * 100).round()}%', - style: const TextStyle(color: Color(0xFFEDF1EE), fontSize: 12), + SizedBox( + width: 40, + child: Text( + '${(_canvasScale * 100).round()}%', + textAlign: TextAlign.center, + style: const TextStyle(color: Color(0xFFEDF1EE), fontSize: 12, fontWeight: FontWeight.bold), + ), ), IconButton( onPressed: () { setState(() { - _canvasScale = (_canvasScale / 1.2).clamp(0.5, 3.0); + _canvasScale = (_canvasScale * 1.2).clamp(0.2, 5.0); }); }, - icon: const Icon(Icons.zoom_out, color: Color(0xFFEDF1EE), size: 20), - tooltip: 'Zoom Out', + icon: const Icon(Icons.add, color: Color(0xFFEDF1EE), size: 16), + tooltip: 'Zoom In', ), const SizedBox(width: 8), IconButton( @@ -380,50 +496,38 @@ class DesignCanvasState extends State { _canvasOffset = Offset.zero; }); }, - icon: const Icon(Icons.center_focus_strong, color: Color(0xFFEDF1EE), size: 20), + icon: const Icon(Icons.center_focus_strong, color: Color(0xFFEDF1EE), size: 16), tooltip: 'Reset View', ), ], ), ), ), - // Main canvas (with pan/zoom) - Listener( - onPointerHover: _updatePointerPosition, - onPointerMove: _updatePointerPosition, - child: GestureDetector( - onPanUpdate: (details) { - if (_resizingWidgetId == null) { - setState(() { - _canvasOffset += details.delta; - }); - } - }, + // Main content + Positioned.fill( + child: Listener( + onPointerHover: _updatePointerPosition, + onPointerMove: _updatePointerPosition, child: Transform.scale( scale: _canvasScale, child: Transform.translate( offset: _canvasOffset, child: Stack( + clipBehavior: Clip.none, children: [ - widgetNodeDndWrapper(widget.widgetRoot, 0, insideStack: false), + widgetNodeDndWrapper(widget.widgetRoot, 0), + if (_isDraggingFromPalette && _draggedWidgetData != null && _dragPointerPositionLocal != null) + Positioned( + left: _dragPointerPositionLocal!.dx, + top: _dragPointerPositionLocal!.dy, + child: buildPaletteDragFeedback(_draggedWidgetData!), + ), ], ), ), ), ), ), - // Custom drag feedback overlay - if (_isDraggingFromPalette && _draggedWidgetData != null && _dragPointerPositionLocal != null) - Positioned( - left: _dragPointerPositionLocal!.dx - 40, - top: _dragPointerPositionLocal!.dy - 40, - child: IgnorePointer( - child: Opacity( - opacity: 0.85, - child: buildPaletteDragFeedback(_draggedWidgetData!), - ), - ), - ), ], ), ), @@ -433,27 +537,54 @@ class DesignCanvasState extends State { ), ), ), - // Reference: Regular Flutter Column - // Container( - // color: Colors.grey[200], - // padding: const EdgeInsets.all(16), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // const Text('Reference: Regular Flutter Column', style: TextStyle(fontWeight: FontWeight.bold)), - // const SizedBox(height: 8), - // Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Container(width: 80, height: 40, color: Colors.red, margin: const EdgeInsets.only(bottom: 8)), - // Container(width: 120, height: 40, color: Colors.green, margin: const EdgeInsets.only(bottom: 8)), - // Container(width: 60, height: 40, color: Colors.blue), - // ], - // ), - // ], - // ), - // ), ], ); } -} \ No newline at end of file +} + +class _GridBackgroundPainter extends CustomPainter { + final double scale; + final Offset offset; + _GridBackgroundPainter({required this.scale, required this.offset}); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = const Color(0xFF3A3A3A) + ..strokeWidth = 0.5; + + final minorGridPaint = Paint() + ..color = const Color(0xFF303030) + ..strokeWidth = 0.5; + + final double gridSize = 50.0 * scale; + final double minorGridSize = 10.0 * scale; + + final double startX = -offset.dx % gridSize; + final double startY = -offset.dy % gridSize; + + final double minorStartX = -offset.dx % minorGridSize; + final double minorStartY = -offset.dy % minorGridSize; + + if (scale > 0.5) { + for (double i = minorStartX; i < size.width; i += minorGridSize) { + canvas.drawLine(Offset(i, 0), Offset(i, size.height), minorGridPaint); + } + for (double i = minorStartY; i < size.height; i += minorGridSize) { + canvas.drawLine(Offset(0, i), Offset(size.width, i), minorGridPaint); + } + } + + for (double i = startX; i < size.width; i += gridSize) { + canvas.drawLine(Offset(i, 0), Offset(i, size.height), paint); + } + for (double i = startY; i < size.height; i += gridSize) { + canvas.drawLine(Offset(0, i), Offset(size.width, i), paint); + } + } + + @override + bool shouldRepaint(covariant _GridBackgroundPainter oldDelegate) { + return oldDelegate.scale != scale || oldDelegate.offset != offset; + } +} \ No newline at end of file diff --git a/lib/views/widgets/device_selector.dart b/lib/views/widgets/device_selector.dart index 309e5b6..5306ade 100644 --- a/lib/views/widgets/device_selector.dart +++ b/lib/views/widgets/device_selector.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import '../../models/device_registry.dart'; -import '../../models/device_registry_entry.dart'; class DeviceSelector extends StatelessWidget { final String selectedDeviceName; @@ -17,25 +16,48 @@ class DeviceSelector extends StatelessWidget { @override Widget build(BuildContext context) { final devices = DeviceRegistry.getAll(); - return Row( - children: [ - DropdownButton( - value: selectedDeviceName, - items: devices.map((d) => DropdownMenuItem( - value: d.name, - child: Text(d.name), - )).toList(), - onChanged: (value) { - if (value != null) onDeviceSelected(value); - }, - ), - const SizedBox(width: 8), - ElevatedButton.icon( - icon: const Icon(Icons.add), - label: const Text('Add Custom'), - onPressed: onAddCustomDevice, - ), - ], + return LayoutBuilder( + builder: (context, constraints) { + final double available = constraints.maxWidth; + final double computedMax = available > 360 ? 320 : (available - 100).clamp(120, available); + final double computedMin = computedMax.clamp(120, computedMax); + return Wrap( + spacing: 8, + runSpacing: 8, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: computedMax, + minWidth: computedMin, + ), + child: DropdownButtonFormField( + value: selectedDeviceName, + isExpanded: true, + items: devices + .map((d) => DropdownMenuItem( + value: d.name, + child: Text(d.name, overflow: TextOverflow.ellipsis), + )) + .toList(), + onChanged: (value) { + if (value != null) onDeviceSelected(value); + }, + decoration: const InputDecoration( + isDense: true, + border: OutlineInputBorder(), + labelText: 'Device', + ), + ), + ), + ElevatedButton.icon( + icon: const Icon(Icons.add), + label: const Text('Add Custom'), + onPressed: onAddCustomDevice, + ), + ], + ); + }, ); } } \ No newline at end of file diff --git a/lib/views/widgets/node_container.dart b/lib/views/widgets/node_container.dart index ae17edc..c18d099 100644 --- a/lib/views/widgets/node_container.dart +++ b/lib/views/widgets/node_container.dart @@ -17,7 +17,7 @@ Widget buildNodeContainer({ }) { Widget container = GestureDetector( onTap: onTap, - child: Container( + child: AnimatedContainer( width: visualSize.width, height: visualSize.height, decoration: BoxDecoration( @@ -40,6 +40,8 @@ Widget buildNodeContainer({ : isScaffold ? 8 : 4, ), ), + duration: const Duration(milliseconds: 160), + curve: Curves.easeOut, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -57,17 +59,7 @@ Widget buildNodeContainer({ children: [ Icon(node.icon, size: 16, color: isSelected ? const Color(0xFF4CAF50) : const Color(0xFFEDF1EE)), const SizedBox(width: 6), - Expanded( - child: Text( - node.label, - style: TextStyle( - color: isSelected ? const Color(0xFF4CAF50) : const Color(0xFFEDF1EE), - fontSize: 12, - fontWeight: FontWeight.w600, - overflow: TextOverflow.ellipsis, - ), - ), - ), + Expanded(child: _AnimatedEllipsisText(text: node.label, isSelected: isSelected)), ], ), ), @@ -89,6 +81,27 @@ Widget buildNodeContainer({ return container; } +class _AnimatedEllipsisText extends StatelessWidget { + final String text; + final bool isSelected; + const _AnimatedEllipsisText({required this.text, required this.isSelected}); + + @override + Widget build(BuildContext context) { + return AnimatedDefaultTextStyle( + duration: const Duration(milliseconds: 160), + curve: Curves.easeOut, + style: TextStyle( + color: isSelected ? const Color(0xFF4CAF50) : const Color(0xFFEDF1EE), + fontSize: 12, + fontWeight: FontWeight.w600, + overflow: TextOverflow.ellipsis, + ), + child: Text(text, maxLines: 1), + ); + } +} + Widget buildResizeHandle({ required WidgetNode node, required Size visualSize, diff --git a/lib/views/widgets/preview_pane.dart b/lib/views/widgets/preview_pane.dart index b056b8b..51552f9 100644 --- a/lib/views/widgets/preview_pane.dart +++ b/lib/views/widgets/preview_pane.dart @@ -56,8 +56,11 @@ class _PreviewPaneState extends State { Widget build(BuildContext context) { final device = DeviceRegistry.getByName(selectedDevice); final isResponsive = device?.isResponsive ?? false; - final width = isResponsive ? responsiveWidth : (device?.width ?? 400); - final height = isResponsive ? responsiveHeight : (device?.height ?? 700); + // Use device logical sizes; fall back to defaults + final double logicalW = isResponsive ? responsiveWidth : (device?.width ?? 390); + final double logicalH = isResponsive ? responsiveHeight : (device?.height ?? 844); + final double baseRenderW = logicalW; + final double baseRenderH = logicalH; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -68,43 +71,68 @@ class _PreviewPaneState extends State { ), const SizedBox(height: 12), if (isResponsive) - Row( + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 8, + runSpacing: 8, children: [ const Text('Zoom:'), - Slider( - value: zoom, - min: 0.2, - max: 2.0, - divisions: 18, - label: '${(zoom * 100).round()}%', - onChanged: (v) => setState(() => zoom = v), + SizedBox( + width: 200, + child: Slider( + value: zoom, + min: 0.2, + max: 2.0, + divisions: 18, + label: '${(zoom * 100).round()}%', + onChanged: (v) => setState(() => zoom = v), + ), ), Text('${(zoom * 100).round()}%'), ], ), - Center( - child: Stack( - children: [ - Container( - width: width * zoom, - height: height * zoom, - decoration: BoxDecoration( - border: Border.all(color: Colors.grey, width: 2), - borderRadius: BorderRadius.circular(12), - color: Colors.black, - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(12), - child: SizedBox( - width: width * zoom, - height: height * zoom, - child: widget.sduiRoot, + Expanded( + child: LayoutBuilder( + builder: (context, constraints) { + const double padding = 16; + final double availW = (constraints.maxWidth - padding * 2).clamp(100, double.infinity); + final double availH = (constraints.maxHeight - padding * 2).clamp(100, double.infinity); + final double scaleToFitW = availW / baseRenderW; + final double scaleToFitH = availH / baseRenderH; + final double fitScale = [scaleToFitW, scaleToFitH, 1.0].where((v) => v.isFinite && v > 0).reduce((a, b) => a < b ? a : b); + final double renderScale = (zoom <= 0 ? 1.0 : zoom); + final double finalScale = renderScale > fitScale ? fitScale : renderScale; + final double renderW = baseRenderW * finalScale; + final double renderH = baseRenderH * finalScale; + return Center( + child: Padding( + padding: const EdgeInsets.all(padding), + child: Stack( + children: [ + Container( + width: renderW, + height: renderH, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey, width: 2), + borderRadius: BorderRadius.circular(12), + color: Colors.black, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: SizedBox( + width: renderW, + height: renderH, + child: widget.sduiRoot, + ), + ), + ), + if (isResponsive) + ..._buildResizeHandles(), + ], ), ), - ), - if (isResponsive) - ..._buildResizeHandles(), - ], + ); + }, ), ), ], diff --git a/lib/views/widgets/properties_panel.dart b/lib/views/widgets/properties_panel.dart index c14a2ab..eb5182a 100644 --- a/lib/views/widgets/properties_panel.dart +++ b/lib/views/widgets/properties_panel.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_colorpicker/flutter_colorpicker.dart'; import '../../models/widget_node.dart'; import '../../models/app_theme.dart'; import '../../services/widget_properties_service.dart'; @@ -22,7 +21,10 @@ class PropertiesPanel extends StatelessWidget { Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(16), - child: Column( + child: LayoutBuilder( + builder: (context, constraints) => ConstrainedBox( + constraints: BoxConstraints(maxWidth: constraints.maxWidth), + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( @@ -74,6 +76,7 @@ class PropertiesPanel extends StatelessWidget { if (selectedWidget != null) ...[ Text( 'Selected: ${selectedWidget!.label}', + overflow: TextOverflow.ellipsis, style: const TextStyle( color: Color(0xFFEDF1EE), fontSize: 14, @@ -83,11 +86,13 @@ class PropertiesPanel extends StatelessWidget { const SizedBox(height: 12), Text( 'Type: ${selectedWidget!.type}', + overflow: TextOverflow.ellipsis, style: const TextStyle(color: Color(0xFFEDF1EE), fontSize: 12), ), const SizedBox(height: 8), Text( 'Children: ${selectedWidget!.children.length}', + overflow: TextOverflow.ellipsis, style: const TextStyle(color: Color(0xFFEDF1EE), fontSize: 12), ), const SizedBox(height: 8), @@ -115,6 +120,8 @@ class PropertiesPanel extends StatelessWidget { style: TextStyle(color: Color(0xFFEDF1EE)), ), ], + ), + ), ), ); } @@ -133,6 +140,7 @@ class PropertiesPanel extends StatelessWidget { children: [ Text( property.label, + overflow: TextOverflow.ellipsis, style: const TextStyle( color: Color(0xFFEDF1EE), fontSize: 14, @@ -226,7 +234,7 @@ class PropertiesPanel extends StatelessWidget { onTap: () { showDialog( context: context, - builder: (context) => ColorPickerDialog( + builder: (context) => _SimpleColorPickerDialog( initialColor: _parseColor(currentValue?.toString() ?? '#FF3F3F3F'), onColorChanged: (color) { final hexColor = '#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}'; @@ -293,8 +301,6 @@ class PropertiesPanel extends StatelessWidget { }, ), ); - default: - return const SizedBox(); } } @@ -307,17 +313,25 @@ class PropertiesPanel extends StatelessWidget { } } -class ColorPickerDialog extends StatefulWidget { +class _SimpleColorPickerDialog extends StatefulWidget { final Color initialColor; final Function(Color) onColorChanged; - const ColorPickerDialog({super.key, required this.initialColor, required this.onColorChanged}); + const _SimpleColorPickerDialog({super.key, required this.initialColor, required this.onColorChanged}); @override - State createState() => _ColorPickerDialogState(); + State<_SimpleColorPickerDialog> createState() => _SimpleColorPickerDialogState(); } -class _ColorPickerDialogState extends State { +class _SimpleColorPickerDialogState extends State<_SimpleColorPickerDialog> { late Color _color; + final List _presetColors = [ + Colors.red, Colors.pink, Colors.purple, Colors.deepPurple, + Colors.indigo, Colors.blue, Colors.lightBlue, Colors.cyan, + Colors.teal, Colors.green, Colors.lightGreen, Colors.lime, + Colors.yellow, Colors.amber, Colors.orange, Colors.deepOrange, + Colors.brown, Colors.grey, Colors.blueGrey, Colors.black, + Colors.white, Colors.transparent, + ]; @override void initState() { @@ -328,14 +342,59 @@ class _ColorPickerDialogState extends State { @override Widget build(BuildContext context) { return AlertDialog( - title: const Text('Pick a color'), - content: SingleChildScrollView( - child: ColorPicker( - pickerColor: _color, - onColorChanged: (color) => setState(() => _color = color), - enableAlpha: true, - showLabel: true, - pickerAreaHeightPercent: 0.7, + title: const Text('Select Color'), + content: SizedBox( + width: 300, + height: 200, + child: Column( + children: [ + Container( + width: double.infinity, + height: 60, + decoration: BoxDecoration( + color: _color, + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(8), + ), + child: Center( + child: Text( + '#${_color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}', + style: TextStyle( + color: _color.computeLuminance() > 0.5 ? Colors.black : Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + const SizedBox(height: 16), + Expanded( + child: GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 6, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + ), + itemCount: _presetColors.length, + itemBuilder: (context, index) { + final color = _presetColors[index]; + return GestureDetector( + onTap: () => setState(() => _color = color), + child: Container( + decoration: BoxDecoration( + color: color, + border: Border.all( + color: _color == color ? Colors.blue : Colors.grey, + width: _color == color ? 3 : 1, + ), + borderRadius: BorderRadius.circular(4), + ), + ), + ); + }, + ), + ), + ], ), ), actions: [ @@ -353,4 +412,5 @@ class _ColorPickerDialogState extends State { ], ); } -} \ No newline at end of file +} + diff --git a/lib/views/widgets/widget_node_dnd.dart b/lib/views/widgets/widget_node_dnd.dart index 7513899..3ab1eb3 100644 --- a/lib/views/widgets/widget_node_dnd.dart +++ b/lib/views/widgets/widget_node_dnd.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import '../../models/widget_node.dart'; import '../../models/widget_data.dart'; import 'canvas_utils.dart'; -import 'node_container.dart'; // This widget/function encapsulates the drag-and-drop logic for widget nodes. // It should be called from DesignCanvasState. @@ -29,11 +28,14 @@ Widget buildWidgetNodeWithDnD({ required String? deepestDropTargetId, required Function(String) onWidgetSelected, required Function(String, String) onWidgetReparent, + Function(String, String, int)? onWidgetReparentAtIndex, required void Function(void Function()) setState, required Widget Function(WidgetNode, int, {bool insideStack}) buildWidgetNodeWithDnD, required Widget Function({required WidgetNode node, required Widget childContent, required Size visualSize, required bool isSelected, required bool isHovered, required bool isScaffold, required void Function() onTap, required List resizeHandles}) buildNodeContainer, + required ScrollController Function(String nodeId) getNodeScrollController, required Widget Function(WidgetNode node, Size visualSize, int dx, int dy) getResizeHandle, required Widget Function(WidgetNode, Size) dragFeedback, + required void Function(String parentId, WidgetData data) onQuickAdd, }) { final isSelected = selectedWidgetId == node.uid; final isHovered = hoveredWidgetId == node.uid; @@ -46,24 +48,121 @@ Widget buildWidgetNodeWithDnD({ GlobalKey dropKey = dropTargetKeys[node.uid] ??= GlobalKey(); Widget childContent; - if (node.type == 'Row Widget' || node.type == 'SduiRow' || node.type == 'Column Widget' || node.type == 'SduiColumn') { - if (node.type == 'Row Widget' || node.type == 'SduiRow') { - childContent = Container( - width: node.size.width, - height: node.size.height, - child: Row( - children: node.children.map((child) => buildWidgetNodeWithDnD(child, depth + 1, insideStack: false)).toList(), - ), - ); - } else { - childContent = Container( - width: node.size.width, - height: node.size.height, - child: Column( - children: node.children.map((child) => buildWidgetNodeWithDnD(child, depth + 1, insideStack: false)).toList(), + if (node.type == 'Row Widget' || node.type == 'SduiRow') { + final rowController = getNodeScrollController(node.uid); + childContent = SizedBox( + width: node.size.width, + height: node.size.height, + child: Scrollbar( + controller: rowController, + thumbVisibility: true, + notificationPredicate: (notif) => notif.metrics.axis == Axis.horizontal, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + controller: rowController, + physics: const AlwaysScrollableScrollPhysics(), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (int i = 0; i < node.children.length; i++) ...[ + _InsertionSlot( + axis: Axis.horizontal, + onAccept: (dragged) { + if (onWidgetReparentAtIndex != null) { + onWidgetReparentAtIndex(dragged.uid, node.uid, i); + } + }, + ), + buildWidgetNodeWithDnD(node.children[i], depth + 1, insideStack: false), + ], + _InsertionSlot( + axis: Axis.horizontal, + onAccept: (dragged) { + if (onWidgetReparentAtIndex != null) { + onWidgetReparentAtIndex(dragged.uid, node.uid, node.children.length); + } + }, + ), + _EndDropGhost( + axis: Axis.horizontal, + isActive: (isDraggingFromPalette && paletteDropTargetId == node.uid) || (!isDraggingFromPalette && deepestDropTargetId == node.uid), + parentId: node.uid, + onQuickAdd: onQuickAdd, + ), + const SizedBox(width: 400), + ], + ), ), - ); - } + ), + ); + } else if (node.type == 'Column Widget' || node.type == 'SduiColumn') { + final columnController = getNodeScrollController(node.uid); + childContent = SizedBox( + width: node.size.width, + height: node.size.height, + child: Stack( + children: [ + Scrollbar( + thumbVisibility: true, + controller: columnController, + child: SingleChildScrollView( + controller: columnController, + physics: const AlwaysScrollableScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + for (int i = 0; i < node.children.length; i++) ...[ + _InsertionSlot( + axis: Axis.vertical, + onAccept: (dragged) { + if (onWidgetReparentAtIndex != null) { + onWidgetReparentAtIndex(dragged.uid, node.uid, i); + } + }, + ), + buildWidgetNodeWithDnD(node.children[i], depth + 1, insideStack: false), + ], + _InsertionSlot( + axis: Axis.vertical, + onAccept: (dragged) { + if (onWidgetReparentAtIndex != null) { + onWidgetReparentAtIndex(dragged.uid, node.uid, node.children.length); + } + }, + ), + const SizedBox(height: 24), + _EndDropGhost( + axis: Axis.vertical, + isActive: (isDraggingFromPalette && paletteDropTargetId == node.uid) || (!isDraggingFromPalette && deepestDropTargetId == node.uid), + parentId: node.uid, + onQuickAdd: onQuickAdd, + ), + const SizedBox(height: 400), // extra scroll padding to allow future items + ], + ), + ), + ), + // Show scroll indicator when there are many children + if (node.children.length > 2) + Positioned( + right: 4, + top: 4, + child: Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.7), + borderRadius: BorderRadius.circular(4), + ), + child: const Icon( + Icons.keyboard_arrow_down, + color: Colors.white, + size: 16, + ), + ), + ), + ], + ), + ); } else if (node.type == 'Stack Widget' || node.type == 'SduiStack') { childContent = Stack( children: node.children.map((child) => buildWidgetNodeWithDnD(child, depth + 1, insideStack: true)).toList(), @@ -73,10 +172,15 @@ Widget buildWidgetNodeWithDnD({ ? buildWidgetNodeWithDnD(node.children.first, depth + 1, insideStack: false) : const SizedBox.shrink(); } else if (node.type == 'Scaffold' || node.type == 'SduiScaffold') { + final showAppBar = node.properties['showAppBar'] as bool? ?? true; final appBarTitle = node.properties['appBarTitle']?.toString() ?? ''; - final appBarColor = parseColor(node.properties['appBarColor']?.toString() ?? '#FF232323'); - childContent = Column( - children: [ + final appBarColor = parseColor(node.properties['appBarBackgroundColor']?.toString() ?? '#FF232323'); + + List columnChildren = []; + + // Only add AppBar if showAppBar is true + if (showAppBar) { + columnChildren.add( GestureDetector( onTap: () => onWidgetSelected(node.uid), child: MouseRegion( @@ -111,13 +215,40 @@ Widget buildWidgetNodeWithDnD({ ), ), ), - Expanded( - child: node.children.isNotEmpty - ? Stack(children: node.children.map((child) => buildWidgetNodeWithDnD(child, depth + 1, insideStack: true)).toList()) - : const SizedBox.shrink(), + ); + } + + // Add body content + columnChildren.add( + Expanded( + child: Scrollbar( + thumbVisibility: true, + controller: getNodeScrollController('${node.uid}:v'), + child: SingleChildScrollView( + controller: getNodeScrollController('${node.uid}:v'), + scrollDirection: Axis.vertical, + child: Scrollbar( + thumbVisibility: true, + controller: getNodeScrollController('${node.uid}:h'), + notificationPredicate: (notif) => notif.metrics.axis == Axis.horizontal, + child: SingleChildScrollView( + controller: getNodeScrollController('${node.uid}:h'), + scrollDirection: Axis.horizontal, + child: SizedBox( + width: node.size.width, + height: node.size.height, + child: node.children.isNotEmpty + ? Stack(children: node.children.map((child) => buildWidgetNodeWithDnD(child, depth + 1, insideStack: true)).toList()) + : const SizedBox.shrink(), + ), + ), + ), + ), ), - ], + ), ); + + childContent = Column(children: columnChildren); } else { childContent = Center( child: Text( @@ -153,8 +284,10 @@ Widget buildWidgetNodeWithDnD({ final baseNodeContainer = nodeContainer; nodeContainer = DragTarget( key: dropKey, - onWillAccept: (data) => false, - onAccept: (data) {}, + onWillAccept: (data) => true, + onAccept: (data) { + // Accept from palette handled upstream via palette flow; keep visual highlight only. + }, builder: (context, candidateData, rejectedData) { return Stack( children: [ @@ -192,7 +325,7 @@ Widget buildWidgetNodeWithDnD({ }); }, builder: (context, candidateData, rejectedData) { - return Draggable( + return LongPressDraggable( data: node, feedback: dragFeedback(node, visualSize), childWhenDragging: Opacity( @@ -213,4 +346,93 @@ Widget buildWidgetNodeWithDnD({ } else { return dragTarget; } -} \ No newline at end of file +} + +class _EndDropGhost extends StatelessWidget { + final Axis axis; + final bool isActive; + final String parentId; + final void Function(String parentId, WidgetData data) onQuickAdd; + const _EndDropGhost({required this.axis, required this.isActive, required this.parentId, required this.onQuickAdd}); + + @override + Widget build(BuildContext context) { + final size = axis == Axis.vertical ? const Size(double.infinity, 48) : const Size(48, double.infinity); + return GestureDetector( + onTap: () async { + final selection = await showMenu<_QuickAddItem>( + context: context, + position: const RelativeRect.fromLTRB(200, 200, 0, 0), + items: const [ + PopupMenuItem<_QuickAddItem>(value: _QuickAddItem.row, child: Text('Add Row')), + PopupMenuItem<_QuickAddItem>(value: _QuickAddItem.column, child: Text('Add Column')), + PopupMenuItem<_QuickAddItem>(value: _QuickAddItem.text, child: Text('Add Text')), + ], + ); + if (selection != null) { + switch (selection) { + case _QuickAddItem.row: + onQuickAdd(parentId, const WidgetData(type: 'SduiRow', label: 'Row', icon: Icons.view_stream, position: Offset.zero)); + break; + case _QuickAddItem.column: + onQuickAdd(parentId, const WidgetData(type: 'SduiColumn', label: 'Column', icon: Icons.view_column, position: Offset.zero)); + break; + case _QuickAddItem.text: + onQuickAdd(parentId, const WidgetData(type: 'SduiText', label: 'Text', icon: Icons.text_fields, position: Offset.zero)); + break; + } + } + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 150), + curve: Curves.easeOut, + width: size.width, + height: size.height, + margin: axis == Axis.vertical ? const EdgeInsets.symmetric(vertical: 6) : const EdgeInsets.symmetric(horizontal: 6), + decoration: BoxDecoration( + color: isActive ? Colors.green.withOpacity(0.12) : Colors.transparent, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: isActive ? Colors.green : Colors.transparent, width: 2), + ), + child: Center( + child: Text( + isActive ? 'Drop here to append (or click to quick add)' : 'Click to quick add', + style: TextStyle(color: isActive ? Colors.green : const Color(0xFF888888)), + ), + ), + ), + ); + } +} + +enum _QuickAddItem { row, column, text } + +class _InsertionSlot extends StatelessWidget { + final Axis axis; + final void Function(WidgetNode dragged) onAccept; + const _InsertionSlot({required this.axis, required this.onAccept}); + + @override + Widget build(BuildContext context) { + final size = axis == Axis.vertical ? const Size(double.infinity, 8) : const Size(8, double.infinity); + return DragTarget( + onWillAccept: (data) => data != null, + onAccept: (data) => onAccept(data), + builder: (context, candidate, rejected) { + final active = candidate.isNotEmpty; + return AnimatedContainer( + duration: const Duration(milliseconds: 100), + curve: Curves.easeOut, + width: size.width, + height: size.height, + margin: axis == Axis.vertical ? const EdgeInsets.symmetric(vertical: 4) : const EdgeInsets.symmetric(horizontal: 4), + decoration: BoxDecoration( + color: active ? Colors.green.withOpacity(0.16) : Colors.transparent, + borderRadius: BorderRadius.circular(4), + border: Border.all(color: active ? Colors.green : Colors.transparent, width: 2), + ), + ); + }, + ); + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 8e08a9f..93227e0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.12.0" boolean_selector: dependency: transitive description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.3" + version: "1.3.2" ffi: dependency: transitive description: @@ -219,10 +219,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: @@ -424,10 +424,10 @@ packages: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "14.3.1" web: dependency: transitive description: @@ -440,10 +440,10 @@ packages: dependency: transitive description: name: win32 - sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" + sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" url: "https://pub.dev" source: hosted - version: "5.14.0" + version: "5.13.0" sdks: - dart: ">=3.8.0 <4.0.0" + dart: ">=3.7.0 <4.0.0" flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index 5514000..8054ee3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: '>=3.0.0 <4.0.0' + sdk: '>=3.7.0 <4.0.0' dependencies: flutter: