creek

The AI Image Editor of 2030
commit 860f3ed75e8e707bf57e491c76d48300870aa9ae
parent c3ba6bac163a343e61a0d89d86b8af29d299c367
Author: Nilotpal Gupta <nilotpalgupta0701@gmail.com>
Date:   Sun, 30 Nov 2025 02:03:23 +0530

clenup + formated

Diffstat:
Mlib/data/database.dart | 7+++++--
Mlib/data/models/file_model.dart | 10++++++----
Mlib/data/models/image_model.dart | 3++-
Mlib/data/models/note_model.dart | 2+-
Mlib/data/repos/file_repo.dart | 27++++++++++++++++++---------
Mlib/data/repos/project_repo.dart | 2+-
Mlib/main.dart | 21++++++++++++---------
Mlib/services/analysis_queue_manager.dart | 33++++++++++++++++++++-------------
Mlib/services/analyze/embedding.dart | 62++++++++++++++++++++++++++++++++++----------------------------
Mlib/services/analyze/font.dart | 90++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Mlib/services/analyze/texture.dart | 84+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mlib/services/download_service.dart | 4++--
Mlib/services/file_service.dart | 23+++++++++++++++++++----
Mlib/services/image_service.dart | 4+++-
Mlib/services/instagram_download_service.dart | 10+++++++---
Mlib/services/project_service.dart | 31+++++++++++++++++++++++++------
Mlib/services/python_service.dart | 29++++++++++++++++++++---------
Mlib/services/theme_service.dart | 3+--
Mlib/ui/pages/image_analysis_page.dart | 83+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mlib/ui/pages/image_save_page.dart | 2+-
Mlib/ui/pages/project_detail_page.dart | 157+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mlib/ui/pages/share_handler_page.dart | 64+++++++++++++++++++++++++++++++++++++---------------------------
Mlib/ui/pages/stylesheet_page.dart | 66++++++++++++++++++++++++++++++++++++++----------------------------
Mlib/ui/styles/variables.dart | 10+++++-----
Mlib/ui/widgets/bottom_bar.dart | 14+++++---------
Mlib/ui/widgets/top_bar.dart | 92+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mlib/utils/clip_image_processor.dart | 10+++++-----
Mlib/utils/image_utils.dart | 3+--
28 files changed, 558 insertions(+), 388 deletions(-)

diff --git a/lib/data/database.dart b/lib/data/database.dart @@ -42,10 +42,13 @@ class AppDatabase { '''); // Create Inbox Project (ID 0) for Drafts - await db.rawInsert(''' + await db.rawInsert( + ''' INSERT INTO projects (id, title, description, last_accessed_at, created_at) VALUES (0, 'Inbox', 'Holding area for shared images', ?, ?) - ''', [DateTime.now().toIso8601String(), DateTime.now().toIso8601String()]); + ''', + [DateTime.now().toIso8601String(), DateTime.now().toIso8601String()], + ); // 2. IMAGES (Moodboard) await db.execute(''' diff --git a/lib/data/models/file_model.dart b/lib/data/models/file_model.dart @@ -41,10 +41,12 @@ class FileModel { filePath: map['file_path'], name: map['name'] ?? 'Untitled', description: map['description'], - tags: map['tags'] != null ? List<String>.from(jsonDecode(map['tags'])) : [], - lastUpdated: map['last_updated'] != null - ? DateTime.parse(map['last_updated']) - : DateTime.parse(map['created_at']), + tags: + map['tags'] != null ? List<String>.from(jsonDecode(map['tags'])) : [], + lastUpdated: + map['last_updated'] != null + ? DateTime.parse(map['last_updated']) + : DateTime.parse(map['created_at']), createdAt: DateTime.parse(map['created_at']), ); } diff --git a/lib/data/models/image_model.dart b/lib/data/models/image_model.dart @@ -40,7 +40,8 @@ class ImageModel { projectId: map['project_id'], filePath: map['file_path'], name: map['name'] ?? 'Untitled', - tags: map['tags'] != null ? List<String>.from(jsonDecode(map['tags'])) : [], + tags: + map['tags'] != null ? List<String>.from(jsonDecode(map['tags'])) : [], analysisData: map['analysis_data'], createdAt: DateTime.parse(map['created_at']), status: map['status'] ?? 'pending', diff --git a/lib/data/models/note_model.dart b/lib/data/models/note_model.dart @@ -18,7 +18,7 @@ class NoteModel { this.normX = 0.5, this.normY = 0.5, this.normWidth = 0.0, - this.normHeight = 0.0 + this.normHeight = 0.0, }); Map<String, dynamic> toMap() { diff --git a/lib/data/repos/file_repo.dart b/lib/data/repos/file_repo.dart @@ -14,7 +14,7 @@ class FileRepo { 'files', where: 'project_id = ?', whereArgs: [projectId], - orderBy: 'last_updated DESC', + orderBy: 'last_updated DESC', ); return res.map((e) => FileModel.fromMap(e)).toList(); } @@ -26,10 +26,15 @@ class FileRepo { return null; } - Future<void> updateDetails(String id, {String? name, String? description, List<String>? tags}) async { + Future<void> updateDetails( + String id, { + String? name, + String? description, + List<String>? tags, + }) async { final db = await AppDatabase.db; final Map<String, dynamic> updates = { - 'last_updated': DateTime.now().toIso8601String() + 'last_updated': DateTime.now().toIso8601String(), }; if (name != null) updates['name'] = name; if (description != null) updates['description'] = description; @@ -41,10 +46,10 @@ class FileRepo { Future<void> touchFile(String id) async { final db = await AppDatabase.db; await db.update( - 'files', - {'last_updated': DateTime.now().toIso8601String()}, - where: 'id = ?', - whereArgs: [id] + 'files', + {'last_updated': DateTime.now().toIso8601String()}, + where: 'id = ?', + whereArgs: [id], ); } @@ -53,11 +58,15 @@ class FileRepo { await db.delete('files', where: 'id = ?', whereArgs: [id]); } - Future<List<String>> getAllFilePathsForProjectIds(List<int> projectIds) async { + Future<List<String>> getAllFilePathsForProjectIds( + List<int> projectIds, + ) async { if (projectIds.isEmpty) return []; final db = await AppDatabase.db; final idList = projectIds.join(','); - final res = await db.rawQuery('SELECT file_path FROM files WHERE project_id IN ($idList)'); + final res = await db.rawQuery( + 'SELECT file_path FROM files WHERE project_id IN ($idList)', + ); return res.map((e) => e['file_path'] as String).toList(); } } diff --git a/lib/data/repos/project_repo.dart b/lib/data/repos/project_repo.dart @@ -34,7 +34,7 @@ class ProjectRepo { final res = await db.query( 'projects', where: 'id != 0', - orderBy: 'title ASC' + orderBy: 'title ASC', ); return res.map((e) => ProjectModel.fromMap(e)).toList(); } diff --git a/lib/main.dart b/lib/main.dart @@ -44,13 +44,15 @@ class _MyAppState extends State<MyApp> { _intentStreamSubscription = ReceiveSharingIntent.instance .getMediaStream() .listen((List<SharedMediaFile> value) { - if (value.isNotEmpty) { - _handleShare(value.first.path); - } - }, onError: (err) => debugPrint("Share error: $err")); + if (value.isNotEmpty) { + _handleShare(value.first.path); + } + }, onError: (err) => debugPrint("Share error: $err")); // 3. Cold Start - ReceiveSharingIntent.instance.getInitialMedia().then((List<SharedMediaFile> value) { + ReceiveSharingIntent.instance.getInitialMedia().then(( + List<SharedMediaFile> value, + ) { if (value.isNotEmpty) { _handleShare(value.first.path); ReceiveSharingIntent.instance.reset(); @@ -70,9 +72,7 @@ class _MyAppState extends State<MyApp> { if (context == null) return; _navigatorKey.currentState?.push( - MaterialPageRoute( - builder: (_) => ShareHandlerPage(sharedText: content), - ), + MaterialPageRoute(builder: (_) => ShareHandlerPage(sharedText: content)), ); } @@ -92,7 +92,10 @@ class _MyAppState extends State<MyApp> { if (!_isReady) { return const MaterialApp( debugShowCheckedModeBanner: false, - home: Scaffold(backgroundColor: Colors.white, body: Center(child: CircularProgressIndicator())), + home: Scaffold( + backgroundColor: Colors.white, + body: Center(child: CircularProgressIndicator()), + ), ); } diff --git a/lib/services/analysis_queue_manager.dart b/lib/services/analysis_queue_manager.dart @@ -5,7 +5,8 @@ import '../data/repos/image_repo.dart'; import 'analyze/image_analyzer.dart'; class AnalysisQueueManager { - static final AnalysisQueueManager _instance = AnalysisQueueManager._internal(); + static final AnalysisQueueManager _instance = + AnalysisQueueManager._internal(); factory AnalysisQueueManager() => _instance; AnalysisQueueManager._internal(); @@ -32,25 +33,31 @@ class AnalysisQueueManager { // 2. Run Analysis if (image.tags.isEmpty) { - debugPrint("[Queue]: Analyzing Full Suite: ${image.name}"); - result = await ImageAnalyzerService.analyzeFullSuite(image.filePath); + debugPrint("[Queue]: Analyzing Full Suite: ${image.name}"); + result = await ImageAnalyzerService.analyzeFullSuite( + image.filePath, + ); } else { - debugPrint("[Queue]: Analyzing Selected: ${image.name} with tags: ${image.tags}"); - result = await ImageAnalyzerService.analyzeSelected(image.filePath, image.tags); + debugPrint( + "[Queue]: Analyzing Selected: ${image.name} with tags: ${image.tags}", + ); + result = await ImageAnalyzerService.analyzeSelected( + image.filePath, + image.tags, + ); } // 3. Handle Result if (result['success'] == true && result['data'] != null) { - final resultsMap = result['data']['results'] ?? {}; - final jsonString = jsonEncode(resultsMap); - await _repo.updateAnalysis(image.id, jsonString); - await _repo.updateStatus(image.id, 'completed'); - debugPrint("[Queue]: Completed: ${image.name}"); + final resultsMap = result['data']['results'] ?? {}; + final jsonString = jsonEncode(resultsMap); + await _repo.updateAnalysis(image.id, jsonString); + await _repo.updateStatus(image.id, 'completed'); + debugPrint("[Queue]: Completed: ${image.name}"); } else { - await _repo.updateStatus(image.id, 'failed'); - debugPrint("[Queue]: Failed: ${result['error']}"); + await _repo.updateStatus(image.id, 'failed'); + debugPrint("[Queue]: Failed: ${result['error']}"); } - } catch (e) { debugPrint("[Queue]: Analysis Exception: $e"); await _repo.updateStatus(image.id, 'failed'); diff --git a/lib/services/analyze/embedding.dart b/lib/services/analyze/embedding.dart @@ -10,12 +10,15 @@ class EmbeddingAnalyzerService { List<List<double>>? _centroids; List<String>? _classes; - Future<void> initialize({required String modelPath, required String jsonPath}) async { + Future<void> initialize({ + required String modelPath, + required String jsonPath, + }) async { if (_session != null) return; try { debugPrint("Initializing Embeddings (Dino/CLIP)..."); - + // Initialize FFI Env OrtEnv.instance.init(); @@ -26,22 +29,28 @@ class EmbeddingAnalyzerService { final jsonString = await File(jsonPath).readAsString(); final jsonData = json.decode(jsonString); - + _classes = List<String>.from(jsonData['classes']); - _centroids = (jsonData['centroids'] as List).map((e) => List<double>.from(e)).toList(); + _centroids = + (jsonData['centroids'] as List) + .map((e) => List<double>.from(e)) + .toList(); } catch (e) { debugPrint("Embeddings Init Error: $e"); } } - Future<Map<String, dynamic>?> analyze(String imagePath, { - String? modelPath, - String? jsonPath + Future<Map<String, dynamic>?> analyze( + String imagePath, { + String? modelPath, + String? jsonPath, }) async { // Safety check - if (modelPath == null || jsonPath == null) return {'success': false, 'scores': {}, 'error': 'Paths missing'}; + if (modelPath == null || jsonPath == null) + return {'success': false, 'scores': {}, 'error': 'Paths missing'}; await initialize(modelPath: modelPath, jsonPath: jsonPath); - if (_session == null || _centroids == null) return {'success': false, 'scores': {}, 'error': 'Init failed'}; + if (_session == null || _centroids == null) + return {'success': false, 'scores': {}, 'error': 'Init failed'}; OrtValueTensor? inputOrt; OrtRunOptions? runOptions; @@ -49,30 +58,33 @@ class EmbeddingAnalyzerService { try { final float32Input = await ClipImageProcessor.preprocess(imagePath); - if (float32Input == null) return {'success': false, 'scores': {}, 'error': 'Image decode failed'}; + if (float32Input == null) + return {'success': false, 'scores': {}, 'error': 'Image decode failed'}; // Create Tensor // Note: ensure ClipImageProcessor returns a flat List<double> - inputOrt = OrtValueTensor.createTensorWithDataList( - float32Input, - [1, 3, 224, 224] - ); + inputOrt = OrtValueTensor.createTensorWithDataList(float32Input, [ + 1, + 3, + 224, + 224, + ]); runOptions = OrtRunOptions(); // Run Inference - // 'image' is the input name for CLIP. + // 'image' is the input name for CLIP. outputs = _session!.run(runOptions, {"image": inputOrt}); - + if (outputs.isEmpty) throw Exception("No output from model"); // Get Output // FFI returns list of outputs. Usually index 0. - final dynamic outputRaw = outputs[0]?.value; + final dynamic outputRaw = outputs[0]?.value; // Flatten Output final List<double> imgFeat = []; - + void flatten(dynamic data) { if (data is num) { imgFeat.add(data.toDouble()); @@ -82,12 +94,13 @@ class EmbeddingAnalyzerService { } } } + flatten(outputRaw); // Normalize & Compare final normFeat = l2Normalize(imgFeat); Map<String, double> scores = {}; - + for (int i = 0; i < _classes!.length; i++) { double score = dotProduct(normFeat, _centroids![i]); scores[_classes![i]] = score * 100.0; @@ -95,17 +108,10 @@ class EmbeddingAnalyzerService { scores = Map.fromEntries( (scores.entries.toList() - ..sort((a, b) => b.value - .compareTo(a.value)) - )//.take(3) + ..sort((a, b) => b.value.compareTo(a.value))), //.take(3) ); - return { - "success": true, - "scores": scores, - "error": null, - }; - + return {"success": true, "scores": scores, "error": null}; } catch (e) { debugPrint("Embeddings Analysis Error: $e"); return {'success': false, 'scores': {}, 'error': e.toString()}; diff --git a/lib/services/analyze/font.dart b/lib/services/analyze/font.dart @@ -13,7 +13,10 @@ class FontIdentifierService { static const int IMG_SIZE = 64; - Future<void> initialize({required String modelPath, required String jsonPath}) async { + Future<void> initialize({ + required String modelPath, + required String jsonPath, + }) async { if (_session != null) return; try { @@ -37,27 +40,35 @@ class FontIdentifierService { } } - Future<Map<String, dynamic>> analyze(String imagePath, { - String? modelPath, - String? jsonPath + Future<Map<String, dynamic>> analyze( + String imagePath, { + String? modelPath, + String? jsonPath, }) async { - if (modelPath == null || jsonPath == null) return {'success': false, 'scores': {}, 'error': 'Paths missing'}; + if (modelPath == null || jsonPath == null) + return {'success': false, 'scores': {}, 'error': 'Paths missing'}; await initialize(modelPath: modelPath, jsonPath: jsonPath); - if (_session == null || _fontDb == null) return {'success': false, 'scores': {}, 'error': 'Init failed'}; + if (_session == null || _fontDb == null) + return {'success': false, 'scores': {}, 'error': 'Init failed'}; try { // 1. OCR Detection (Native Platform Call) final inputImage = InputImage.fromFilePath(imagePath); final recognizedText = await _textRecognizer.processImage(inputImage); - + if (recognizedText.blocks.isEmpty) { - return {'success': true, 'scores': {'No Text Detected': 1.0}, 'error': null}; + return { + 'success': true, + 'scores': {'No Text Detected': 1.0}, + 'error': null, + }; } // 2. Load Image for Processing final bytes = await File(imagePath).readAsBytes(); final fullImage = img.decodeImage(bytes); - if (fullImage == null) return {'success': false, 'scores': {}, 'error': 'Image decode failed'}; + if (fullImage == null) + return {'success': false, 'scores': {}, 'error': 'Image decode failed'}; // Store counts to find dominant font Map<String, double> fontCounts = {}; @@ -66,10 +77,9 @@ class FontIdentifierService { // 3. Iterate Text Blocks for (TextBlock block in recognizedText.blocks) { for (TextLine line in block.lines) { - // Crop final rect = line.boundingBox; - + // Safety check for bounds int x = max(0, rect.left.toInt()); int y = max(0, rect.top.toInt()); @@ -88,7 +98,7 @@ class FontIdentifierService { // Aggregate Scores final fontName = match.key; final conf = match.value; - + if (fontCounts.containsKey(fontName)) { fontCounts[fontName] = fontCounts[fontName]! + conf; } else { @@ -102,18 +112,15 @@ class FontIdentifierService { fontCounts.updateAll((key, value) => (value / totalBlocks) * 100); // Sort - var sorted = fontCounts.entries.toList()..sort((a, b) => b.value.compareTo(a.value)); + var sorted = + fontCounts.entries.toList() + ..sort((a, b) => b.value.compareTo(a.value)); Map<String, dynamic> finalScores = {}; - for(var entry in sorted) { + for (var entry in sorted) { finalScores[entry.key] = entry.value; } - return { - 'success': true, - 'scores': finalScores, - 'error': null, - }; - + return {'success': true, 'scores': finalScores, 'error': null}; } catch (e) { debugPrint("Font Analysis Failed: $e"); return {'success': false, 'scores': {}, 'error': e.toString()}; @@ -125,11 +132,11 @@ class FontIdentifierService { // Resize to 64x64 final resized = img.copyResize(crop, width: IMG_SIZE, height: IMG_SIZE); final pixels = Float32List(IMG_SIZE * IMG_SIZE); - + // 1. Calculate Mean Brightness (Adaptive Threshold) double totalLum = 0; for (final pixel in resized) { - totalLum += pixel.r * 0.299 + pixel.g * 0.587 + pixel.b * 0.114; + totalLum += pixel.r * 0.299 + pixel.g * 0.587 + pixel.b * 0.114; } double mean = totalLum / (IMG_SIZE * IMG_SIZE); @@ -154,36 +161,43 @@ class FontIdentifierService { try { // Input 1: Image [1, 64, 64, 1] - imgTensor = OrtValueTensor.createTensorWithDataList( - imageFloats, [1, 64, 64, 1] - ); + imgTensor = OrtValueTensor.createTensorWithDataList(imageFloats, [ + 1, + 64, + 64, + 1, + ]); // Input 2: Dummy Char [1, 26, 1] (Required by FANNet architecture) - final dummyFloats = Float32List(26 * 1); - charTensor = OrtValueTensor.createTensorWithDataList( - dummyFloats, [1, 26, 1] - ); + final dummyFloats = Float32List(26 * 1); + charTensor = OrtValueTensor.createTensorWithDataList(dummyFloats, [ + 1, + 26, + 1, + ]); runOptions = OrtRunOptions(); - + // Run outputs = _session!.run(runOptions, { 'image_input': imgTensor, - 'char_input': charTensor + 'char_input': charTensor, }); // Output Flattening - final rawOutput = outputs[0]?.value; + final rawOutput = outputs[0]?.value; final List<double> flatOutput = []; - + void flatten(dynamic d) { - if (d is num) flatOutput.add(d.toDouble()); - else if (d is List) for(var i in d) flatten(i); + if (d is num) + flatOutput.add(d.toDouble()); + else if (d is List) + for (var i in d) flatten(i); } + flatten(rawOutput); - - return flatOutput; + return flatOutput; } finally { imgTensor?.release(); charTensor?.release(); @@ -203,7 +217,7 @@ class FontIdentifierService { sum += diff * diff; } double dist = sqrt(sum); - + if (dist < minDist) { minDist = dist; bestFont = fontName; diff --git a/lib/services/analyze/texture.dart b/lib/services/analyze/texture.dart @@ -13,18 +13,21 @@ class TextureAnalyzerService { // DINOv2 Small (S14) Config static const int INPUT_SIZE = 224; - static const int EMBED_DIM = 384; - + static const int EMBED_DIM = 384; + // Logic params matching your Python script static const double RELATIVE_THRESH = 0.85; static const double ABSOLUTE_FLOOR = 0.05; // Updated to match Python - Future<void> initialize({required String modelPath, required String jsonPath}) async { + Future<void> initialize({ + required String modelPath, + required String jsonPath, + }) async { if (_session != null) return; try { debugPrint("Initializing Texture Service (DINOv2 S14)..."); - + OrtEnv.instance.init(); // 1. Load Model @@ -37,19 +40,21 @@ class TextureAnalyzerService { final Map<String, dynamic> jsonMap = json.decode(jsonStr); _centroids = {}; jsonMap.forEach((k, v) => _centroids![k] = List<double>.from(v)); - } catch (e) { debugPrint("Texture Service Init Error: $e"); } } - Future<Map<String, dynamic>> analyze(String path, { - String? modelPath, - String? jsonPath + Future<Map<String, dynamic>> analyze( + String path, { + String? modelPath, + String? jsonPath, }) async { - if (modelPath == null || jsonPath == null) return {'success': false, 'scores': {}, 'error': 'Paths missing'}; + if (modelPath == null || jsonPath == null) + return {'success': false, 'scores': {}, 'error': 'Paths missing'}; await initialize(modelPath: modelPath, jsonPath: jsonPath); - if (_session == null || _centroids == null) return {'success': false, 'scores': {}, 'error': 'Init failed'}; + if (_session == null || _centroids == null) + return {'success': false, 'scores': {}, 'error': 'Init failed'}; OrtValueTensor? inputOrt; OrtRunOptions? runOptions; @@ -59,11 +64,19 @@ class TextureAnalyzerService { // 1. Preprocess (Standard ImageNet Normalization) final bytes = await File(path).readAsBytes(); final image = img.decodeImage(bytes); - if (image == null) return {'success': false, 'scores': {}, 'error': 'Decode failed'}; + if (image == null) + return {'success': false, 'scores': {}, 'error': 'Decode failed'}; + + final resized = img.copyResize( + image, + width: INPUT_SIZE, + height: INPUT_SIZE, + ); + final List<double> inputFloats = List.filled( + 1 * 3 * INPUT_SIZE * INPUT_SIZE, + 0.0, + ); - final resized = img.copyResize(image, width: INPUT_SIZE, height: INPUT_SIZE); - final List<double> inputFloats = List.filled(1 * 3 * INPUT_SIZE * INPUT_SIZE, 0.0); - // ImageNet Stats const mean = [0.485, 0.456, 0.406]; const std = [0.229, 0.224, 0.225]; @@ -75,34 +88,44 @@ class TextureAnalyzerService { // R inputFloats[pixelIndex] = ((pixel.r / 255.0) - mean[0]) / std[0]; // G - inputFloats[pixelIndex + (INPUT_SIZE * INPUT_SIZE)] = ((pixel.g / 255.0) - mean[1]) / std[1]; + inputFloats[pixelIndex + (INPUT_SIZE * INPUT_SIZE)] = + ((pixel.g / 255.0) - mean[1]) / std[1]; // B - inputFloats[pixelIndex + (2 * INPUT_SIZE * INPUT_SIZE)] = ((pixel.b / 255.0) - mean[2]) / std[2]; + inputFloats[pixelIndex + (2 * INPUT_SIZE * INPUT_SIZE)] = + ((pixel.b / 255.0) - mean[2]) / std[2]; pixelIndex++; } } - final float32List = Float32List.fromList(inputFloats); + final float32List = Float32List.fromList(inputFloats); // 2. Inference - inputOrt = OrtValueTensor.createTensorWithDataList(float32List, [1, 3, INPUT_SIZE, INPUT_SIZE]); + inputOrt = OrtValueTensor.createTensorWithDataList(float32List, [ + 1, + 3, + INPUT_SIZE, + INPUT_SIZE, + ]); runOptions = OrtRunOptions(); - + // Run Inference // Note: 'input' is standard for tf2onnx/torch.onnx exports - outputs = _session!.run(runOptions, {"input": inputOrt}); - + outputs = _session!.run(runOptions, {"input": inputOrt}); + if (outputs.isEmpty) throw Exception("No output"); // 3. Extract Feature Vector // The Python export script baked "Mean Pooling" into the model. // So we get a single vector [1, 384] directly. No need to loop patches! - final rawOutput = outputs[0]?.value as List; + final rawOutput = outputs[0]?.value as List; final List<double> embedding = []; void flatten(dynamic data) { - if (data is num) embedding.add(data.toDouble()); - else if (data is List) for (var item in data) flatten(item); + if (data is num) + embedding.add(data.toDouble()); + else if (data is List) + for (var item in data) flatten(item); } + flatten(rawOutput); // 4. Normalize Embedding (L2 Norm) @@ -117,7 +140,9 @@ class TextureAnalyzerService { }); // 6. Filter & Sort - var sorted = rawScores.entries.toList()..sort((a, b) => b.value.compareTo(a.value)); + var sorted = + rawScores.entries.toList() + ..sort((a, b) => b.value.compareTo(a.value)); Map<String, dynamic> finalResults = {}; if (sorted.isNotEmpty) { @@ -125,17 +150,12 @@ class TextureAnalyzerService { for (var entry in sorted) { if (entry.value < ABSOLUTE_FLOOR) continue; if (entry.value < (topScore * RELATIVE_THRESH)) break; - + finalResults[entry.key] = entry.value; } } - - return { - 'success': true, - 'scores': finalResults, - 'error': null, - }; + return {'success': true, 'scores': finalResults, 'error': null}; } catch (e) { debugPrint("Texture Analysis Failed: $e"); return {'success': false, 'scores': {}, 'error': e.toString()}; diff --git a/lib/services/download_service.dart b/lib/services/download_service.dart @@ -61,7 +61,7 @@ class DownloadService { // 1️1. GOOGLE PHOTOS / GOOGLEUSERCONTENT.COM RegExp googlePhotosRegex = RegExp( - r'https:\/\/lh3\.googleusercontent\.com\/[a-zA-Z0-9\-\._=]+' + r'https:\/\/lh3\.googleusercontent\.com\/[a-zA-Z0-9\-\._=]+', ); var matchPhotos = googlePhotosRegex.firstMatch(response.body); @@ -142,7 +142,7 @@ class DownloadService { await file.writeAsBytes(response.bodyBytes); debugPrint("Saved Successfully! → $filePath"); - return filePath; + return filePath; } catch (e) { debugPrint("Error: $e"); return null; diff --git a/lib/services/file_service.dart b/lib/services/file_service.dart @@ -7,7 +7,12 @@ import '../data/models/file_model.dart'; class FileService { final _repo = FileRepo(); - Future<String> saveFile(File file, int projectId, {String? description, String? name}) async { + Future<String> saveFile( + File file, + int projectId, { + String? description, + String? name, + }) async { final dir = await getApplicationDocumentsDirectory(); final folder = Directory("${dir.path}/files"); if (!await folder.exists()) await folder.create(recursive: true); @@ -15,7 +20,7 @@ class FileService { final id = const Uuid().v4(); String ext = file.path.split('.').last; final newPath = "${folder.path}/$id.$ext"; - + await file.copy(newPath); final projectFile = FileModel( @@ -36,8 +41,18 @@ class FileService { await _repo.touchFile(id); } - Future<void> updateFileDetails(String id, {String? name, String? description, List<String>? tags}) async { - await _repo.updateDetails(id, name: name, description: description, tags: tags); + Future<void> updateFileDetails( + String id, { + String? name, + String? description, + List<String>? tags, + }) async { + await _repo.updateDetails( + id, + name: name, + description: description, + tags: tags, + ); } Future<void> deleteFile(String id) async { diff --git a/lib/services/image_service.dart b/lib/services/image_service.dart @@ -20,7 +20,9 @@ class ImageService { }) async { final existing = await _repo.getByFilePath(file.path); if (existing != null) { - debugPrint("ImageService: Updating existing draft ${existing.id} -> Project $projectId"); + debugPrint( + "ImageService: Updating existing draft ${existing.id} -> Project $projectId", + ); await _repo.updateProject(existing.id, projectId); await updateTags(existing.id, tags); return existing.id; diff --git a/lib/services/instagram_download_service.dart b/lib/services/instagram_download_service.dart @@ -10,16 +10,20 @@ class InstagramDownloadService { Future<List<String>?> downloadInstagramImage(String url) async { try { final dir = await getApplicationDocumentsDirectory(); - + // Create directories final instagramDir = Directory('${dir.path}/instagram_downloads'); - if (!await instagramDir.exists()) await instagramDir.create(recursive: true); + if (!await instagramDir.exists()) + await instagramDir.create(recursive: true); final imagesDir = Directory('${dir.path}/images'); if (!await imagesDir.exists()) await imagesDir.create(recursive: true); // Call Python Service - final jsonResult = await _pythonService.downloadInstagramImage(url, instagramDir.path); + final jsonResult = await _pythonService.downloadInstagramImage( + url, + instagramDir.path, + ); if (jsonResult != null && jsonResult['success'] == true) { final List<dynamic> paths = jsonResult['file_paths']; diff --git a/lib/services/project_service.dart b/lib/services/project_service.dart @@ -10,7 +10,11 @@ class ProjectService { final _imageRepo = ImageRepo(); final _fileRepo = FileRepo(); - Future<int> createProject(String title, {String? description, int? parentId}) async { + Future<int> createProject( + String title, { + String? description, + int? parentId, + }) async { final project = ProjectModel( title: title.trim(), description: description, @@ -21,16 +25,27 @@ class ProjectService { return await _projectRepo.createProject(project); } - Future<void> updateProjectDetails(int projectId, {String? title, String? description}) async { + Future<void> updateProjectDetails( + int projectId, { + String? title, + String? description, + }) async { if (title != null && title.trim().isEmpty) return; // Prevent empty titles - await _projectRepo.updateProject(projectId, title: title?.trim(), description: description?.trim()); + await _projectRepo.updateProject( + projectId, + title: title?.trim(), + description: description?.trim(), + ); } Future<void> openProject(int id) async { await _projectRepo.touchProject(id); } - Future<void> saveStylesheet(int projectId, Map<String, dynamic> stylesheet) async { + Future<void> saveStylesheet( + int projectId, + Map<String, dynamic> stylesheet, + ) async { final jsonString = jsonEncode(stylesheet); await _projectRepo.updateStylesheet(projectId, jsonString); } @@ -41,8 +56,12 @@ class ProjectService { allIdsToDelete.addAll(subEventIds); List<String> pathsToDelete = []; - pathsToDelete.addAll(await _imageRepo.getAllFilePathsForProjectIds(allIdsToDelete)); - pathsToDelete.addAll(await _fileRepo.getAllFilePathsForProjectIds(allIdsToDelete)); + pathsToDelete.addAll( + await _imageRepo.getAllFilePathsForProjectIds(allIdsToDelete), + ); + pathsToDelete.addAll( + await _fileRepo.getAllFilePathsForProjectIds(allIdsToDelete), + ); for (var path in pathsToDelete) { try { diff --git a/lib/services/python_service.dart b/lib/services/python_service.dart @@ -3,12 +3,16 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; class PythonService { - static const MethodChannel _channel = MethodChannel('com.example.adobe/methods'); + static const MethodChannel _channel = MethodChannel( + 'com.example.adobe/methods', + ); // 1. Layout Analysis (OpenCV) Future<Map<String, dynamic>> analyzeLayout(String imagePath) async { try { - final String? result = await _channel.invokeMethod('analyzeLayout', {'imagePath': imagePath}); + final String? result = await _channel.invokeMethod('analyzeLayout', { + 'imagePath': imagePath, + }); if (result == null) return {'success': false, 'error': 'Null response'}; return json.decode(result); } catch (e) { @@ -20,7 +24,9 @@ class PythonService { // 2. Color Style Analysis (Scikit-Learn) Future<Map<String, dynamic>> analyzeColorStyle(String imagePath) async { try { - final String? result = await _channel.invokeMethod('analyzeColorStyle', {'imagePath': imagePath}); + final String? result = await _channel.invokeMethod('analyzeColorStyle', { + 'imagePath': imagePath, + }); if (result == null) return {'success': false, 'error': 'Null response'}; return json.decode(result); } catch (e) { @@ -30,12 +36,15 @@ class PythonService { } // 3. Instagram Downloader - Future<Map<String, dynamic>?> downloadInstagramImage(String url, String outputDir) async { + Future<Map<String, dynamic>?> downloadInstagramImage( + String url, + String outputDir, + ) async { try { - final String? result = await _channel.invokeMethod('downloadInstagramImage', { - 'url': url, - 'outputDir': outputDir - }); + final String? result = await _channel.invokeMethod( + 'downloadInstagramImage', + {'url': url, 'outputDir': outputDir}, + ); return result != null ? json.decode(result) : null; } catch (e) { debugPrint("Instagram Download Error: $e"); @@ -44,7 +53,9 @@ class PythonService { } // 4. Stylesheet Generation - Future<Map<String, dynamic>?> generateStylesheet(List<String> jsonList) async { + Future<Map<String, dynamic>?> generateStylesheet( + List<String> jsonList, + ) async { try { final String? result = await _channel.invokeMethod('generateStylesheet', { 'jsonList': jsonList, diff --git a/lib/services/theme_service.dart b/lib/services/theme_service.dart @@ -36,4 +36,4 @@ class ThemeService with ChangeNotifier { notifyListeners(); } -} -\ No newline at end of file +} diff --git a/lib/ui/pages/image_analysis_page.dart b/lib/ui/pages/image_analysis_page.dart @@ -36,9 +36,9 @@ class _ImageAnalysisPageState extends State<ImageAnalysisPage> { } catch (e) { if (mounted) { setState(() => _errorMessage = 'Error picking image: $e'); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Error picking image: $e')), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Error picking image: $e'))); } } } @@ -82,32 +82,45 @@ class _ImageAnalysisPageState extends State<ImageAnalysisPage> { shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), - builder: (ctx) => SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 20), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - leading: const Icon(Icons.camera_alt_outlined, color: Colors.black), - title: const Text("Take Photo", style: TextStyle(fontFamily: 'GeneralSans')), - onTap: () { - Navigator.pop(ctx); - _pickImage(ImageSource.camera); - }, - ), - ListTile( - leading: const Icon(Icons.image_outlined, color: Colors.black), - title: const Text("Choose from Gallery", style: TextStyle(fontFamily: 'GeneralSans')), - onTap: () { - Navigator.pop(ctx); - _pickImage(ImageSource.gallery); - }, + builder: + (ctx) => SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: const Icon( + Icons.camera_alt_outlined, + color: Colors.black, + ), + title: const Text( + "Take Photo", + style: TextStyle(fontFamily: 'GeneralSans'), + ), + onTap: () { + Navigator.pop(ctx); + _pickImage(ImageSource.camera); + }, + ), + ListTile( + leading: const Icon( + Icons.image_outlined, + color: Colors.black, + ), + title: const Text( + "Choose from Gallery", + style: TextStyle(fontFamily: 'GeneralSans'), + ), + onTap: () { + Navigator.pop(ctx); + _pickImage(ImageSource.gallery); + }, + ), + ], ), - ], + ), ), - ), - ), ); } @@ -172,8 +185,11 @@ class _ImageAnalysisPageState extends State<ImageAnalysisPage> { Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.bug_report_outlined, - size: 48, color: Colors.grey[400]), + Icon( + Icons.bug_report_outlined, + size: 48, + color: Colors.grey[400], + ), const SizedBox(height: 12), Text( "Select image to test full suite", @@ -188,7 +204,9 @@ class _ImageAnalysisPageState extends State<ImageAnalysisPage> { Container( color: Colors.black26, child: const Center( - child: CircularProgressIndicator(color: Colors.white), + child: CircularProgressIndicator( + color: Colors.white, + ), ), ), ], @@ -210,7 +228,10 @@ class _ImageAnalysisPageState extends State<ImageAnalysisPage> { ), child: Text( _errorMessage!, - style: const TextStyle(color: Colors.red, fontFamily: 'GeneralSans'), + style: const TextStyle( + color: Colors.red, + fontFamily: 'GeneralSans', + ), ), ), diff --git a/lib/ui/pages/image_save_page.dart b/lib/ui/pages/image_save_page.dart @@ -586,7 +586,7 @@ class _ImageSavePageState extends State<ImageSavePage> { if (widget.isFromShare) { SystemNavigator.pop(); } else { - Navigator.popUntil(context, (route) => route.isFirst); + Navigator.pop(context); } } } catch (e) { diff --git a/lib/ui/pages/project_detail_page.dart b/lib/ui/pages/project_detail_page.dart @@ -33,14 +33,14 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> { setState(() => _isLoading = true); try { final project = await _projectRepo.getProjectById(widget.projectId); - + // If project not found, handle exit if (project == null) { if (mounted) { Navigator.pop(context); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Project not found')), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Project not found'))); } return; } @@ -73,87 +73,85 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> { await showDialog( context: context, - builder: (context) => AlertDialog( - title: const Text( - "Create New Event", - style: TextStyle( - fontFamily: 'GeneralSans', - fontWeight: FontWeight.w600, - ), - ), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - TextField( - controller: nameController, - decoration: const InputDecoration( - hintText: "Event Name", - labelText: "Name", - border: OutlineInputBorder(), + builder: + (context) => AlertDialog( + title: const Text( + "Create New Event", + style: TextStyle( + fontFamily: 'GeneralSans', + fontWeight: FontWeight.w600, ), - autofocus: true, - style: const TextStyle(fontFamily: 'GeneralSans'), ), - const SizedBox(height: 16), - TextField( - controller: descriptionController, - decoration: const InputDecoration( - hintText: "Description (optional)", - labelText: "Description", - border: OutlineInputBorder(), - ), - maxLines: 3, - style: const TextStyle(fontFamily: 'GeneralSans'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: nameController, + decoration: const InputDecoration( + hintText: "Event Name", + labelText: "Name", + border: OutlineInputBorder(), + ), + autofocus: true, + style: const TextStyle(fontFamily: 'GeneralSans'), + ), + const SizedBox(height: 16), + TextField( + controller: descriptionController, + decoration: const InputDecoration( + hintText: "Description (optional)", + labelText: "Description", + border: OutlineInputBorder(), + ), + maxLines: 3, + style: const TextStyle(fontFamily: 'GeneralSans'), + ), + ], ), - ], - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text("Cancel"), - ), - ElevatedButton( - onPressed: () async { - if (nameController.text.trim().isNotEmpty) { - try { - await _projectService.createProject( - nameController.text.trim(), - description: descriptionController.text.trim().isEmpty - ? null - : descriptionController.text.trim(), - parentId: _project!.id, - ); - if (context.mounted) { - Navigator.pop(context); - _loadData(); // Refresh to show new event + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text("Cancel"), + ), + ElevatedButton( + onPressed: () async { + if (nameController.text.trim().isNotEmpty) { + try { + await _projectService.createProject( + nameController.text.trim(), + description: + descriptionController.text.trim().isEmpty + ? null + : descriptionController.text.trim(), + parentId: _project!.id, + ); + if (context.mounted) { + Navigator.pop(context); + _loadData(); // Refresh to show new event + } + } catch (e) { + debugPrint("Error creating event: $e"); + } } - } catch (e) { - debugPrint("Error creating event: $e"); - } - } - }, - child: const Text("Create"), + }, + child: const Text("Create"), + ), + ], ), - ], - ), ); } void _navigateToBoard(int projectId) { Navigator.push( context, - MaterialPageRoute( - builder: (_) => ProjectBoardPage(projectId: projectId), - ), + MaterialPageRoute(builder: (_) => ProjectBoardPage(projectId: projectId)), ).then((_) => _loadData()); } void _navigateToStylesheet(int projectId) { Navigator.push( context, - MaterialPageRoute( - builder: (_) => StylesheetPage(projectId: projectId), - ), + MaterialPageRoute(builder: (_) => StylesheetPage(projectId: projectId)), ).then((_) => _loadData()); } @@ -198,7 +196,8 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> { // 1. Main Project Details & Actions _buildSectionHeader("Project Overview", theme), const SizedBox(height: 8), - if (_project!.description != null && _project!.description!.isNotEmpty) + if (_project!.description != null && + _project!.description!.isNotEmpty) Padding( padding: const EdgeInsets.only(bottom: 16), child: Text( @@ -210,7 +209,7 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> { ), ), ), - + // Actions for Main Project _buildActionRow(_project!.id!, theme, isDark), @@ -223,9 +222,12 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> { _buildSectionHeader("Events", theme), IconButton( onPressed: _createEventDialog, - icon: Icon(Icons.add_circle, color: theme.colorScheme.primary), + icon: Icon( + Icons.add_circle, + color: theme.colorScheme.primary, + ), tooltip: "Add Event", - ) + ), ], ), const SizedBox(height: 8), @@ -264,7 +266,7 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> { return _buildEventCard(event, theme, isDark); }, ), - + const SizedBox(height: 40), ], ), @@ -300,7 +302,7 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> { color: Colors.black.withOpacity(0.03), blurRadius: 8, offset: const Offset(0, 2), - ) + ), ], ), padding: const EdgeInsets.all(16), @@ -344,7 +346,12 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> { } /// Reusable row of actions (Moodboard, Stylesheet, Files) - Widget _buildActionRow(int targetId, ThemeData theme, bool isDark, {bool isSmall = false}) { + Widget _buildActionRow( + int targetId, + ThemeData theme, + bool isDark, { + bool isSmall = false, + }) { return Row( children: [ Expanded( diff --git a/lib/ui/pages/share_handler_page.dart b/lib/ui/pages/share_handler_page.dart @@ -49,7 +49,8 @@ class _ShareHandlerPageState extends State<ShareHandlerPage> { if (url.contains('instagram.com')) { // Instagram Logic - final downloadedPaths = await _instagramService.downloadInstagramImage(url); + final downloadedPaths = await _instagramService + .downloadInstagramImage(url); if (downloadedPaths != null && downloadedPaths.isNotEmpty) { tempFiles.addAll(downloadedPaths.map((path) => File(path))); } @@ -111,36 +112,45 @@ class _ShareHandlerPageState extends State<ShareHandlerPage> { backgroundColor: Colors.white, appBar: AppBar(title: const Text("Processing")), body: Center( - child: _hasError - ? Padding( - padding: const EdgeInsets.all(24.0), - child: Column( + child: + _hasError + ? Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.error_outline, + color: Colors.red, + size: 50, + ), + const SizedBox(height: 16), + Text( + "Error processing media", + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 8), + Text( + _errorMessage ?? "Unknown Error", + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.grey), + ), + const SizedBox(height: 24), + ElevatedButton( + onPressed: () => Navigator.pop(context), + child: const Text("Close"), + ), + ], + ), + ) + : const Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.error_outline, color: Colors.red, size: 50), - const SizedBox(height: 16), - Text("Error processing media", - style: Theme.of(context).textTheme.titleMedium), - const SizedBox(height: 8), - Text(_errorMessage ?? "Unknown Error", - textAlign: TextAlign.center, - style: const TextStyle(color: Colors.grey)), - const SizedBox(height: 24), - ElevatedButton( - onPressed: () => Navigator.pop(context), - child: const Text("Close"), - ), + CircularProgressIndicator(), + SizedBox(height: 20), + Text("Downloading media..."), ], ), - ) - : const Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CircularProgressIndicator(), - SizedBox(height: 20), - Text("Downloading media..."), - ], - ), ), ); } diff --git a/lib/ui/pages/stylesheet_page.dart b/lib/ui/pages/stylesheet_page.dart @@ -11,10 +11,7 @@ import 'package:adobe/services/python_service.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(); @@ -34,22 +31,22 @@ class _StylesheetPageState extends State<StylesheetPage> { Future<void> _loadSavedStylesheet() async { final project = await ProjectRepo().getProjectById(_currentProjectId); - if (project?.globalStylesheet != null && project!.globalStylesheet!.isNotEmpty) { + if (project?.globalStylesheet != null && + project!.globalStylesheet!.isNotEmpty) { try { final parsed = jsonDecode(project.globalStylesheet!); const encoder = JsonEncoder.withIndent(' '); setState(() { _resultJson = encoder.convert(parsed); }); - } catch (_) { - } + } catch (_) {} } } Future<void> _generateStylesheet() async { setState(() { _isLoading = true; - _resultJson = null; + _resultJson = null; }); try { @@ -57,19 +54,24 @@ class _StylesheetPageState extends State<StylesheetPage> { final images = await ImageRepo().getImages(_currentProjectId); // 2. Extract valid analysis data - final List<String> analysisDataList = images - .map((img) => img.analysisData) - .where((data) => data != null && data.isNotEmpty) - .cast<String>() - .toList(); + final List<String> analysisDataList = + images + .map((img) => img.analysisData) + .where((data) => data != null && data.isNotEmpty) + .cast<String>() + .toList(); if (analysisDataList.isEmpty) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("No analyzed images found for this project.")), + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("No analyzed images found for this project."), + ), ); } - setState(() { _isLoading = false; }); + setState(() { + _isLoading = false; + }); return; } @@ -94,9 +96,9 @@ class _StylesheetPageState extends State<StylesheetPage> { } catch (e) { debugPrint("Error generating stylesheet: $e"); if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Error: $e")), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text("Error: $e"))); } } finally { if (mounted) { @@ -111,7 +113,7 @@ class _StylesheetPageState extends State<StylesheetPage> { Widget build(BuildContext context) { return Scaffold( backgroundColor: Variables.background, - + appBar: TopBar( currentProjectId: _currentProjectId, onBack: () => Navigator.of(context).pop(), @@ -129,18 +131,19 @@ class _StylesheetPageState extends State<StylesheetPage> { return SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), + constraints: BoxConstraints(minHeight: constraints.maxHeight), child: Center( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 20), + padding: const EdgeInsets.symmetric( + horizontal: 24.0, + vertical: 20, + ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // No saved result if (_resultJson == null && !_isLoading) ...[ - Text( + Text( "Are you ready to start\nbuilding the visual identity?", textAlign: TextAlign.center, style: Variables.headerStyle, @@ -150,7 +153,9 @@ class _StylesheetPageState extends State<StylesheetPage> { // Loading if (_isLoading) - const CircularProgressIndicator(color: Variables.textPrimary), + const CircularProgressIndicator( + color: Variables.textPrimary, + ), // Result JSON if (_resultJson != null) @@ -165,7 +170,10 @@ class _StylesheetPageState extends State<StylesheetPage> { ), child: Text( _resultJson!, - style: const TextStyle(fontFamily: 'monospace', fontSize: 12), + style: const TextStyle( + fontFamily: 'monospace', + fontSize: 12, + ), ), ), @@ -187,7 +195,9 @@ class _StylesheetPageState extends State<StylesheetPage> { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - _resultJson == null ? "Generate Stylesheet" : "Regenerate Stylesheet", + _resultJson == null + ? "Generate Stylesheet" + : "Regenerate Stylesheet", style: Variables.buttonTextStyle, ), const SizedBox(width: 8), diff --git a/lib/ui/styles/variables.dart b/lib/ui/styles/variables.dart @@ -6,22 +6,22 @@ class Variables { static const Color textPrimary = Color(0xFF27272A); static const Color textSecondary = Color(0xFF71717A); static const Color textDisabled = Color(0xFFA1A1AA); - + static const Color surfaceSubtle = Color(0xFFF4F4F5); static const Color background = Colors.white; - + static const Color borderSubtle = Color(0xFFE4E4E7); // Dimensions static const double fontSizeHeader = 20.0; static const double lineHeightHeader = 24.0; - + static const double fontSizeBody = 14.0; static const double lineHeightBody = 20.0; static const double trackingBody = 0.25; static const double fontSizeSmall = 12.0; - + static const double fontSizeCaption = 10.0; // Text Styles @@ -46,7 +46,7 @@ class Variables { fontSize: fontSizeCaption, color: textSecondary, ); - + static TextStyle get buttonTextStyle => const TextStyle( fontFamily: 'GeneralSans', fontSize: fontSizeBody, diff --git a/lib/ui/widgets/bottom_bar.dart b/lib/ui/widgets/bottom_bar.dart @@ -4,11 +4,7 @@ import 'package:adobe/ui/styles/variables.dart'; import 'package:adobe/ui/pages/project_board_page.dart'; import 'package:adobe/ui/pages/stylesheet_page.dart'; -enum BottomBarItem { - moodboard, - stylesheet, - files -} +enum BottomBarItem { moodboard, stylesheet, files } class BottomBar extends StatelessWidget { final BottomBarItem currentTab; @@ -92,10 +88,10 @@ class BottomBar extends StatelessWidget { } Widget _buildNavItem( - BuildContext context, - BottomBarItem item, - String label, - String assetPath, + BuildContext context, + BottomBarItem item, + String label, + String assetPath, ) { final bool isSelected = item == currentTab; final Color color = diff --git a/lib/ui/widgets/top_bar.dart b/lib/ui/widgets/top_bar.dart @@ -50,8 +50,9 @@ class _TopBarState extends State<TopBar> { Future<void> _loadData() async { try { - final current = - await _projectRepo.getProjectById(widget.currentProjectId); + final current = await _projectRepo.getProjectById( + widget.currentProjectId, + ); if (current == null) { setState(() => _isLoading = false); return; @@ -101,7 +102,10 @@ class _TopBarState extends State<TopBar> { // 1. Back Button if (widget.onBack != null) ...[ IconButton( - icon: const Icon(Icons.arrow_back, color: Variables.textPrimary), + icon: const Icon( + Icons.arrow_back, + color: Variables.textPrimary, + ), onPressed: widget.onBack, padding: EdgeInsets.zero, constraints: const BoxConstraints(), @@ -111,40 +115,45 @@ class _TopBarState extends State<TopBar> { // 2. Title & Selector Expanded( - child: widget.titleOverride != null - ? Text( - widget.titleOverride!, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w600, - color: Variables.textPrimary, - ), - overflow: TextOverflow.ellipsis, - ) - : (!_isLoading && _currentProject != null && _rootProject != null) + child: + widget.titleOverride != null + ? Text( + widget.titleOverride!, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + color: Variables.textPrimary, + ), + overflow: TextOverflow.ellipsis, + ) + : (!_isLoading && + _currentProject != null && + _rootProject != null) ? Row( - children: [ - Flexible( - child: Text( - _rootProject!.title, - style: const TextStyle( - fontSize: 22, - fontWeight: FontWeight.w600, - color: Variables.textPrimary, - ), - overflow: TextOverflow.ellipsis, + children: [ + Flexible( + child: Text( + _rootProject!.title, + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.w600, + color: Variables.textPrimary, ), + overflow: TextOverflow.ellipsis, ), - const SizedBox(width: 8), - ], - ) + ), + const SizedBox(width: 8), + ], + ) : const SizedBox(), ), - if (!_isLoading && _currentProject != null && _rootProject != null) + if (!_isLoading && + _currentProject != null && + _rootProject != null) PopupMenuButton<ProjectModel>( padding: EdgeInsets.zero, - onSelected: (project) => - widget.onProjectChanged?.call(project), + onSelected: + (project) => widget.onProjectChanged?.call(project), color: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), @@ -183,20 +192,21 @@ class _TopBarState extends State<TopBar> { ), itemBuilder: (context) { return _contextList.map((project) { - final isSelected = - project.id == _currentProject!.id; + final isSelected = project.id == _currentProject!.id; final isRoot = project.id == _rootProject!.id; return PopupMenuItem<ProjectModel>( value: project, child: Text( isRoot ? "Main Project" : project.title, style: Variables.bodyStyle.copyWith( - fontWeight: isSelected - ? FontWeight.bold - : FontWeight.normal, - color: isSelected - ? Variables.textPrimary - : Variables.textSecondary, + fontWeight: + isSelected + ? FontWeight.bold + : FontWeight.normal, + color: + isSelected + ? Variables.textPrimary + : Variables.textSecondary, ), ), ); @@ -207,8 +217,10 @@ class _TopBarState extends State<TopBar> { // 3. Settings Icon IconButton( - icon: const Icon(Icons.settings_outlined, - color: Variables.textPrimary), + icon: const Icon( + Icons.settings_outlined, + color: Variables.textPrimary, + ), onPressed: widget.onSettingsPressed, padding: EdgeInsets.zero, constraints: const BoxConstraints(), diff --git a/lib/utils/clip_image_processor.dart b/lib/utils/clip_image_processor.dart @@ -13,14 +13,14 @@ class ClipImageProcessor { static Future<Float32List?> preprocess(String filePath) async { final bytes = await File(filePath).readAsBytes(); final image = img.decodeImage(bytes); - + if (image == null) return null; // 1. Resize (Shortest side to 224) int w = image.width; int h = image.height; int target = 224; - + img.Image resized; if (w < h) { resized = img.copyResize(image, width: target); @@ -34,7 +34,7 @@ class ClipImageProcessor { // 3. Convert to Float32 List (NCHW format: Batch, Channels, Height, Width) // Size: 1 * 3 * 224 * 224 = 150,528 float values final Float32List inputData = Float32List(1 * 3 * 224 * 224); - + int pixelIndex = 0; // Iterate pixels and separate channels // Planar format (RRRR... GGGG... BBBB...) @@ -45,7 +45,7 @@ class ClipImageProcessor { for (var y = 0; y < 224; y++) { for (var x = 0; x < 224; x++) { final pixel = cropped.getPixel(x, y); - + // Normalize: (Value/255 - Mean) / Std double r = (pixel.r / 255.0 - mean[0]) / std[0]; double g = (pixel.g / 255.0 - mean[1]) / std[1]; @@ -54,7 +54,7 @@ class ClipImageProcessor { inputData[rOffset + pixelIndex] = r; inputData[gOffset + pixelIndex] = g; inputData[bOffset + pixelIndex] = b; - + pixelIndex++; } } diff --git a/lib/utils/image_utils.dart b/lib/utils/image_utils.dart @@ -27,4 +27,4 @@ double cosineSim(List<double> a, List<double> b) { nB += b[i] * b[i]; } return (nA == 0 || nB == 0) ? 0.0 : dot / (sqrt(nA) * sqrt(nB)); -} -\ No newline at end of file +}