creek

The AI Image Editor of 2030
commit de68e7ca01424ff4e016ea3556f33a33f15287bc
parent 265e7baf124624556dd72378a51f1a637ec296a5
Author: maydayv7 <maydayv7@gmail.com>
Date:   Wed,  3 Dec 2025 07:38:29 +0530

Integrate stylesheet page and service

Diffstat:
Mlib/services/stylesheet_service.dart | 81++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mlib/ui/pages/stylesheet_page.dart | 206++++++++++++++++++-------------------------------------------------------------
2 files changed, 117 insertions(+), 170 deletions(-)

diff --git a/lib/services/stylesheet_service.dart b/lib/services/stylesheet_service.dart @@ -2,17 +2,36 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; -/// A simple model to hold the extracted design tokens +// A simple model to hold the extracted design tokens class StylesheetData { final List<Color> colors; final List<String> fonts; + final List<String> graphics; + final List<String> compositions; + final List<String> materialLook; + final List<String> textures; + final List<String> lighting; + final List<String> style; + final List<String> era; + final List<String> emotions; - StylesheetData({required this.colors, required this.fonts}); + StylesheetData({ + required this.colors, + required this.fonts, + this.graphics = const [], + this.compositions = const [], + this.materialLook = const [], + this.textures = const [], + this.lighting = const [], + this.style = const [], + this.era = const [], + this.emotions = const [], + }); } class StylesheetService { - /// Main entry point: Parses a raw (potentially dirty) JSON string - /// and returns a structured [StylesheetData] object. + // Main entry point: Parses a raw (potentially dirty) JSON string + // and returns a structured [StylesheetData] object StylesheetData parse(String? rawJson) { if (rawJson == null || rawJson.isEmpty) { return StylesheetData(colors: [], fonts: []); @@ -27,14 +46,26 @@ class StylesheetService { // 3. Extract and Process Colors List<Color> colors = _extractColors(data); - return StylesheetData(colors: colors, fonts: fonts); + // 4. Extract other attributes + return StylesheetData( + colors: colors, + fonts: fonts, + graphics: _extractStrings(data, ['Graphics', 'graphics'], valueKey: 'path'), + compositions: _extractStrings(data, ['Compositions', 'Composition', 'compositions']), + materialLook: _extractStrings(data, ['Material look', 'Material Look', 'material_look']), + textures: _extractStrings(data, ['Textures', 'Background/Texture', 'textures']), + lighting: _extractStrings(data, ['Lighting', 'lighting']), + style: _extractStrings(data, ['Style', 'style']), + era: _extractStrings(data, ['Era/Cultural Reference', 'Era', 'era']), + emotions: _extractStrings(data, ['Emotions', 'Emotional', 'emotions']), + ); } // --------------------------------------------------------------------------- // PARSING LOGIC // --------------------------------------------------------------------------- - /// Safely parses the raw string into a Map, handling dirty AI output. + // Safely parses the raw string into a Map, handling dirty AI output Map<String, dynamic> _parseRawJson(String rawString) { try { // Try standard decode first @@ -50,9 +81,13 @@ class StylesheetService { } } - /// Cleans "dirty" JSON strings by fixing quotes and unquoted keys. + /// Cleans "dirty" JSON strings by fixing quotes and unquoted keys String _cleanJsonString(String raw) { String cleaned = raw; + + // Remove Markdown code blocks if present (common AI artifact) + cleaned = cleaned.replaceAll(RegExp(r'^```json\s*|\s*```$'), ''); + // Add quotes to keys cleaned = cleaned.replaceAllMapped( RegExp(r'([{,]\s*)([a-zA-Z0-9_\s/]+)(\s*:)'), @@ -75,7 +110,7 @@ class StylesheetService { return cleaned; } - /// Normalizes the structure if the API returns { "results": ... } + // Normalizes the structure if the API returns { "results": ... } Map<String, dynamic> _normalizeResult(dynamic parsed) { if (parsed is String) { try { @@ -96,9 +131,9 @@ class StylesheetService { // EXTRACTION LOGIC // --------------------------------------------------------------------------- - /// Extracts font names and resolves them to valid Google Font strings. + // Extracts font names and resolves them to valid Google Font strings. List<String> _extractFonts(Map<String, dynamic> data) { - dynamic fontData = _findValue(data, ['Typography', 'fonts', 'typography']); + dynamic fontData = _findValue(data, ['Typography', 'fonts', 'typography', 'Fonts']); if (fontData == null) return []; List<String> rawNames = []; @@ -119,12 +154,13 @@ class StylesheetService { return rawNames.map((name) => _resolveGoogleFontName(name)).toList(); } - /// Extracts colors from Hex codes or semantic labels. + // Extracts colors from Hex codes or semantic labels List<Color> _extractColors(Map<String, dynamic> data) { dynamic colorData = _findValue(data, [ 'Colour Palette', 'Color Palette', 'colors', + 'Colors' ]); if (colorData == null) return []; @@ -147,6 +183,29 @@ class StylesheetService { return resolvedColors; } + // Generic helper to extract a list of strings from various keys + // Supports extraction from [{ "label": "val" }] or ["val"] or "val" + List<String> _extractStrings(Map<String, dynamic> data, List<String> keys, {String valueKey = 'label'}) { + dynamic rawData = _findValue(data, keys); + if (rawData == null) return []; + + List<String> results = []; + if (rawData is List) { + for (var item in rawData) { + if (item is Map && item.containsKey(valueKey)) { + results.add(item[valueKey].toString()); + } else if (item is String) { + results.add(item); + } + } + } else if (rawData is Map && rawData.containsKey(valueKey)) { + results.add(rawData[valueKey].toString()); + } else if (rawData is String) { + results.add(rawData); + } + return results; + } + // --------------------------------------------------------------------------- // HELPERS // --------------------------------------------------------------------------- diff --git a/lib/ui/pages/stylesheet_page.dart b/lib/ui/pages/stylesheet_page.dart @@ -4,15 +4,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; import 'package:adobe/ui/styles/variables.dart'; import 'package:adobe/ui/widgets/bottom_bar.dart'; import 'package:adobe/ui/widgets/top_bar.dart'; -import 'package:adobe/data/repos/image_repo.dart'; import 'package:adobe/data/repos/project_repo.dart'; -import 'package:adobe/services/python_service.dart'; +import 'package:adobe/data/repos/image_repo.dart'; import 'package:adobe/data/repos/note_repo.dart'; -import 'package:path/path.dart' as p; -import 'package:path_provider/path_provider.dart'; +import 'package:adobe/services/stylesheet_service.dart'; +import 'package:adobe/services/python_service.dart'; class StylesheetPage extends StatefulWidget { final int projectId; @@ -28,13 +29,13 @@ class _StylesheetPageState extends State<StylesheetPage> { late int _currentProjectId; bool _isLoading = false; - Map<String, dynamic>? _stylesheetMap; + StylesheetData? _stylesheetData; String? _rawJsonString; List<String> _projectAssets = []; List<String> _logoPaths = []; - final Map<String, String> _fontNameCache = {}; final ImagePicker _picker = ImagePicker(); + final StylesheetService _stylesheetService = StylesheetService(); // --- ASSET MAPPING --- final Map<String, String> _lightingAssets = { @@ -89,17 +90,6 @@ class _StylesheetPageState extends State<StylesheetPage> { } // --- UTILS --- - String _cleanJsonString(String raw) { - // 1. Remove Markdown code blocks if present (common AI artifact) - String cleaned = raw.replaceAll(RegExp(r'^```json\s*|\s*```$'), ''); - - // 2. Fix unquoted keys if necessary (only if standard parse fails) - cleaned = cleaned.replaceAllMapped( - RegExp(r'([{,]\s*)([a-zA-Z0-9_\s/]+)(\s*:)'), - (match) => '${match[1]}"${match[2]?.trim()}"${match[3]}', - ); - return cleaned; - } String _formatLabel(String label) { String clean = label.replaceAll(RegExp(r'[-_]'), ' '); @@ -115,22 +105,6 @@ class _StylesheetPageState extends State<StylesheetPage> { return assetMap[normalized]; } - String _resolveGoogleFontName(String dirtyName) { - if (_fontNameCache.containsKey(dirtyName)) { - return _fontNameCache[dirtyName]!; - } - String cleanInput = dirtyName.toLowerCase().replaceAll(RegExp(r'[-_]regular$'), '').replaceAll(RegExp(r'[^a-z0-9]'), ''); - final allFonts = GoogleFonts.asMap().keys; - for (String officialName in allFonts) { - String cleanOfficial = officialName.toLowerCase().replaceAll(RegExp(r'[^a-z0-9]'), ''); - if (cleanOfficial == cleanInput) { - _fontNameCache[dirtyName] = officialName; - return officialName; - } - } - return dirtyName; - } - Future<File?> _resolveFile(String path) async { final file = File(path); if (await file.exists()) return file; @@ -151,43 +125,19 @@ class _StylesheetPageState extends State<StylesheetPage> { if (project == null) return; List<String> currentAssets = project.assetsPath; - Map<String, dynamic>? parsedMap; - String? rawJson; - - if (project.globalStylesheet != null && project.globalStylesheet!.isNotEmpty) { - rawJson = project.globalStylesheet!; - dynamic parsed; - - // Try standard decode first - try { - parsed = jsonDecode(rawJson); - } catch (e) { - // Try cleaning markdown and loose keys - try { - parsed = jsonDecode(_cleanJsonString(rawJson)); - } catch (_) {} - } - - if (parsed is String) { - try { - parsed = jsonDecode(parsed); - } catch (_) {} - } + String? rawJson = project.globalStylesheet; + StylesheetData? data; - if (parsed is Map<String, dynamic>) { - if (parsed.containsKey('results') && parsed['results'] is Map) { - parsedMap = parsed['results']; - } else { - parsedMap = parsed; - } - } + if (rawJson != null && rawJson.isNotEmpty) { + // Use the service to parse + data = _stylesheetService.parse(rawJson); } if (mounted) { setState(() { _projectAssets = currentAssets; _rawJsonString = rawJson; - _stylesheetMap = parsedMap; + _stylesheetData = data; }); } } @@ -195,7 +145,7 @@ class _StylesheetPageState extends State<StylesheetPage> { Future<void> _generateStylesheet() async { setState(() { _isLoading = true; - _stylesheetMap = null; + _stylesheetData = null; _rawJsonString = null; }); @@ -228,17 +178,6 @@ class _StylesheetPageState extends State<StylesheetPage> { } } - dynamic _getData(List<String> keys) { - if (_stylesheetMap == null) return null; - for (var k in keys) { - if (_stylesheetMap!.containsKey(k)) return _stylesheetMap![k]; - for (var mapKey in _stylesheetMap!.keys) { - if (mapKey.toLowerCase() == k.toLowerCase()) return _stylesheetMap![mapKey]; - } - } - return null; - } - @override Widget build(BuildContext context) { return Scaffold( @@ -248,7 +187,7 @@ class _StylesheetPageState extends State<StylesheetPage> { onBack: () => Navigator.of(context).pop(), onProjectChanged: (p) => setState(() { _currentProjectId = p.id!; - _stylesheetMap = null; + _stylesheetData = null; _projectAssets = []; _logoPaths = []; _loadSavedStylesheet(); @@ -258,7 +197,7 @@ class _StylesheetPageState extends State<StylesheetPage> { // Only show content once full stylesheet is parsed atleast once body: _isLoading ? const Center(child: CircularProgressIndicator(color: Variables.textPrimary)) - : (_stylesheetMap == null && _rawJsonString == null) + : (_stylesheetData == null && _rawJsonString == null) ? _buildEmptyState() : _buildContent(), bottomNavigationBar: BottomBar(currentTab: BottomBarItem.stylesheet, projectId: _currentProjectId), @@ -283,16 +222,8 @@ class _StylesheetPageState extends State<StylesheetPage> { } Widget _buildContent() { - final graphics = _getData(['Graphics', 'graphics']); - final colors = _getData(['Colour Palette', 'Color Palette', 'colors', 'Colors']); - final typography = _getData(['Typography', 'fonts', 'Fonts']); - final compositions = _getData(['Compositions', 'Composition', 'compositions']); - final materialLook = _getData(['Material look', 'Material Look', 'material_look']); - final textures = _getData(['Textures', 'Background/Texture', 'textures']); - final lighting = _getData(['Lighting', 'lighting']); - final style = _getData(['Style', 'style']); - final era = _getData(['Era/Cultural Reference', 'Era', 'era']); - final emotions = _getData(['Emotions', 'Emotional', 'emotions']); + final data = _stylesheetData; + if (data == null) return const SizedBox(); return RefreshIndicator( onRefresh: _generateStylesheet, @@ -305,16 +236,16 @@ class _StylesheetPageState extends State<StylesheetPage> { const SizedBox(height: 24), _buildLogosSection(null), - if (graphics != null || _projectAssets.isNotEmpty) _buildGraphicsSection(graphics), - if (colors != null) _buildColorsSection(colors), - if (typography != null) _buildFontsSection(typography), - if (compositions != null) _buildCompositionsSection(compositions), - if (materialLook != null) _buildMaterialLookSection(materialLook), - if (textures != null) _buildTexturesSection(textures), - if (lighting != null) _buildLightingSection(lighting), - if (style != null) _buildStyleSection(style), - if (era != null) _buildEraSection(era), - if (emotions != null) _buildEmotionsSection(emotions), + if (data.graphics.isNotEmpty || _projectAssets.isNotEmpty) _buildGraphicsSection(data.graphics), + if (data.colors.isNotEmpty) _buildColorsSection(data.colors), + if (data.fonts.isNotEmpty) _buildFontsSection(data.fonts), + if (data.compositions.isNotEmpty) _buildCompositionsSection(data.compositions), + if (data.materialLook.isNotEmpty) _buildMaterialLookSection(data.materialLook), + if (data.textures.isNotEmpty) _buildTexturesSection(data.textures), + if (data.lighting.isNotEmpty) _buildLightingSection(data.lighting), + if (data.style.isNotEmpty) _buildStyleSection(data.style), + if (data.era.isNotEmpty) _buildEraSection(data.era), + if (data.emotions.isNotEmpty) _buildEmotionsSection(data.emotions), const SizedBox(height: 24), ], @@ -451,14 +382,9 @@ class _StylesheetPageState extends State<StylesheetPage> { } // --- Graphics --- - Widget _buildGraphicsSection(dynamic data) { + Widget _buildGraphicsSection(List<String> extractedGraphics) { List<String> graphicPaths = List.from(_projectAssets); - if (data is List) { - for (var item in data) { - if (item is Map && item.containsKey('path')) graphicPaths.add(item['path'].toString()); - else if (item is String) graphicPaths.add(item); - } - } else if (data is String) graphicPaths.add(data); + graphicPaths.addAll(extractedGraphics); if (graphicPaths.isEmpty) return const SizedBox(); @@ -507,17 +433,7 @@ class _StylesheetPageState extends State<StylesheetPage> { } // --- Fonts --- - Widget _buildFontsSection(dynamic data) { - List<String> fontNames = []; - if (data is List) { - for (var item in data) { - if (item is Map && item.containsKey('label')) fontNames.add(item['label'].toString().trim()); - else if (item is String) fontNames.add(item.trim()); - } - } else if (data is Map && data.containsKey('label')) { - fontNames.add(data['label'].toString().trim()); - } - + Widget _buildFontsSection(List<String> fontNames) { if (fontNames.isEmpty) return const SizedBox(); return Column( @@ -538,13 +454,12 @@ class _StylesheetPageState extends State<StylesheetPage> { ); } - Widget _buildFontCard(String rawFontName) { - final String correctFontName = _resolveGoogleFontName(rawFontName); - final String displayFontName = _formatLabel(rawFontName); - + Widget _buildFontCard(String resolvedFontName) { + final String displayFontName = _formatLabel(resolvedFontName); + TextStyle sampleStyle; try { - sampleStyle = GoogleFonts.getFont(correctFontName); + sampleStyle = GoogleFonts.getFont(resolvedFontName); } catch (_) { sampleStyle = const TextStyle(fontFamily: 'GeneralSans'); } @@ -585,16 +500,8 @@ class _StylesheetPageState extends State<StylesheetPage> { } // --- Colors --- - Widget _buildColorsSection(dynamic data) { - List<String> colorList = []; - if (data is List) { - for (var item in data) { - if (item is Map && item.containsKey('label')) colorList.add(item['label'].toString()); - else if (item is String) colorList.add(item); - } - } - - if (colorList.isEmpty) return const SizedBox(); + Widget _buildColorsSection(List<Color> colors) { + if (colors.isEmpty) return const SizedBox(); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -602,31 +509,20 @@ class _StylesheetPageState extends State<StylesheetPage> { _buildSectionHeader("Colors", showArrow: false), Wrap( spacing: 8, runSpacing: 8, - children: colorList.take(5).map((color) => _buildColorSwatch(color)).toList(), + children: colors.take(5).map((color) => _buildColorSwatch(color)).toList(), ), const SizedBox(height: 16), ], ); } - Widget _buildColorSwatch(String label) { - Color color = _getColorFromLabel(label); + Widget _buildColorSwatch(Color color) { return Container( width: 104, height: 52, decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(8)), ); } - Color _getColorFromLabel(String label) { - if (label.startsWith('#') || label.length == 6) { - try { - String hex = label.replaceAll('#', ''); - if (hex.length == 6) return Color(int.parse('0xFF$hex')); - } catch (_) {} - } - return Colors.grey.shade400; - } - // --- Unified Card Builder --- Widget _buildUnifiedCard(String label, String? assetPath) { final formattedLabel = _formatLabel(label); @@ -705,15 +601,7 @@ class _StylesheetPageState extends State<StylesheetPage> { } } - Widget _buildAttributeSection(String title, dynamic data, Map<String, String>? assetMap) { - List<String> items = []; - if (data is List) { - for (var item in data) { - if (item is Map && item.containsKey('label')) items.add(item['label'].toString()); - else if (item is String) items.add(item); - } - } else if (data is String) items.add(data); - + Widget _buildAttributeSection(String title, List<String> items, Map<String, String>? assetMap) { if (items.isEmpty) return const SizedBox(); return Column( @@ -738,31 +626,31 @@ class _StylesheetPageState extends State<StylesheetPage> { ); } - Widget _buildCompositionsSection(dynamic data) { + Widget _buildCompositionsSection(List<String> data) { return _buildAttributeSection("Compositions", data, null); } - Widget _buildMaterialLookSection(dynamic data) { + Widget _buildMaterialLookSection(List<String> data) { return _buildAttributeSection("Material Look", data, _materialAssets); } - Widget _buildTexturesSection(dynamic data) { + Widget _buildTexturesSection(List<String> data) { return _buildAttributeSection("Textures", data, _textureAssets); } - Widget _buildLightingSection(dynamic data) { + Widget _buildLightingSection(List<String> data) { return _buildAttributeSection("Lighting", data, _lightingAssets); } - Widget _buildStyleSection(dynamic data) { + Widget _buildStyleSection(List<String> data) { return _buildAttributeSection("Style", data, null); } - Widget _buildEraSection(dynamic data) { + Widget _buildEraSection(List<String> data) { return _buildAttributeSection("Era", data, null); } - Widget _buildEmotionsSection(dynamic data) { + Widget _buildEmotionsSection(List<String> data) { return _buildAttributeSection("Emotions", data, null); } }