commit 860f3ed75e8e707bf57e491c76d48300870aa9ae
parent c3ba6bac163a343e61a0d89d86b8af29d299c367
Author: Nilotpal Gupta <nilotpalgupta0701@gmail.com>
Date: Sun, 30 Nov 2025 02:03:23 +0530
clenup + formated
Diffstat:
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
+}