commit 308917ae2f196caa7796b4d74a7ee3b237b6a366 parent ccfa0612d95167c7025dd06d9f38d81f3712ccf5 Author: Nilotpal Gupta <nilotpalgupta0701@gmail.com> Date: Mon, 1 Dec 2025 10:43:13 +0530 cleanup + format Diffstat:
27 files changed, 629 insertions(+), 454 deletions(-)
diff --git a/lib/data/models/project_model.dart b/lib/data/models/project_model.dart @@ -56,7 +56,9 @@ class ProjectModel { lastAccessedAt: DateTime.parse(map['last_accessed_at']), createdAt: DateTime.parse(map['created_at']), assetsPath: - map['assets_path'] != null ? List<String>.from(jsonDecode(map['assets_path'])) : [], + map['assets_path'] != null + ? List<String>.from(jsonDecode(map['assets_path'])) + : [], ); } } diff --git a/lib/data/repos/image_repo.dart b/lib/data/repos/image_repo.dart @@ -1,4 +1,6 @@ import 'dart:convert'; +import 'package:flutter/widgets.dart'; + import '../database.dart'; import '../models/image_model.dart'; @@ -96,7 +98,7 @@ class ImageRepo { final List<dynamic> decoded = jsonDecode(tagsJson); return decoded.map((e) => e.toString()).toList(); } catch (e) { - print("Error decoding tags: $e"); + debugPrint("Error decoding tags: $e"); return []; } } diff --git a/lib/data/repos/note_repo.dart b/lib/data/repos/note_repo.dart @@ -31,11 +31,14 @@ class NoteRepo { Future<List<NoteModel>> getNotesByProjectId(int projectId) async { final db = await AppDatabase.db; - final res = await db.rawQuery(''' + final res = await db.rawQuery( + ''' SELECT notes.* FROM notes INNER JOIN images ON notes.image_id = images.id WHERE images.project_id = ? - ''', [projectId]); + ''', + [projectId], + ); return res.map((e) => NoteModel.fromMap(e)).toList(); } diff --git a/lib/services/analysis_queue_manager.dart b/lib/services/analysis_queue_manager.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:image/image.dart' as img; +import 'package:image/image.dart' as img; import '../data/models/image_model.dart'; import '../data/models/note_model.dart'; import '../data/repos/image_repo.dart'; @@ -66,9 +66,7 @@ class AnalysisQueueManager { // Run Analysis if (image.tags.isEmpty) { debugPrint("[Queue]: Analyzing Full Suite: ${image.name}"); - result = await ImageAnalyzerService.analyzeFullSuite( - image.filePath, - ); + result = await ImageAnalyzerService.analyzeFullSuite(image.filePath); } else { debugPrint( "[Queue]: Analyzing Selected: ${image.name} with tags: ${image.tags}", @@ -101,14 +99,18 @@ class AnalysisQueueManager { try { // 1. Fetch Parent Info final parentImageModel = await _imageRepo.getById(imageId); - if (parentImageModel == null) throw Exception("Parent image not found: $imageId"); + if (parentImageModel == null) { + throw Exception("Parent image not found: $imageId"); + } final parentFile = File(parentImageModel.filePath); if (!parentFile.existsSync()) throw Exception("Parent file missing"); // 2. Decode once for all notes in this group final bytes = await parentFile.readAsBytes(); parentImageCache = img.decodeImage(bytes); - if (parentImageCache == null) throw Exception("Failed to decode parent image"); + if (parentImageCache == null) { + throw Exception("Failed to decode parent image"); + } final appDir = await getApplicationDocumentsDirectory(); final cropDir = Directory('${appDir.path}/crops'); @@ -128,9 +130,9 @@ class AnalysisQueueManager { } Future<void> _processSingleNoteWithCache( - NoteModel note, - img.Image parentImage, - Directory cropDir + NoteModel note, + img.Image parentImage, + Directory cropDir, ) async { await _noteRepo.updateNote(note.id!, status: 'analyzing'); try { @@ -156,29 +158,34 @@ class AnalysisQueueManager { if (top + h > parentImage.height) h = parentImage.height - top; if (w <= 0 || h <= 0) { - debugPrint("[Queue]: Note ${note.id} has invalid dimensions"); - await _noteRepo.updateNote(note.id!, status: 'failed'); - return; + debugPrint("[Queue]: Note ${note.id} has invalid dimensions"); + await _noteRepo.updateNote(note.id!, status: 'failed'); + return; } - final croppedImg = img.copyCrop(parentImage, x: left, y: top, width: w, height: h); + final croppedImg = img.copyCrop( + parentImage, + x: left, + y: top, + width: w, + height: h, + ); await cropFile.writeAsBytes(img.encodeJpg(croppedImg)); } // 3. Analysis debugPrint("[Queue]: Analyzing Note ${note.id} tag: ${note.category}"); - final result = await ImageAnalyzerService.analyzeSelected( - cropPath, - [note.category], - ); + final result = await ImageAnalyzerService.analyzeSelected(cropPath, [ + note.category, + ]); // 4. Update DB if (result['success'] == true && result['data'] != null) { final resultsMap = result['data']['results'] ?? {}; final jsonString = jsonEncode(resultsMap); await _noteRepo.updateNote( - note.id!, - analysisData: jsonString, + note.id!, + analysisData: jsonString, status: 'completed', cropFilePath: cropPath, ); diff --git a/lib/services/analyze/embedding.dart b/lib/services/analyze/embedding.dart @@ -46,11 +46,13 @@ class EmbeddingAnalyzerService { String? jsonPath, }) async { // Safety check - if (modelPath == null || jsonPath == null) + if (modelPath == null || jsonPath == null) { return {'success': false, 'scores': {}, 'error': 'Paths missing'}; + } await initialize(modelPath: modelPath, jsonPath: jsonPath); - if (_session == null || _centroids == null) + if (_session == null || _centroids == null) { return {'success': false, 'scores': {}, 'error': 'Init failed'}; + } OrtValueTensor? inputOrt; OrtRunOptions? runOptions; @@ -58,8 +60,9 @@ class EmbeddingAnalyzerService { try { final float32Input = await ClipImageProcessor.preprocess(imagePath); - if (float32Input == null) + if (float32Input == null) { return {'success': false, 'scores': {}, 'error': 'Image decode failed'}; + } // Create Tensor // Note: ensure ClipImageProcessor returns a flat List<double> diff --git a/lib/services/analyze/font.dart b/lib/services/analyze/font.dart @@ -45,11 +45,13 @@ class FontIdentifierService { String? modelPath, String? jsonPath, }) async { - if (modelPath == null || jsonPath == null) + if (modelPath == null || jsonPath == null) { return {'success': false, 'scores': {}, 'error': 'Paths missing'}; + } await initialize(modelPath: modelPath, jsonPath: jsonPath); - if (_session == null || _fontDb == null) + if (_session == null || _fontDb == null) { return {'success': false, 'scores': {}, 'error': 'Init failed'}; + } try { // 1. OCR Detection (Native Platform Call) @@ -67,8 +69,9 @@ class FontIdentifierService { // 2. Load Image for Processing final bytes = await File(imagePath).readAsBytes(); final fullImage = img.decodeImage(bytes); - if (fullImage == null) + if (fullImage == null) { return {'success': false, 'scores': {}, 'error': 'Image decode failed'}; + } // Store counts to find dominant font Map<String, double> fontCounts = {}; @@ -189,10 +192,13 @@ class FontIdentifierService { final List<double> flatOutput = []; void flatten(dynamic d) { - if (d is num) + if (d is num) { flatOutput.add(d.toDouble()); - else if (d is List) - for (var i in d) flatten(i); + } else if (d is List) { + for (var i in d) { + flatten(i); + } + } } flatten(rawOutput); diff --git a/lib/services/analyze/image_analyzer.dart b/lib/services/analyze/image_analyzer.dart @@ -319,13 +319,18 @@ class ImageAnalyzerService { runInIsolate: false, assetPaths: assetPaths, task: (path, _) async { - final String? location = await FlaskService().generateAsset(imagePath: path); - final res = {"success": true, "scores": {'image': location}, "error": null}; + final String? location = await FlaskService().generateAsset( + imagePath: path, + ); + final res = { + "success": true, + "scores": {'image': location}, + "error": null, + }; return res; }, ) : skipTask(), - ]); totalSw.stop(); diff --git a/lib/services/analyze/texture.dart b/lib/services/analyze/texture.dart @@ -50,11 +50,13 @@ class TextureAnalyzerService { String? modelPath, String? jsonPath, }) async { - if (modelPath == null || jsonPath == null) + if (modelPath == null || jsonPath == null) { return {'success': false, 'scores': {}, 'error': 'Paths missing'}; + } await initialize(modelPath: modelPath, jsonPath: jsonPath); - if (_session == null || _centroids == null) + if (_session == null || _centroids == null) { return {'success': false, 'scores': {}, 'error': 'Init failed'}; + } OrtValueTensor? inputOrt; OrtRunOptions? runOptions; @@ -64,8 +66,9 @@ class TextureAnalyzerService { // 1. Preprocess (Standard ImageNet Normalization) final bytes = await File(path).readAsBytes(); final image = img.decodeImage(bytes); - if (image == null) + if (image == null) { return {'success': false, 'scores': {}, 'error': 'Decode failed'}; + } final resized = img.copyResize( image, @@ -120,10 +123,13 @@ class TextureAnalyzerService { final rawOutput = outputs[0]?.value as List; final List<double> embedding = []; void flatten(dynamic data) { - if (data is num) + if (data is num) { embedding.add(data.toDouble()); - else if (data is List) - for (var item in data) flatten(item); + } else if (data is List) { + for (var item in data) { + flatten(item); + } + } } flatten(rawOutput); diff --git a/lib/services/flask_service.dart b/lib/services/flask_service.dart @@ -12,13 +12,16 @@ class FlaskService { // =========================================================================== // CONFIGURATION // =========================================================================== - + // NOTE: REPLACE WITH YOUR WIFI IP ADDRESS // BOTH PC AND MOBILE SHOULD BE ON SAME WIFI // NO NEED FOR NGORK OR SMEE // PORT: 5000, http - static const String _serverUrl = 'http://172.16.114.193:5000'; // --> READ NOTE (REPLACE WITH IITG_CONNECT WIFI IP) - static const Map<String, String> _headers = {'Content-Type': 'application/json'}; + static const String _serverUrl = + 'http://172.16.114.193:5000'; // --> READ NOTE (REPLACE WITH IITG_CONNECT WIFI IP) + static const Map<String, String> _headers = { + 'Content-Type': 'application/json', + }; // =========================================================================== // 1. PIPELINES (Complex workflows) @@ -38,8 +41,8 @@ class FlaskService { // Step 1: Get the description of the sketch // We use a specific prompt to ensure we get structural details final String? sketchDescription = await describeImage( - imagePath: sketchPath, - prompt: '<MORE_DETAILED_CAPTION>' + imagePath: sketchPath, + prompt: '<MORE_DETAILED_CAPTION>', ); if (sketchDescription == null) { @@ -49,7 +52,7 @@ class FlaskService { // Step 2: Construct the Global Prompt // Strategy: Style + User Intent + Content context - final String globalPrompt = + final String globalPrompt = "$stylePrompt. $userPrompt. The image features: $sketchDescription"; debugPrint("🔗 [Pipeline] Generated Global Prompt: \n$globalPrompt"); @@ -111,7 +114,7 @@ class FlaskService { if (path != null) { // 1. Find the project ID int? projectId; - + final imagemodel = await ImageRepo().getByFilePath(imagePath); if (imagemodel != null) { projectId = imagemodel.projectId; @@ -127,7 +130,7 @@ class FlaskService { final project = await ProjectRepo().getProjectById(projectId); if (project != null) { project.assetsPath.add(path); // Update memory - await ProjectRepo().updateAssets(projectId, project.assetsPath); + await ProjectRepo().updateAssets(projectId, project.assetsPath); debugPrint("✅ Asset path saved to Project DB: $path"); } } @@ -146,16 +149,13 @@ class FlaskService { String prompt = '<MORE_DETAILED_CAPTION>', }) async { debugPrint("👁️ [Describe] Preparing request..."); - + final String? base64Image = await _encodeFile(imagePath); if (base64Image == null) return null; final response = await _postRequest( endpoint: '/describe', - body: { - 'image': base64Image, - 'prompt': prompt, - }, + body: {'image': base64Image, 'prompt': prompt}, ); if (response != null && response.statusCode == 200) { @@ -187,8 +187,10 @@ class FlaskService { if (response != null && response.statusCode == 200) { return _saveImageFromResponse(response, filenamePrefix); } - - debugPrint("❌ $logPrefix Failed: ${response?.statusCode ?? 'No Connection'}"); + + debugPrint( + "❌ $logPrefix Failed: ${response?.statusCode ?? 'No Connection'}", + ); return null; } @@ -217,26 +219,36 @@ class FlaskService { return base64Encode(await file.readAsBytes()); } - Future<String?> _saveImageFromResponse(http.Response response, String prefix) async { + Future<String?> _saveImageFromResponse( + http.Response response, + String prefix, + ) async { try { final data = jsonDecode(response.body); if (data['image'] == null) return null; final Uint8List imageBytes = base64Decode(data['image']); - + final directory = await getApplicationDocumentsDirectory(); // Use join for safe path construction final imagesDirPath = p.join(directory.path, 'generated_images'); final imagesDir = Directory(imagesDirPath); - + if (!await imagesDir.exists()) await imagesDir.create(recursive: true); final timestamp = DateTime.now().millisecondsSinceEpoch; - final safePrefix = prefix.replaceAll(RegExp(r'[^\w\s]'), '').trim().replaceAll(' ', '_'); - final shortPrefix = safePrefix.length > 20 ? safePrefix.substring(0, 20) : safePrefix; - + final safePrefix = prefix + .replaceAll(RegExp(r'[^\w\s]'), '') + .trim() + .replaceAll(' ', '_'); + final shortPrefix = + safePrefix.length > 20 ? safePrefix.substring(0, 20) : safePrefix; + // Use join here too - final String filePath = p.join(imagesDir.path, '${shortPrefix}_$timestamp.png'); + final String filePath = p.join( + imagesDir.path, + '${shortPrefix}_$timestamp.png', + ); await File(filePath).writeAsBytes(imageBytes); debugPrint("✅ Image saved: $filePath"); diff --git a/lib/services/instagram_download_service.dart b/lib/services/instagram_download_service.dart @@ -13,8 +13,9 @@ class InstagramDownloadService { // Create directories final instagramDir = Directory('${dir.path}/instagram_downloads'); - if (!await instagramDir.exists()) + if (!await instagramDir.exists()) { await instagramDir.create(recursive: true); + } final imagesDir = Directory('${dir.path}/images'); if (!await imagesDir.exists()) await imagesDir.create(recursive: true); diff --git a/lib/services/note_service.dart b/lib/services/note_service.dart @@ -38,8 +38,7 @@ class NoteService { double? normWidth, double? normHeight, }) async { - // TODO - // If category or crop area changes, need to re-analyze + // TODO: If category or crop area changes, need to re-analyze await _repo.updateNote( noteId, content: content, diff --git a/lib/services/project_service.dart b/lib/services/project_service.dart @@ -1,5 +1,7 @@ import 'dart:convert'; import 'dart:io'; +import 'package:flutter/rendering.dart'; + import '../data/repos/project_repo.dart'; import '../data/repos/image_repo.dart'; import '../data/repos/file_repo.dart'; @@ -68,7 +70,7 @@ class ProjectService { final file = File(path); if (await file.exists()) await file.delete(); } catch (e) { - print("Error deleting file $path: $e"); + debugPrint("Error deleting file $path: $e"); } } diff --git a/lib/ui/pages/canvas_board_page.dart b/lib/ui/pages/canvas_board_page.dart @@ -1,12 +1,9 @@ import 'dart:io'; import 'dart:math' as math; import 'dart:ui' as ui; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:undo/undo.dart'; import './canvas_toolbar/magic_draw_overlay.dart'; import './canvas_toolbar/text_tools_overlay.dart'; @@ -357,7 +354,7 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { color: Colors.white, boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.15), + color: Colors.black.withValues(alpha: 0.15), blurRadius: 40, offset: const Offset(0, 10), ), @@ -387,12 +384,14 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { .getMaxScaleOnAxis(), onTap: () { if (!_isMagicDrawActive) { - if (_isEditingText && selectedId != e['id']) + if (_isEditingText && selectedId != e['id']) { _exitEditMode(); + } setState(() { selectedId = e['id']; - if (e['type'] == 'text') + if (e['type'] == 'text') { _isTextToolsActive = true; + } }); setState(() { elements.remove(e); @@ -674,16 +673,18 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { ) { return source.map((e) { final copy = Map<String, dynamic>.from(e); - if (e['position'] is Offset) + if (e['position'] is Offset) { copy['position'] = Offset( (e['position'] as Offset).dx, (e['position'] as Offset).dy, ); - if (e['size'] is Size) + } + if (e['size'] is Size) { copy['size'] = Size( (e['size'] as Size).width, (e['size'] as Size).height, ); + } return copy; }).toList(); } @@ -724,7 +725,7 @@ class _ManipulatingBox extends StatefulWidget { final FocusNode? focusNode; const _ManipulatingBox({ - Key? key, + super.key, required this.id, required this.position, required this.size, @@ -743,7 +744,7 @@ class _ManipulatingBox extends StatefulWidget { required this.onDragEnd, this.textController, this.focusNode, - }) : super(key: key); + }); @override State<_ManipulatingBox> createState() => _ManipulatingBoxState(); @@ -823,7 +824,7 @@ class _ManipulatingBoxState extends State<_ManipulatingBox> { ) : widget.type == 'text' ? Border.all( - color: Colors.grey.withOpacity(0.3), + color: Colors.grey.withValues(alpha: 0.3), width: 1.0 * handleScale, ) : null, @@ -1011,8 +1012,9 @@ class CanvasPainter extends CustomPainter { if (path.points.length > 1) { final Path p = Path(); p.moveTo(path.points.first.offset.dx, path.points.first.offset.dy); - for (int i = 1; i < path.points.length; i++) + for (int i = 1; i < path.points.length; i++) { p.lineTo(path.points[i].offset.dx, path.points[i].offset.dy); + } canvas.drawPath(p, paint); } else if (path.points.isNotEmpty) { canvas.drawPoints(ui.PointMode.points, [ @@ -1031,8 +1033,9 @@ class CanvasPainter extends CustomPainter { ..style = PaintingStyle.stroke; final Path p = Path(); p.moveTo(currentPoints.first.offset.dx, currentPoints.first.offset.dy); - for (int i = 1; i < currentPoints.length; i++) + for (int i = 1; i < currentPoints.length; i++) { p.lineTo(currentPoints[i].offset.dx, currentPoints[i].offset.dy); + } canvas.drawPath(p, paint); } canvas.restore(); @@ -1070,7 +1073,7 @@ class CanvasBottomBar extends StatelessWidget { border: Border(top: BorderSide(color: Colors.grey[200]!)), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, -5), ), diff --git a/lib/ui/pages/canvas_toolbar/magic_draw_overlay.dart b/lib/ui/pages/canvas_toolbar/magic_draw_overlay.dart @@ -56,7 +56,7 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.1), + color: Colors.black.withValues(alpha: 0.1), blurRadius: 15, offset: const Offset(0, 5), ), @@ -134,7 +134,7 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { border: Border.all(color: Colors.white, width: 2), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.1), + color: Colors.black.withValues(alpha: 0.1), blurRadius: 4, ), ], @@ -475,7 +475,7 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet> data: SliderThemeData( trackHeight: 36, activeTrackColor: activeColor, - inactiveTrackColor: activeColor.withOpacity(0.2), + inactiveTrackColor: activeColor.withValues(alpha: 0.2), thumbColor: Colors.transparent, thumbShape: const RoundSliderThumbShape( enabledThumbRadius: 0, @@ -613,7 +613,7 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet> ), child: Icon(Icons.block, size: 16, color: Colors.red[300]), ), - ..._brandPalette.map((c) => _buildColorCircle(c)).toList(), + ..._brandPalette.map((c) => _buildColorCircle(c)), Container( width: 32, height: 32, @@ -663,7 +663,7 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet> decoration: BoxDecoration( color: color, shape: BoxShape.circle, - border: Border.all(color: Colors.grey.withOpacity(0.2)), + border: Border.all(color: Colors.grey.withValues(alpha: 0.2)), ), ); } diff --git a/lib/ui/pages/canvas_toolbar/text_tools_overlay.dart b/lib/ui/pages/canvas_toolbar/text_tools_overlay.dart @@ -42,7 +42,7 @@ class TextToolsOverlay extends StatelessWidget { borderRadius: BorderRadius.circular(30), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.15), + color: Colors.black.withValues(alpha: 0.15), blurRadius: 12, offset: const Offset(0, 6), ), diff --git a/lib/ui/pages/create_file_page.dart b/lib/ui/pages/create_file_page.dart @@ -303,7 +303,9 @@ class _CreateFilePageState extends State<CreateFilePage> { borderRadius: BorderRadius.circular(4), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: Colors.black.withValues( + alpha: 0.05, + ), blurRadius: 4, offset: const Offset(0, 2), ), diff --git a/lib/ui/pages/define_brand_page.dart b/lib/ui/pages/define_brand_page.dart @@ -61,9 +61,9 @@ class _DefineBrandPageState extends State<DefineBrandPage> { Future<void> _handleFinish() async { // 1. Basic Validation - only Project Name is required if (_projectNameController.text.trim().isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('Project name is required.'))); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Project name is required.')), + ); return; } @@ -122,10 +122,7 @@ class _DefineBrandPageState extends State<DefineBrandPage> { if (brandName.isNotEmpty) { final initial = brandName[0].toUpperCase(); setState(() { - _competitorBrands.add({ - 'name': brandName, - 'initial': initial, - }); + _competitorBrands.add({'name': brandName, 'initial': initial}); _competitorInputController.clear(); }); } @@ -133,9 +130,6 @@ class _DefineBrandPageState extends State<DefineBrandPage> { @override Widget build(BuildContext context) { - final theme = Theme.of(context); - final isDark = theme.brightness == Brightness.dark; - return Scaffold( backgroundColor: const Color(0xFFFAFAFA), body: SafeArea( @@ -169,18 +163,18 @@ class _DefineBrandPageState extends State<DefineBrandPage> { crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 16), - + // Icon - Container( + Container( width: 52, height: 52, - decoration: BoxDecoration( + decoration: BoxDecoration( color: const Color(0xFFE0E7FF), borderRadius: BorderRadius.circular(1000), - ), - child: Center( - child: SvgPicture.asset( - 'assets/icons/painting-ai-line.svg', + ), + child: Center( + child: SvgPicture.asset( + 'assets/icons/painting-ai-line.svg', width: 24, height: 24, colorFilter: const ColorFilter.mode( @@ -195,26 +189,26 @@ class _DefineBrandPageState extends State<DefineBrandPage> { // Title and Subtitle Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - 'Define Your Brand', - style: TextStyle( - fontFamily: 'GeneralSans', - fontSize: 20, - fontWeight: FontWeight.w500, - color: Variables.textPrimary, - height: 24 / 20, - ), - ), + Text( + 'Define Your Brand', + style: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 20, + fontWeight: FontWeight.w500, + color: Variables.textPrimary, + height: 24 / 20, + ), + ), const SizedBox(height: 4), - Text( - 'Answer a few quick questions to help us craft your unique style guide.', - style: TextStyle( - fontFamily: 'GeneralSans', - fontSize: 14, - fontWeight: FontWeight.w400, - color: Variables.textSecondary, + Text( + 'Answer a few quick questions to help us craft your unique style guide.', + style: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 14, + fontWeight: FontWeight.w400, + color: Variables.textSecondary, height: 20 / 14, ), ), @@ -231,7 +225,7 @@ class _DefineBrandPageState extends State<DefineBrandPage> { _buildFormField( label: 'Project Name', hintText: 'Enter project name', - controller: _projectNameController, + controller: _projectNameController, required: true, ), const SizedBox(height: 16), @@ -240,7 +234,7 @@ class _DefineBrandPageState extends State<DefineBrandPage> { _buildFormField( label: 'What do you want & who is it for.', hintText: 'Describe your work and your audience.', - controller: _descriptionController, + controller: _descriptionController, maxLines: 3, required: false, ), @@ -249,9 +243,10 @@ class _DefineBrandPageState extends State<DefineBrandPage> { // What problem you solve _buildFormField( label: 'What problem you solve.', - hintText: 'Explain the main issue your brand addresses.', - controller: _problemController, - maxLines: 3, + hintText: + 'Explain the main issue your brand addresses.', + controller: _problemController, + maxLines: 3, required: false, ), const SizedBox(height: 16), @@ -260,7 +255,7 @@ class _DefineBrandPageState extends State<DefineBrandPage> { _buildFormField( label: 'Long-term goal for the brand.', hintText: 'E.g. - Improving food availability...', - controller: _goalController, + controller: _goalController, required: false, ), const SizedBox(height: 32), @@ -291,71 +286,72 @@ class _DefineBrandPageState extends State<DefineBrandPage> { ), ), ], - ), - ), + ), + ), - // Bottom Button + // Bottom Button bottomNavigationBar: Container( padding: const EdgeInsets.fromLTRB(16, 0, 16, 24), decoration: BoxDecoration( color: const Color(0xFFFAFAFA), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, -2), ), ], ), child: SafeArea( - child: SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: _isLoading ? null : _handleFinish, - style: ElevatedButton.styleFrom( + child: SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _isLoading ? null : _handleFinish, + style: ElevatedButton.styleFrom( backgroundColor: Variables.textPrimary, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 12), - shape: RoundedRectangleBorder( + shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(112), - ), - elevation: 0, - ), - child: _isLoading - ? const SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator( + ), + elevation: 0, + ), + child: + _isLoading + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2, + ), + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Create Project', + style: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 14, + fontWeight: FontWeight.w500, color: Colors.white, - strokeWidth: 2, ), - ) - : Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Create Project', - style: TextStyle( - fontFamily: 'GeneralSans', - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.white, - ), - ), - const SizedBox(width: 12), - SvgPicture.asset( - 'assets/icons/generate_icon.svg', - width: 18, - height: 18, - colorFilter: const ColorFilter.mode( - Colors.white, - BlendMode.srcIn, ), - ), - ], + const SizedBox(width: 12), + SvgPicture.asset( + 'assets/icons/generate_icon.svg', + width: 18, + height: 18, + colorFilter: const ColorFilter.mode( + Colors.white, + BlendMode.srcIn, + ), ), - ), - ), + ], + ), + ), + ), ), ), ); @@ -398,13 +394,13 @@ class _DefineBrandPageState extends State<DefineBrandPage> { ), const SizedBox(height: 6), Container( - decoration: BoxDecoration( + decoration: BoxDecoration( color: const Color(0xFFE4E4E7), borderRadius: BorderRadius.circular(8), - ), - child: TextField( - controller: controller, - maxLines: maxLines, + ), + child: TextField( + controller: controller, + maxLines: maxLines, style: TextStyle( fontFamily: 'GeneralSans', fontSize: 14, @@ -412,8 +408,8 @@ class _DefineBrandPageState extends State<DefineBrandPage> { color: Variables.textPrimary, height: 20 / 14, ), - decoration: InputDecoration( - hintText: hintText, + decoration: InputDecoration( + hintText: hintText, hintStyle: TextStyle( fontFamily: 'GeneralSans', fontSize: 14, @@ -470,7 +466,10 @@ class _DefineBrandPageState extends State<DefineBrandPage> { children: [ for (final keyword in _keywords) Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), decoration: BoxDecoration( color: const Color(0xFFE0E7FF), borderRadius: BorderRadius.circular(48), @@ -507,12 +506,12 @@ class _DefineBrandPageState extends State<DefineBrandPage> { GestureDetector( onTap: _showAddKeywordDialog, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), decoration: BoxDecoration( - border: Border.all( - color: const Color(0xFFE4E4E7), - width: 1, - ), + border: Border.all(color: const Color(0xFFE4E4E7), width: 1), borderRadius: BorderRadius.circular(48), ), child: Row( @@ -529,11 +528,7 @@ class _DefineBrandPageState extends State<DefineBrandPage> { ), ), const SizedBox(width: 4), - Icon( - Icons.add, - size: 14, - color: Variables.textPrimary, - ), + Icon(Icons.add, size: 14, color: Variables.textPrimary), ], ), ), @@ -596,8 +591,8 @@ class _DefineBrandPageState extends State<DefineBrandPage> { fontWeight: FontWeight.w400, color: Variables.textSecondary, height: 20 / 14, - ), - border: InputBorder.none, + ), + border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, contentPadding: const EdgeInsets.symmetric( @@ -618,7 +613,10 @@ class _DefineBrandPageState extends State<DefineBrandPage> { final brand = _competitorBrands[index]; return Container( margin: const EdgeInsets.only(right: 8), - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), decoration: BoxDecoration( color: const Color(0xFFFAFAFA), border: Border.all( @@ -688,7 +686,8 @@ class _DefineBrandPageState extends State<DefineBrandPage> { final controller = TextEditingController(); showDialog<void>( context: context, - builder: (context) => AlertDialog( + builder: + (context) => AlertDialog( title: const Text('Add Keyword'), content: TextField( controller: controller, @@ -704,9 +703,9 @@ class _DefineBrandPageState extends State<DefineBrandPage> { onPressed: () { final val = controller.text.trim(); if (val.isNotEmpty && !_keywords.contains(val)) { - setState(() { - _keywords.add(val); - }); + setState(() { + _keywords.add(val); + }); } Navigator.pop(context); }, @@ -716,4 +715,4 @@ class _DefineBrandPageState extends State<DefineBrandPage> { ), ); } -} -\ No newline at end of file +} diff --git a/lib/ui/pages/home_page.dart b/lib/ui/pages/home_page.dart @@ -7,9 +7,7 @@ import 'package:adobe/data/repos/image_repo.dart'; import 'package:adobe/data/repos/file_repo.dart'; import 'package:adobe/services/project_service.dart'; import 'package:image/image.dart' as img; -import 'package:intl/intl.dart'; import 'project_detail_page.dart'; -import 'image_analysis_page.dart'; import 'define_brand_page.dart'; class HomePage extends StatefulWidget { @@ -28,7 +26,7 @@ class _HomePageState extends State<HomePage> { List<ProjectModel> _allProjects = []; List<FileModel> _recentFiles = []; Map<int, ProjectModel> _projectMap = {}; - Map<int, List<String>> _projectPreviews = {}; + final Map<int, List<String>> _projectPreviews = {}; Map<String, String> _fileDimensions = {}; bool _isLoading = true; final String _userName = "Alex"; // Can be loaded from preferences later @@ -115,7 +113,7 @@ class _HomePageState extends State<HomePage> { String _getProjectBreadcrumb(FileModel file) { final project = _projectMap[file.projectId]; if (project == null) return ''; - + // Check if project is an event (has parentId) if (project.isEvent) { final parentProject = _projectMap[project.parentId!]; @@ -227,20 +225,25 @@ class _HomePageState extends State<HomePage> { hintText: 'Search', hintStyle: TextStyle( fontSize: 12, - color: theme.colorScheme.onSurface.withOpacity(0.5), + color: theme.colorScheme.onSurface.withValues( + alpha: 0.5, + ), fontFamily: 'GeneralSans', ), prefixIcon: Icon( Icons.search, size: 18, - color: theme.colorScheme.onSurface.withOpacity(0.5), + color: theme.colorScheme.onSurface.withValues( + alpha: 0.5, + ), ), prefixIconConstraints: const BoxConstraints( minWidth: 50, minHeight: 18, ), filled: true, - fillColor: isDark ? Colors.grey[800] : Colors.grey[200], + fillColor: + isDark ? Colors.grey[800] : Colors.grey[200], border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, @@ -286,10 +289,21 @@ class _HomePageState extends State<HomePage> { }, ), const SizedBox(height: 12), - ...(_recentFiles.take(2).map((file) => Padding( - padding: const EdgeInsets.only(bottom: 12), - child: _buildRecentFileCard(file, theme, isDark), - )).toList()), + ...(_recentFiles + .take(2) + .map( + (file) => Padding( + padding: const EdgeInsets.only( + bottom: 12, + ), + child: _buildRecentFileCard( + file, + theme, + isDark, + ), + ), + ) + .toList()), const SizedBox(height: 24), ], @@ -309,7 +323,11 @@ class _HomePageState extends State<HomePage> { itemCount: _allProjects.length, itemBuilder: (context, index) { final project = _allProjects[index]; - return _buildProjectCard(project, theme, isDark); + return _buildProjectCard( + project, + theme, + isDark, + ); }, ), ), @@ -346,7 +364,11 @@ class _HomePageState extends State<HomePage> { ); } - Widget _buildSectionHeader(String title, ThemeData theme, {VoidCallback? onTap}) { + Widget _buildSectionHeader( + String title, + ThemeData theme, { + VoidCallback? onTap, + }) { return Row( children: [ Text( @@ -365,7 +387,7 @@ class _HomePageState extends State<HomePage> { child: Icon( Icons.chevron_right, size: 24, - color: theme.colorScheme.onSurface.withOpacity(0.6), + color: theme.colorScheme.onSurface.withValues(alpha: 0.6), ), ), ], @@ -400,14 +422,17 @@ class _HomePageState extends State<HomePage> { child: Image.file( File(file.filePath), fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) => Container( - color: isDark ? Colors.grey[800] : Colors.grey[200], - child: Icon( - Icons.broken_image, - size: 24, - color: theme.colorScheme.onSurface.withOpacity(0.3), - ), - ), + errorBuilder: + (context, error, stackTrace) => Container( + color: isDark ? Colors.grey[800] : Colors.grey[200], + child: Icon( + Icons.broken_image, + size: 24, + color: theme.colorScheme.onSurface.withValues( + alpha: 0.3, + ), + ), + ), ), ), ), @@ -433,7 +458,8 @@ class _HomePageState extends State<HomePage> { fontSize: 10, fontWeight: FontWeight.w500, fontFamily: 'Inter', - color: theme.colorScheme.onSurface.withOpacity(0.6), + color: theme.colorScheme.onSurface + .withValues(alpha: 0.6), ), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -457,7 +483,9 @@ class _HomePageState extends State<HomePage> { Icon( Icons.more_vert, size: 16, - color: theme.colorScheme.onSurface.withOpacity(0.6), + color: theme.colorScheme.onSurface.withValues( + alpha: 0.6, + ), ), ], ), @@ -467,7 +495,9 @@ class _HomePageState extends State<HomePage> { style: TextStyle( fontSize: 10, fontFamily: 'GeneralSans', - color: theme.colorScheme.onSurface.withOpacity(0.6), + color: theme.colorScheme.onSurface.withValues( + alpha: 0.6, + ), ), ), const SizedBox(height: 2), @@ -476,7 +506,9 @@ class _HomePageState extends State<HomePage> { style: TextStyle( fontSize: 10, fontFamily: 'GeneralSans', - color: theme.colorScheme.onSurface.withOpacity(0.4), + color: theme.colorScheme.onSurface.withValues( + alpha: 0.4, + ), ), ), ], @@ -523,8 +555,8 @@ class _HomePageState extends State<HomePage> { child: Icon( Icons.folder_outlined, size: 32, - color: theme.colorScheme.onSurface.withOpacity( - 0.3, + color: theme.colorScheme.onSurface.withValues( + alpha: 0.3, ), ), ), @@ -541,8 +573,9 @@ class _HomePageState extends State<HomePage> { : Colors.grey[200], child: Icon( Icons.broken_image, - color: theme.colorScheme.onSurface - .withOpacity(0.3), + color: theme.colorScheme.onSurface.withValues( + alpha: 0.3, + ), ), ), ), @@ -570,7 +603,7 @@ class _HomePageState extends State<HomePage> { Icon( Icons.more_vert, size: 16, - color: theme.colorScheme.onSurface.withOpacity(0.6), + color: theme.colorScheme.onSurface.withValues(alpha: 0.6), ), ], ), @@ -614,7 +647,7 @@ class _HomePageState extends State<HomePage> { child: Icon( Icons.image_outlined, size: 32, - color: theme.colorScheme.onSurface.withOpacity(0.3), + color: theme.colorScheme.onSurface.withValues(alpha: 0.3), ), ), ), @@ -637,7 +670,7 @@ class _HomePageState extends State<HomePage> { style: TextStyle( fontSize: 12, fontFamily: 'GeneralSans', - color: theme.colorScheme.onSurface.withOpacity(0.6), + color: theme.colorScheme.onSurface.withValues(alpha: 0.6), height: 1.2, ), maxLines: 1, @@ -650,4 +683,4 @@ class _HomePageState extends State<HomePage> { ), ); } -} -\ No newline at end of file +} diff --git a/lib/ui/pages/image_analysis_page.dart b/lib/ui/pages/image_analysis_page.dart @@ -168,7 +168,7 @@ class _ImageAnalysisPageState extends State<ImageAnalysisPage> { borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 4), ), diff --git a/lib/ui/pages/image_details_page.dart b/lib/ui/pages/image_details_page.dart @@ -1,7 +1,6 @@ import 'dart:io'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import '../../services/image_service.dart'; import '../../services/note_service.dart'; @@ -56,11 +55,11 @@ class ImageDetailsPage extends StatefulWidget { final int projectId; const ImageDetailsPage({ - Key? key, + super.key, required this.imagePath, required this.imageId, required this.projectId, - }) : super(key: key); + }); @override State<ImageDetailsPage> createState() => _ImageDetailsPageState(); @@ -318,8 +317,9 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> { void _onResizeUpdate(DragUpdateDetails details) { if (!_isResizing || _finalSelectionRect == null || - _activeHandle == DragHandle.none) + _activeHandle == DragHandle.none) { return; + } final pos = _getLocalPosition(details.globalPosition); if (pos == null) return; @@ -597,7 +597,7 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> { }); } - Navigator.pop(context); + if (context.mounted) Navigator.pop(context); } }, ), @@ -644,10 +644,11 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> { return GestureDetector( onTap: () { setState(() { - if (isSelected) + if (isSelected) { tempTags.remove(tag); - else + } else { tempTags.add(tag); + } }); }, child: Container( @@ -713,7 +714,7 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> { onPressed: () async { this.setState(() => _currentTags = tempTags); await _imageService.updateTags(widget.imageId, tempTags); - Navigator.pop(context); + if (context.mounted) Navigator.pop(context); }, child: const Text( "Save", @@ -757,9 +758,7 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> { ? Container( height: 40, decoration: BoxDecoration( - color: const Color( - 0xFFF3F4F6, - ), + color: const Color(0xFFF3F4F6), borderRadius: BorderRadius.circular(30), ), child: InkWell( @@ -972,7 +971,7 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> { boxShadow: [ BoxShadow( color: Colors.black - .withOpacity(0.3), + .withValues(alpha: 0.3), blurRadius: 4, offset: const Offset(0, 1), ), @@ -990,7 +989,7 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> { ), ), ); - }).toList(), + }), // Notes Button (Show only when not in selection mode) if (!isSelectionModeActive) @@ -1235,11 +1234,10 @@ class _NotesListSheet extends StatefulWidget { final VoidCallback onAddNotePressed; const _NotesListSheet({ - Key? key, required this.notes, this.highlightId, required this.onAddNotePressed, - }) : super(key: key); + }); @override State<_NotesListSheet> createState() => __NotesListSheetState(); @@ -1407,10 +1405,10 @@ class NoteModalOverlay extends StatelessWidget { final Size screenSize; const NoteModalOverlay({ - Key? key, + super.key, required this.modalContent, required this.screenSize, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -1532,7 +1530,7 @@ class ResizingSelectionOverlayPainter extends CustomPainter { const double handleRadius = 8; final Paint handleShadow = Paint() - ..color = Colors.black.withOpacity(0.3) + ..color = Colors.black.withValues(alpha: 0.3) ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 2); final Paint handleFill = Paint()..color = Colors.white; final Paint handleBorder = diff --git a/lib/ui/pages/image_save_page.dart b/lib/ui/pages/image_save_page.dart @@ -32,13 +32,13 @@ class ImageSavePage extends StatefulWidget { final String? parentProjectName; const ImageSavePage({ - Key? key, + super.key, required this.imagePaths, required this.projectId, required this.projectName, this.isFromShare = true, this.parentProjectName, - }) : super(key: key); + }); @override State<ImageSavePage> createState() => _ImageSavePageState(); @@ -315,8 +315,9 @@ class _ImageSavePageState extends State<ImageSavePage> { void _onResizeUpdate(DragUpdateDetails details) { if (!_isResizing || _finalSelectionRect == null || - _activeHandle == DragHandle.none) + _activeHandle == DragHandle.none) { return; + } final pos = _getLocalPosition(details.globalPosition); if (pos == null) return; @@ -835,8 +836,8 @@ class _ImageSavePageState extends State<ImageSavePage> { shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: Colors.black.withOpacity( - 0.3, + color: Colors.black.withValues( + alpha: 0.3, ), blurRadius: 4, offset: const Offset(0, 1), @@ -849,7 +850,7 @@ class _ImageSavePageState extends State<ImageSavePage> { ), ), ); - }).toList(), + }), // PAGE DOTS if (widget.imagePaths.length > 1 && !isPageLocked) @@ -873,8 +874,8 @@ class _ImageSavePageState extends State<ImageSavePage> { color: _currentImageIndex == index ? Colors.blue - : Colors.white.withOpacity( - 0.5, + : Colors.white.withValues( + alpha: 0.5, ), ), ), @@ -993,7 +994,7 @@ class _ImageSavePageState extends State<ImageSavePage> { ), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, -5), ), @@ -1114,10 +1115,10 @@ class NoteModalOverlay extends StatelessWidget { final Size screenSize; const NoteModalOverlay({ - Key? key, + super.key, required this.modalContent, required this.screenSize, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -1254,7 +1255,7 @@ class SelectionOverlayPainter extends CustomPainter { final Paint handleShadow = Paint() - ..color = Colors.black.withOpacity(0.3) + ..color = Colors.black.withValues(alpha: 0.3) ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 2); final Paint handleFill = Paint()..color = Colors.white; diff --git a/lib/ui/pages/project_board_page_alternate.dart b/lib/ui/pages/project_board_page_alternate.dart @@ -23,7 +23,7 @@ class ProjectBoardPageAlternateState extends State<ProjectBoardPageAlternate> { List<ImageModel> _filteredImages = []; List<String> _allTags = []; - Set<String> _selectedTags = {}; + final Set<String> _selectedTags = {}; bool _isLoading = true; @override @@ -133,7 +133,7 @@ class ProjectBoardPageAlternateState extends State<ProjectBoardPageAlternate> { } }); // Update main state - this.setState(() { + setState(() { _applyFilter(); }); }, @@ -162,7 +162,7 @@ class ProjectBoardPageAlternateState extends State<ProjectBoardPageAlternate> { Expanded( child: TextButton( onPressed: () { - this.setState(() { + setState(() { _selectedTags.clear(); _applyFilter(); }); @@ -365,7 +365,7 @@ class ProjectBoardPageAlternateState extends State<ProjectBoardPageAlternate> { begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [ - Colors.black.withOpacity(0.8), + Colors.black.withValues(alpha: 0.8), Colors.transparent, ], ), @@ -381,10 +381,10 @@ class ProjectBoardPageAlternateState extends State<ProjectBoardPageAlternate> { vertical: 2, ), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), + color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(4), border: Border.all( - color: Colors.white.withOpacity(0.1), + color: Colors.white.withValues(alpha: 0.1), ), ), child: Text( diff --git a/lib/ui/pages/project_detail_page.dart b/lib/ui/pages/project_detail_page.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'package:flutter/material.dart'; import '../../data/models/project_model.dart'; import '../../data/repos/project_repo.dart'; @@ -212,7 +211,7 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> { _project!.description!, style: TextStyle( fontSize: 14, - color: theme.colorScheme.onSurface.withOpacity(0.7), + color: theme.colorScheme.onSurface.withValues(alpha: 0.7), fontFamily: 'GeneralSans', ), ), @@ -307,7 +306,7 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> { ), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.03), + color: Colors.black.withValues(alpha: 0.03), blurRadius: 8, offset: const Offset(0, 2), ), @@ -339,7 +338,7 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> { event.description!, style: TextStyle( fontSize: 13, - color: theme.colorScheme.onSurface.withOpacity(0.6), + color: theme.colorScheme.onSurface.withValues(alpha: 0.6), ), maxLines: 2, overflow: TextOverflow.ellipsis, diff --git a/lib/ui/pages/project_file_page.dart b/lib/ui/pages/project_file_page.dart @@ -219,8 +219,8 @@ class _ProjectFilePageState extends State<ProjectFilePage> { Text( "${_files.length} items", style: TextStyle( - color: theme.colorScheme.onSurface.withOpacity( - 0.6, + color: theme.colorScheme.onSurface.withValues( + alpha: 0.6, ), fontFamily: 'GeneralSans', ), @@ -295,7 +295,7 @@ class _ProjectFilePageState extends State<ProjectFilePage> { ), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.03), + color: Colors.black.withValues(alpha: 0.03), blurRadius: 8, offset: const Offset(0, 2), ), @@ -312,7 +312,7 @@ class _ProjectFilePageState extends State<ProjectFilePage> { width: 48, height: 48, decoration: BoxDecoration( - color: theme.colorScheme.primary.withOpacity(0.1), + color: theme.colorScheme.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Icon( @@ -338,7 +338,9 @@ class _ProjectFilePageState extends State<ProjectFilePage> { "Updated $dateStr", style: TextStyle( fontSize: 12, - color: theme.colorScheme.onSurface.withOpacity(0.5), + color: theme.colorScheme.onSurface.withValues( + alpha: 0.5, + ), fontFamily: 'GeneralSans', ), ), @@ -374,7 +376,7 @@ class _ProjectFilePageState extends State<ProjectFilePage> { file.description!, style: TextStyle( fontSize: 13, - color: theme.colorScheme.onSurface.withOpacity(0.7), + color: theme.colorScheme.onSurface.withValues(alpha: 0.7), fontFamily: 'GeneralSans', ), maxLines: 2, diff --git a/lib/ui/pages/stylesheet_page.dart b/lib/ui/pages/stylesheet_page.dart @@ -15,10 +15,7 @@ import 'package:path_provider/path_provider.dart'; class StylesheetPage extends StatefulWidget { final int projectId; - const StylesheetPage({ - super.key, - required this.projectId, - }); + const StylesheetPage({super.key, required this.projectId}); @override State<StylesheetPage> createState() => _StylesheetPageState(); @@ -27,10 +24,10 @@ class StylesheetPage extends StatefulWidget { class _StylesheetPageState extends State<StylesheetPage> { late int _currentProjectId; bool _isLoading = false; - + Map<String, dynamic>? _stylesheetMap; String? _rawJsonString; - + // New state variable to hold assets from ProjectRepo List<String> _projectAssets = []; @@ -48,18 +45,21 @@ class _StylesheetPageState extends State<StylesheetPage> { String _cleanJsonString(String raw) { String cleaned = raw; cleaned = cleaned.replaceAllMapped( - RegExp(r'([{,]\s*)([a-zA-Z0-9_\s/]+)(\s*:)'), - (match) => '${match[1]}"${match[2]?.trim()}"${match[3]}' + RegExp(r'([{,]\s*)([a-zA-Z0-9_\s/]+)(\s*:)'), + (match) => '${match[1]}"${match[2]?.trim()}"${match[3]}', ); cleaned = cleaned.replaceAllMapped( RegExp(r'(:\s*)([a-zA-Z0-9_\-\.\/\s]+)(?=\s*[,}])'), (match) { String val = match[2]!.trim(); - if (val == 'true' || val == 'false' || val == 'null' || double.tryParse(val) != null) { + if (val == 'true' || + val == 'false' || + val == 'null' || + double.tryParse(val) != null) { return match[0]!; } return '${match[1]}"$val"'; - } + }, ); return cleaned; } @@ -69,20 +69,24 @@ class _StylesheetPageState extends State<StylesheetPage> { return _fontNameCache[dirtyName]!; } - String cleanInput = dirtyName.toLowerCase() + String cleanInput = dirtyName + .toLowerCase() .replaceAll(RegExp(r'[-_]regular$'), '') .replaceAll(RegExp(r'[^a-z0-9]'), ''); final allFonts = GoogleFonts.asMap().keys; - + for (String officialName in allFonts) { - String cleanOfficial = officialName.toLowerCase().replaceAll(RegExp(r'[^a-z0-9]'), ''); + String cleanOfficial = officialName.toLowerCase().replaceAll( + RegExp(r'[^a-z0-9]'), + '', + ); if (cleanOfficial == cleanInput) { _fontNameCache[dirtyName] = officialName; return officialName; } } - return dirtyName; + return dirtyName; } Future<File?> _resolveFile(String path) async { @@ -92,12 +96,14 @@ class _StylesheetPageState extends State<StylesheetPage> { // 2. If that fails (iOS UUID change?), try to find it in the current docs dir try { - final filename = p.basename(path); // Get "image_123.png" from the long path + final filename = p.basename( + path, + ); // Get "image_123.png" from the long path final dir = await getApplicationDocumentsDirectory(); - + // Reconstruct path: CurrentDir + generated_images + filename final fixedPath = p.join(dir.path, 'generated_images', filename); - + final fixedFile = File(fixedPath); if (await fixedFile.exists()) { return fixedFile; @@ -111,7 +117,7 @@ class _StylesheetPageState extends State<StylesheetPage> { Future<void> _loadSavedStylesheet() async { final project = await ProjectRepo().getProjectById(_currentProjectId); - + if (project == null) return; // 1. Load Assets directly from Project Model @@ -121,10 +127,11 @@ class _StylesheetPageState extends State<StylesheetPage> { Map<String, dynamic>? parsedMap; String? rawJson; - if (project.globalStylesheet != null && project.globalStylesheet!.isNotEmpty) { + if (project.globalStylesheet != null && + project.globalStylesheet!.isNotEmpty) { rawJson = project.globalStylesheet!; dynamic parsed; - + try { parsed = jsonDecode(rawJson); } catch (e) { @@ -134,7 +141,9 @@ class _StylesheetPageState extends State<StylesheetPage> { } if (parsed is String) { - try { parsed = jsonDecode(parsed); } catch (_) {} + try { + parsed = jsonDecode(parsed); + } catch (_) {} } if (parsed is Map<String, dynamic>) { @@ -165,19 +174,21 @@ class _StylesheetPageState extends State<StylesheetPage> { try { // 1. Fetch Image Analysis Data final images = await ImageRepo().getImages(_currentProjectId); - final List<String> analysisData = images - .map((img) => img.analysisData) - .where((data) => data != null && data.isNotEmpty) - .cast<String>() - .toList(); + final List<String> analysisData = + images + .map((img) => img.analysisData) + .where((data) => data != null && data.isNotEmpty) + .cast<String>() + .toList(); // 2. Fetch Note Analysis Data final notes = await NoteRepo().getNotesByProjectId(_currentProjectId); - final List<String> noteAnalysisData = notes - .map((n) => n.analysisData) - .where((data) => data != null && data.isNotEmpty) - .cast<String>() - .toList(); + final List<String> noteAnalysisData = + notes + .map((n) => n.analysisData) + .where((data) => data != null && data.isNotEmpty) + .cast<String>() + .toList(); // 3. Combine both analysisData.addAll(noteAnalysisData); @@ -196,7 +207,7 @@ class _StylesheetPageState extends State<StylesheetPage> { if (mounted && result != null) { final jsonString = jsonEncode(result); await ProjectRepo().updateStylesheet(_currentProjectId, jsonString); - + // Reload everything (assets + stylesheet) to keep sync await _loadSavedStylesheet(); } @@ -212,7 +223,8 @@ class _StylesheetPageState extends State<StylesheetPage> { for (var k in keys) { if (_stylesheetMap!.containsKey(k)) return _stylesheetMap![k]; for (var mapKey in _stylesheetMap!.keys) { - if (mapKey.toLowerCase() == k.toLowerCase()) return _stylesheetMap![mapKey]; + if (mapKey.toLowerCase() == k.toLowerCase()) + return _stylesheetMap![mapKey]; } } return null; @@ -225,16 +237,22 @@ class _StylesheetPageState extends State<StylesheetPage> { appBar: TopBar( currentProjectId: _currentProjectId, onBack: () => Navigator.of(context).pop(), - onProjectChanged: (p) => setState(() { - _currentProjectId = p.id!; - _stylesheetMap = null; - _projectAssets = []; - _loadSavedStylesheet(); - }), + onProjectChanged: + (p) => setState(() { + _currentProjectId = p.id!; + _stylesheetMap = null; + _projectAssets = []; + _loadSavedStylesheet(); + }), ), - body: _isLoading - ? const Center(child: CircularProgressIndicator(color: Variables.textPrimary)) - : (_stylesheetMap == null && _rawJsonString == null && _projectAssets.isEmpty) + body: + _isLoading + ? const Center( + child: CircularProgressIndicator(color: Variables.textPrimary), + ) + : (_stylesheetMap == null && + _rawJsonString == null && + _projectAssets.isEmpty) ? _buildEmptyState() : _buildContent(), bottomNavigationBar: BottomBar( @@ -249,7 +267,10 @@ class _StylesheetPageState extends State<StylesheetPage> { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text("No stylesheet data.", style: Variables.headerStyle.copyWith(fontSize: 18)), + Text( + "No stylesheet data.", + style: Variables.headerStyle.copyWith(fontSize: 18), + ), const SizedBox(height: 24), _buildGenerateButton("Generate Stylesheet"), ], @@ -264,11 +285,18 @@ class _StylesheetPageState extends State<StylesheetPage> { final emotions = _getData(['Emotions', 'emotions']); final era = _getData(['Era/Cultural Reference', 'era']); final typography = _getData(['Typography', 'fonts']); - + // We check _projectAssets.isNotEmpty to determine if we show the section final hasAssets = _projectAssets.isNotEmpty; - - final foundAny = (style != null || lighting != null || colors != null || emotions != null || era != null || typography != null || hasAssets); + + final foundAny = + (style != null || + lighting != null || + colors != null || + emotions != null || + era != null || + typography != null || + hasAssets); return RefreshIndicator( onRefresh: _generateStylesheet, @@ -284,7 +312,11 @@ class _StylesheetPageState extends State<StylesheetPage> { children: [ const Text( "Visual Identity", - style: TextStyle(fontFamily: 'GeneralSans', fontSize: 24, fontWeight: FontWeight.w600), + style: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 24, + fontWeight: FontWeight.w600, + ), ), IconButton( icon: const Icon(Icons.refresh), @@ -297,12 +329,10 @@ class _StylesheetPageState extends State<StylesheetPage> { if (foundAny) ...[ // 1. Assets / Subjects Section (From Project Repo) - if (hasAssets) - _buildAssetsSection(_projectAssets), + if (hasAssets) _buildAssetsSection(_projectAssets), // 2. Typography (Now a Slider) - if (typography != null) - _buildTypographySection(typography), + if (typography != null) _buildTypographySection(typography), // 3. Color Palette if (colors != null) ...[ @@ -320,11 +350,12 @@ class _StylesheetPageState extends State<StylesheetPage> { ], // 4. Slider Sections - if (style != null) _buildSliderSection("Style & Aesthetic", style), - if (emotions != null) _buildSliderSection("Mood & Emotions", emotions), + if (style != null) + _buildSliderSection("Style & Aesthetic", style), + if (emotions != null) + _buildSliderSection("Mood & Emotions", emotions), if (lighting != null) _buildSliderSection("Lighting", lighting), if (era != null) _buildSliderSection("Era & Culture", era), - ] else ...[ Padding( padding: const EdgeInsets.symmetric(horizontal: 20), @@ -338,7 +369,7 @@ class _StylesheetPageState extends State<StylesheetPage> { ), ), ], - + const SizedBox(height: 40), Center(child: _buildGenerateButton("Regenerate")), const SizedBox(height: 40), @@ -352,7 +383,8 @@ class _StylesheetPageState extends State<StylesheetPage> { return GestureDetector( onTap: _generateStylesheet, child: Container( - width: 200, height: 44, + width: 200, + height: 44, decoration: BoxDecoration( color: Variables.textPrimary, borderRadius: BorderRadius.circular(112), @@ -369,8 +401,11 @@ class _StylesheetPageState extends State<StylesheetPage> { child: Text( title.toUpperCase(), style: const TextStyle( - fontFamily: 'GeneralSans', fontSize: 12, fontWeight: FontWeight.bold, - letterSpacing: 1.2, color: Variables.textSecondary, + fontFamily: 'GeneralSans', + fontSize: 12, + fontWeight: FontWeight.bold, + letterSpacing: 1.2, + color: Variables.textSecondary, ), ), ); @@ -415,11 +450,13 @@ class _StylesheetPageState extends State<StylesheetPage> { return Container( decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular(12), // Matching color card radius + borderRadius: BorderRadius.circular( + 12, + ), // Matching color card radius border: Border.all(color: Variables.borderSubtle), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.03), + color: Colors.black.withValues(alpha: 0.03), blurRadius: 8, offset: const Offset(0, 2), ), @@ -430,14 +467,19 @@ class _StylesheetPageState extends State<StylesheetPage> { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded( - child: exists - ? Image.file(file, fit: BoxFit.cover) - : Container( - color: Colors.grey.shade100, - child: const Center( - child: Icon(Icons.broken_image, color: Colors.grey, size: 20), + child: + exists + ? Image.file(file, fit: BoxFit.cover) + : Container( + color: Colors.grey.shade100, + child: const Center( + child: Icon( + Icons.broken_image, + color: Colors.grey, + size: 20, + ), + ), ), - ), ), // Container( // padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), @@ -496,7 +538,8 @@ class _StylesheetPageState extends State<StylesheetPage> { scrollDirection: Axis.horizontal, itemCount: fontNames.length, separatorBuilder: (_, __) => const SizedBox(width: 12), - itemBuilder: (context, index) => _buildTypographyCard(fontNames[index]), + itemBuilder: + (context, index) => _buildTypographyCard(fontNames[index]), ), ), const SizedBox(height: 32), @@ -507,7 +550,7 @@ class _StylesheetPageState extends State<StylesheetPage> { Widget _buildTypographyCard(String rawFontName) { final String correctFontName = _resolveGoogleFontName(rawFontName); TextStyle sampleStyle; - + try { sampleStyle = GoogleFonts.getFont(correctFontName); } catch (_) { @@ -523,7 +566,7 @@ class _StylesheetPageState extends State<StylesheetPage> { border: Border.all(color: Variables.borderSubtle), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.03), + color: Colors.black.withValues(alpha: 0.03), blurRadius: 8, offset: const Offset(0, 2), ), @@ -537,10 +580,10 @@ class _StylesheetPageState extends State<StylesheetPage> { child: Text( "Aa", style: sampleStyle.copyWith( - fontSize: 56, - height: 1, - fontWeight: FontWeight.w400, - color: Colors.black + fontSize: 56, + height: 1, + fontWeight: FontWeight.w400, + color: Colors.black, ), ), ), @@ -605,7 +648,8 @@ class _StylesheetPageState extends State<StylesheetPage> { Widget _buildColorCard(String label) { Color color = _getColorFromLabel(label); - String hexCode = "#${color.value.toRadixString(16).substring(2).toUpperCase()}"; + String hexCode = + "#${color.value.toRadixString(16).substring(2).toUpperCase()}"; return Container( decoration: BoxDecoration( @@ -617,10 +661,7 @@ class _StylesheetPageState extends State<StylesheetPage> { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - flex: 3, - child: Container(color: color), - ), + Expanded(flex: 3, child: Container(color: color)), Expanded( flex: 2, child: Padding( @@ -675,7 +716,7 @@ class _StylesheetPageState extends State<StylesheetPage> { child: _buildSectionHeader(title), ), SizedBox( - height: 120, + height: 120, child: ListView.separated( padding: const EdgeInsets.symmetric(horizontal: 20), scrollDirection: Axis.horizontal, @@ -684,7 +725,7 @@ class _StylesheetPageState extends State<StylesheetPage> { itemBuilder: (context, index) { final item = items[index]; final label = item['label']?.toString() ?? ''; - + return Container( width: 120, height: 120, @@ -695,7 +736,7 @@ class _StylesheetPageState extends State<StylesheetPage> { border: Border.all(color: Variables.borderSubtle), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.03), + color: Colors.black.withValues(alpha: 0.03), blurRadius: 8, offset: const Offset(0, 2), ), diff --git a/lib/ui/widgets/image_context_menu.dart b/lib/ui/widgets/image_context_menu.dart @@ -1,9 +1,7 @@ -import 'dart:io'; import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import '../../data/models/image_model.dart'; -import '../../services/image_service.dart'; import '../../utils/image_actions_helper.dart'; import '../styles/variables.dart'; @@ -44,7 +42,8 @@ class ImageContextMenu extends StatelessWidget { Widget build(BuildContext context) { return GestureDetector( // Get exact touch position - onLongPressStart: (details) => _showRadialMenu(context, details.globalPosition), + onLongPressStart: + (details) => _showRadialMenu(context, details.globalPosition), child: child, ); } @@ -65,14 +64,15 @@ class _RadialMenuOverlay extends StatefulWidget { State<_RadialMenuOverlay> createState() => _RadialMenuOverlayState(); } -class _RadialMenuOverlayState extends State<_RadialMenuOverlay> with SingleTickerProviderStateMixin { +class _RadialMenuOverlayState extends State<_RadialMenuOverlay> + with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _scaleAnimation; // Configuration - final double radius = 120.0; - final double buttonSize = 56.0; - final double arcSpan = 150.0; + final double radius = 120.0; + final double buttonSize = 56.0; + final double arcSpan = 150.0; @override void initState() { @@ -81,12 +81,12 @@ class _RadialMenuOverlayState extends State<_RadialMenuOverlay> with SingleTicke duration: const Duration(milliseconds: 350), vsync: this, ); - + _scaleAnimation = CurvedAnimation( parent: _controller, curve: Curves.easeOutBack, ); - + _controller.forward(); } @@ -144,7 +144,10 @@ class _RadialMenuOverlayState extends State<_RadialMenuOverlay> with SingleTicke void _notImplemented() { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: const Text("Coming soon", style: TextStyle(fontFamily: 'GeneralSans')), + content: const Text( + "Coming soon", + style: TextStyle(fontFamily: 'GeneralSans'), + ), backgroundColor: Variables.textPrimary, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), @@ -163,7 +166,7 @@ class _RadialMenuOverlayState extends State<_RadialMenuOverlay> with SingleTicke double baseAngle = isRightSide ? 180 : 0; // 2. Determine Vertical clipping adjustments - double topBoundary = 120; + double topBoundary = 120; double bottomBoundary = screenSize.height - 120; double rotationOffset = 0; @@ -181,21 +184,24 @@ class _RadialMenuOverlayState extends State<_RadialMenuOverlay> with SingleTicke final buttons = [ _MenuButtonData( svgPath: 'assets/icons/share_icon.svg', - onTap: _shareImage + onTap: _shareImage, ), _MenuButtonData( svgPath: 'assets/icons/files_icon.svg', - onTap: _sendToFiles + onTap: _sendToFiles, + ), + _MenuButtonData( + icon: Icons.drive_file_rename_outline, + onTap: _renameImage, ), - _MenuButtonData(icon: Icons.drive_file_rename_outline, onTap: _renameImage), _MenuButtonData( svgPath: 'assets/icons/trash_icon.svg', onTap: _deleteImage, - isDestructive: true + isDestructive: true, ), _MenuButtonData( svgPath: 'assets/icons/ai-search.svg', - onTap: _notImplemented + onTap: _notImplemented, ), ]; @@ -215,11 +221,8 @@ class _RadialMenuOverlayState extends State<_RadialMenuOverlay> with SingleTicke final step = arcSpan / (buttons.length - 1); final startAngle = baseAngle - (arcSpan / 2); final angleDeg = startAngle + (step * index); - - return _buildAnimatedButton( - angleDeg: angleDeg, - data: buttons[index], - ); + + return _buildAnimatedButton(angleDeg: angleDeg, data: buttons[index]); }), ], ); @@ -243,10 +246,7 @@ class _RadialMenuOverlayState extends State<_RadialMenuOverlay> with SingleTicke top: dy, child: Transform.scale( scale: _scaleAnimation.value, - child: _FloatingCircleButton( - size: buttonSize, - data: data, - ), + child: _FloatingCircleButton(size: buttonSize, data: data), ), ); }, @@ -273,10 +273,7 @@ class _FloatingCircleButton extends StatefulWidget { final double size; final _MenuButtonData data; - const _FloatingCircleButton({ - required this.size, - required this.data, - }); + const _FloatingCircleButton({required this.size, required this.data}); @override State<_FloatingCircleButton> createState() => _FloatingCircleButtonState(); @@ -287,7 +284,8 @@ class _FloatingCircleButtonState extends State<_FloatingCircleButton> { @override Widget build(BuildContext context) { - final Color normalIconColor = widget.data.isDestructive ? Colors.red : Variables.textPrimary; + final Color normalIconColor = + widget.data.isDestructive ? Colors.red : Variables.textPrimary; final Color activeIconColor = Colors.white; final Color normalBgColor = Colors.white; @@ -314,21 +312,22 @@ class _FloatingCircleButtonState extends State<_FloatingCircleButton> { shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.15), + color: Colors.black.withValues(alpha: 0.15), blurRadius: 10, offset: const Offset(0, 4), ), ], ), padding: const EdgeInsets.all(14), - child: widget.data.svgPath != null - ? SvgPicture.asset( - widget.data.svgPath!, - width: 18, - height: 18, - colorFilter: ColorFilter.mode(currentColor, BlendMode.srcIn), - ) - : Icon(widget.data.icon, color: currentColor, size: 24), + child: + widget.data.svgPath != null + ? SvgPicture.asset( + widget.data.svgPath!, + width: 18, + height: 18, + colorFilter: ColorFilter.mode(currentColor, BlendMode.srcIn), + ) + : Icon(widget.data.icon, color: currentColor, size: 24), ), ); } diff --git a/lib/utils/image_actions_helper.dart b/lib/utils/image_actions_helper.dart @@ -28,43 +28,70 @@ class ImageActionsHelper { ImageModel image, VoidCallback onSuccess, ) async { - final TextEditingController nameController = TextEditingController(text: image.name); + final TextEditingController nameController = TextEditingController( + text: image.name, + ); await showDialog( context: context, - builder: (ctx) => AlertDialog( - backgroundColor: Colors.white, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), - title: const Text("Rename Image", style: TextStyle(fontFamily: 'GeneralSans', fontWeight: FontWeight.w600)), - content: TextField( - controller: nameController, - autofocus: true, - decoration: InputDecoration( - hintText: "Enter new name", - filled: true, - fillColor: Variables.surfaceSubtle, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide.none, + builder: + (ctx) => AlertDialog( + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), ), + title: const Text( + "Rename Image", + style: TextStyle( + fontFamily: 'GeneralSans', + fontWeight: FontWeight.w600, + ), + ), + content: TextField( + controller: nameController, + autofocus: true, + decoration: InputDecoration( + hintText: "Enter new name", + filled: true, + fillColor: Variables.surfaceSubtle, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx), + child: const Text( + "Cancel", + style: TextStyle( + color: Variables.textSecondary, + fontFamily: 'GeneralSans', + ), + ), + ), + TextButton( + onPressed: () async { + if (nameController.text.isNotEmpty) { + await ImageService().renameImage( + image.id, + nameController.text.trim(), + ); + onSuccess(); + if (ctx.mounted) Navigator.pop(ctx); + } + }, + child: const Text( + "Save", + style: TextStyle( + color: Variables.textPrimary, + fontFamily: 'GeneralSans', + fontWeight: FontWeight.w600, + ), + ), + ), + ], ), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx), - child: const Text("Cancel", style: TextStyle(color: Variables.textSecondary, fontFamily: 'GeneralSans')), - ), - TextButton( - onPressed: () async { - if (nameController.text.isNotEmpty) { - await ImageService().renameImage(image.id, nameController.text.trim()); - onSuccess(); - Navigator.pop(ctx); - } - }, - child: const Text("Save", style: TextStyle(color: Variables.textPrimary, fontFamily: 'GeneralSans', fontWeight: FontWeight.w600)), - ), - ], - ), ); } @@ -75,25 +102,50 @@ class ImageActionsHelper { ) async { final confirm = await showDialog<bool>( context: context, - builder: (ctx) => AlertDialog( - backgroundColor: Colors.white, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), - title: const Text("Delete Image?", style: TextStyle(fontFamily: 'GeneralSans', fontWeight: FontWeight.w600)), - content: const Text( - "This action cannot be undone.", - style: TextStyle(fontFamily: 'GeneralSans', color: Variables.textSecondary), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx, false), - child: const Text("Cancel", style: TextStyle(color: Variables.textSecondary, fontFamily: 'GeneralSans')), - ), - TextButton( - onPressed: () => Navigator.pop(ctx, true), - child: const Text("Delete", style: TextStyle(color: Colors.red, fontFamily: 'GeneralSans', fontWeight: FontWeight.w600)), + builder: + (ctx) => AlertDialog( + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + title: const Text( + "Delete Image?", + style: TextStyle( + fontFamily: 'GeneralSans', + fontWeight: FontWeight.w600, + ), + ), + content: const Text( + "This action cannot be undone.", + style: TextStyle( + fontFamily: 'GeneralSans', + color: Variables.textSecondary, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx, false), + child: const Text( + "Cancel", + style: TextStyle( + color: Variables.textSecondary, + fontFamily: 'GeneralSans', + ), + ), + ), + TextButton( + onPressed: () => Navigator.pop(ctx, true), + child: const Text( + "Delete", + style: TextStyle( + color: Colors.red, + fontFamily: 'GeneralSans', + fontWeight: FontWeight.w600, + ), + ), + ), + ], ), - ], - ), ); if (confirm == true) {