commit 3b4a61633cd4d6a6a9a0af74dd1906a5076b7bc5 parent 3adbfcc2abad4c9797adcb3881d3e24b56ac80d9 Author: maydayv7 <maydayv7@gmail.com> Date: Wed, 3 Dec 2025 03:25:41 +0530 Fix stylesheet page Diffstat:
30 files changed, 312 insertions(+), 525 deletions(-)
diff --git a/android/app/src/main/python/stylesheet_generator.py b/android/app/src/main/python/stylesheet_generator.py @@ -258,7 +258,7 @@ class FontClusterEngine: clean = raw_name.lower().split('-')[0].strip() if clean in self.full_font_map: return self.full_font_map[clean] - return "Unknown/Other" + return "Display/Other" # Default fallback # ========================================== # UNIFIED CONSENSUS ENGINE @@ -273,6 +273,12 @@ class UnifiedStyleEngine: self.color_pool = [] self.doc_counter = 0 + self.material_filter = { + "glossy", "matte", "metallic", "organic", + "wood", "laminated", "porcelain", "wet-look", + "denim", "fabric", "leather" + } + self.aliases = { "Colour Palette": "Color Palette", "Texture": "Background/Texture", @@ -314,10 +320,8 @@ class UnifiedStyleEngine: # PATH A: COLOR PALETTE # NOTE: We intentionally SKIP processing 'scores' (moods) here if std_category == "Color Palette": - if isinstance(payload, dict): - # Extract "palette" list (Actual Hex Codes) - if "palette" in payload and isinstance(payload["palette"], list): - for hex_code in payload["palette"]: + if isinstance(payload, dict) and "palette" in payload: + for hex_code in payload["palette"]: if isinstance(hex_code, str) and hex_code.startswith('#'): self.color_pool.append(hex_to_rgb(hex_code)) # Fallback: if payload is just a list of hex strings @@ -406,7 +410,45 @@ class UnifiedStyleEngine: candidates.append({"label": tag, "score": round(score, 2)}) candidates.sort(key=lambda x: x['score'], reverse=True) - if candidates: final_json["results"][category] = candidates[:5] + if candidates: + final_json["results"][category] = candidates[:5] + + # 1.5 Split Texture into 'Background/Texture' and 'Material Look' + if "Background/Texture" in final_json["results"]: + textures = [] + materials = [] + + for item in final_json["results"]["Background/Texture"]: + label = item["label"].lower().replace("_", " ").replace("-", " ") + # Check if label or parts of it match material keywords + is_material = False + if item["label"] in self.material_filter: + is_material = True + else: + # Partial matching (e.g. "wood grain" -> wood) + for mat in self.material_filter: + if mat in label: + is_material = True + break + + if is_material: + materials.append(item) + else: + textures.append(item) + + if textures: + final_json["results"]["Background/Texture"] = textures + else: + # Remove empty key if all moved to material + del final_json["results"]["Background/Texture"] + + if materials: + # Merge with existing Material Look if present + if "Material Look" in final_json["results"]: + final_json["results"]["Material Look"].extend(materials) + final_json["results"]["Material Look"].sort(key=lambda x: x['score'], reverse=True) + else: + final_json["results"]["Material Look"] = materials # 2. Process Typography if self.font_family_scores: @@ -475,9 +517,7 @@ def generate_stylesheet(json_strings_list: Any) -> str: try: data = json.loads(json_str) engine.ingest_data(data) - except Exception as e: - print(f"Error parsing JSON chunk: {e}") - continue + except: continue final_output = engine.compute_final_stylesheet() return json.dumps(final_output) diff --git a/assets/icons/Flyer.svg b/assets/icons/Flyer.svg @@ -1,4 +0,0 @@ -<svg width="57" height="38" viewBox="0 0 57 38" fill="none" xmlns="http://www.w3.org/2000/svg"> -<line x1="0.270379" y1="0.420475" x2="56.2704" y2="36.4205" stroke="#9F9FA9"/> -<line y1="-0.5" x2="66.5583" y2="-0.5" transform="matrix(-0.8411 0.54088 -0.554451 -0.832217 55.9822 0.841064)" stroke="#9F9FA9"/> -</svg> diff --git a/assets/icons/Group.svg b/assets/icons/group.svg diff --git a/assets/stylesheet/lighting/backlit.png b/assets/stylesheet/lighting/backlit.png Binary files differ. diff --git a/assets/stylesheet/lighting/diffused.png b/assets/stylesheet/lighting/diffused.png Binary files differ. diff --git a/assets/stylesheet/lighting/dramatic-contrast.png b/assets/stylesheet/lighting/dramatic-contrast.png Binary files differ. diff --git a/assets/stylesheet/lighting/flat-lighting.png b/assets/stylesheet/lighting/flat-lighting.png Binary files differ. diff --git a/assets/stylesheet/lighting/soft-light.png b/assets/stylesheet/lighting/soft-light.png Binary files differ. diff --git a/assets/stylesheet/lighting/spectacular-highlights.png b/assets/stylesheet/lighting/spectacular-highlights.png Binary files differ. diff --git a/assets/stylesheet/lighting/studio-lighting.png b/assets/stylesheet/lighting/studio-lighting.png Binary files differ. diff --git a/assets/stylesheet/material-look/glossy.png b/assets/stylesheet/material-look/glossy.png Binary files differ. diff --git a/assets/stylesheet/material-look/laminated.png b/assets/stylesheet/material-look/laminated.png Binary files differ. diff --git a/assets/stylesheet/material-look/matte.png b/assets/stylesheet/material-look/matte.png Binary files differ. diff --git a/assets/stylesheet/material-look/metallic.png b/assets/stylesheet/material-look/metallic.png Binary files differ. diff --git a/assets/stylesheet/material-look/organic.png b/assets/stylesheet/material-look/organic.png Binary files differ. diff --git a/assets/stylesheet/material-look/porcelain.png b/assets/stylesheet/material-look/porcelain.png Binary files differ. diff --git a/assets/stylesheet/material-look/wet-look.png b/assets/stylesheet/material-look/wet-look.png Binary files differ. diff --git a/assets/stylesheet/material-look/wooden.png b/assets/stylesheet/material-look/wooden.png Binary files differ. diff --git a/assets/stylesheet/texture/bokeh-background.png b/assets/stylesheet/texture/bokeh-background.png Binary files differ. diff --git a/assets/stylesheet/texture/concrete.png b/assets/stylesheet/texture/concrete.png Binary files differ. diff --git a/assets/stylesheet/texture/motifs.png b/assets/stylesheet/texture/motifs.png Binary files differ. diff --git a/assets/stylesheet/texture/newspaper-texture.png b/assets/stylesheet/texture/newspaper-texture.png Binary files differ. diff --git a/assets/stylesheet/texture/paper-grain.png b/assets/stylesheet/texture/paper-grain.png Binary files differ. diff --git a/assets/stylesheet/texture/printed-pattern.png b/assets/stylesheet/texture/printed-pattern.png Binary files differ. diff --git a/assets/stylesheet/texture/studio-backdrop.png b/assets/stylesheet/texture/studio-backdrop.png Binary files differ. diff --git a/assets/stylesheet/texture/subtle-grid.png b/assets/stylesheet/texture/subtle-grid.png Binary files differ. diff --git a/lib/services/analyze/texture.dart b/lib/services/analyze/texture.dart @@ -1,5 +1,3 @@ -// lib/services/analyze/texture.dart - import 'dart:io'; import 'dart:convert'; import 'package:adobe/utils/image_utils.dart'; diff --git a/lib/ui/pages/create_file_page.dart b/lib/ui/pages/create_file_page.dart @@ -67,7 +67,7 @@ class _CreateFilePageState extends State<CreateFilePage> { width: 336, height: 192, displaySize: '3.5 x 2 in', - svgPath: 'assets/icons/Group.svg', + svgPath: 'assets/icons/group.svg', ), CanvasPreset( name: 'Photo Collage', @@ -754,4 +754,3 @@ class _ProjectSelectionModalState extends State<ProjectSelectionModal> { ); } } - diff --git a/lib/ui/pages/stylesheet_page.dart b/lib/ui/pages/stylesheet_page.dart @@ -36,6 +36,48 @@ class _StylesheetPageState extends State<StylesheetPage> { final Map<String, String> _fontNameCache = {}; final ImagePicker _picker = ImagePicker(); + // --- ASSET MAPPING --- + final Map<String, String> _lightingAssets = { + 'backlit': 'assets/stylesheet/lighting/backlit.png', + 'diffused': 'assets/stylesheet/lighting/diffused.png', + 'dramaticcontrast': 'assets/stylesheet/lighting/dramatic-contrast.png', + 'flatlighting': 'assets/stylesheet/lighting/flat-lighting.png', + 'softlight': 'assets/stylesheet/lighting/soft-light.png', + 'spectacularhighlights': 'assets/stylesheet/lighting/spectacular-highlights.png', + 'specularhighlights': 'assets/stylesheet/lighting/spectacular-highlights.png', + 'studiolighting': 'assets/stylesheet/lighting/studio-lighting.png', + }; + + final Map<String, String> _materialAssets = { + 'glossy': 'assets/stylesheet/material-look/glossy.png', + 'laminated': 'assets/stylesheet/material-look/laminated.png', + 'matte': 'assets/stylesheet/material-look/matte.png', + 'metallic': 'assets/stylesheet/material-look/metallic.png', + 'mettalic': 'assets/stylesheet/material-look/metallic.png', + 'organic': 'assets/stylesheet/material-look/organic.png', + 'porcelain': 'assets/stylesheet/material-look/porcelain.png', + 'wetlook': 'assets/stylesheet/material-look/wet-look.png', + 'wooden': 'assets/stylesheet/material-look/wooden.png', + 'wood': 'assets/stylesheet/material-look/wooden.png', + 'plastic': 'assets/stylesheet/material-look/laminated.png', + }; + + final Map<String, String> _textureAssets = { + 'bokehbackground': 'assets/stylesheet/texture/bokeh-background.png', + 'concrete': 'assets/stylesheet/texture/concrete.png', + 'stone': 'assets/stylesheet/texture/concrete.png', + 'motifs': 'assets/stylesheet/texture/motifs.png', + 'motif': 'assets/stylesheet/texture/motifs.png', + 'newspapertexture': 'assets/stylesheet/texture/newspaper-texture.png', + 'newspaper': 'assets/stylesheet/texture/newspaper-texture.png', + 'papergrain': 'assets/stylesheet/texture/paper-grain.png', + 'printedpattern': 'assets/stylesheet/texture/printed-pattern.png', + 'studiobackdrop': 'assets/stylesheet/texture/studio-backdrop.png', + 'studio_backdrop': 'assets/stylesheet/texture/studio-backdrop.png', + 'subtlegrid': 'assets/stylesheet/texture/subtle-grid.png', + 'grid': 'assets/stylesheet/texture/subtle-grid.png', + }; + @override void initState() { super.initState(); @@ -50,7 +92,7 @@ class _StylesheetPageState extends State<StylesheetPage> { } } - // --- PARSING LOGIC --- + // --- UTILS --- String _cleanJsonString(String raw) { // 1. Remove Markdown code blocks if present (common AI artifact) String cleaned = raw.replaceAll(RegExp(r'^```json\s*|\s*```$'), ''); @@ -63,6 +105,20 @@ class _StylesheetPageState extends State<StylesheetPage> { return cleaned; } + String _formatLabel(String label) { + String clean = label.replaceAll(RegExp(r'[-_]'), ' '); + List<String> words = clean.split(' '); + return words.map((w) { + if (w.isEmpty) return ''; + return w[0].toUpperCase() + w.substring(1).toLowerCase(); + }).join(' '); + } + + String? _findAssetPath(Map<String, String> assetMap, String label) { + String normalized = label.toLowerCase().replaceAll(RegExp(r'[^a-z0-9]'), ''); + return assetMap[normalized]; + } + String _resolveGoogleFontName(String dirtyName) { if (_fontNameCache.containsKey(dirtyName)) { return _fontNameCache[dirtyName]!; @@ -201,13 +257,12 @@ class _StylesheetPageState extends State<StylesheetPage> { _logoPaths = []; _loadSavedStylesheet(); }), - onSettingsPressed: () { - // Handle settings action - }, + onSettingsPressed: () {}, ), + // Only show content once full stylesheet is parsed atleast once body: _isLoading ? const Center(child: CircularProgressIndicator(color: Variables.textPrimary)) - : (_stylesheetMap == null && _rawJsonString == null && _projectAssets.isEmpty && _logoPaths.isEmpty) + : (_stylesheetMap == null && _rawJsonString == null) ? _buildEmptyState() : _buildContent(), bottomNavigationBar: BottomBar(currentTab: BottomBarItem.stylesheet, projectId: _currentProjectId), @@ -220,7 +275,7 @@ class _StylesheetPageState extends State<StylesheetPage> { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - "Are you ready to start building\nyour stylesheet", + "Are you ready to start building\nyour visual identity", style: Variables.headerStyle.copyWith(fontSize: 18), textAlign: TextAlign.center, ), @@ -232,67 +287,43 @@ class _StylesheetPageState extends State<StylesheetPage> { } Widget _buildContent() { - // Extract data in order matching Figma design final graphics = _getData(['Graphics', 'graphics']); final colors = _getData(['Colour Palette', 'Color Palette', 'colors', 'Colors']); final typography = _getData(['Typography', 'fonts', 'Fonts']); - final compositions = _getData(['Compositions', 'compositions']); + final compositions = _getData(['Compositions', 'Composition', 'compositions']); final materialLook = _getData(['Material look', 'Material Look', 'material_look']); - final textures = _getData(['Textures', 'textures']); + 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', 'emotions']); + final era = _getData(['Era/Cultural Reference', 'Era', 'era']); + final emotions = _getData(['Emotions', 'Emotional', 'emotions']); return RefreshIndicator( onRefresh: _generateStylesheet, child: SingleChildScrollView( - padding: const EdgeInsets.only(left: 16, right: 16, top: 24, bottom: 24), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Regenerate Button - Center( - child: _buildGenerateButton("Regenerate Stylesheet"), - ), + Center(child: _buildGenerateButton("Regenerate Stylesheet")), const SizedBox(height: 24), - // Logos Section - always show for manual upload - _buildLogosSection(null), - // Graphics Section - includes project assets + _buildLogosSection(null), if (graphics != null || _projectAssets.isNotEmpty) _buildGraphicsSection(graphics), - - // Colors Section if (colors != null) _buildColorsSection(colors), - - // Fonts Section if (typography != null) _buildFontsSection(typography), - - // Compositions Section if (compositions != null) _buildCompositionsSection(compositions), - - // Material look Section if (materialLook != null) _buildMaterialLookSection(materialLook), - - // Textures Section if (textures != null) _buildTexturesSection(textures), - - // Lighting Section if (lighting != null) _buildLightingSection(lighting), - - // Style Section if (style != null) _buildStyleSection(style), - - // Era Section if (era != null) _buildEraSection(era), - - // Emotions Section if (emotions != null) _buildEmotionsSection(emotions), const SizedBox(height: 24), - ], - ), - ), + ], + ), + ), ); } @@ -300,18 +331,21 @@ class _StylesheetPageState extends State<StylesheetPage> { return GestureDetector( onTap: _generateStylesheet, child: Container( - width: 200, height: 44, - decoration: BoxDecoration(color: Variables.textPrimary, borderRadius: BorderRadius.circular(112)), - alignment: Alignment.center, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + decoration: BoxDecoration( + color: Variables.textPrimary, + borderRadius: BorderRadius.circular(112) + ), child: Row( + mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ Text(label, style: Variables.buttonTextStyle), const SizedBox(width: 8), SvgPicture.asset( 'assets/icons/generate_icon.svg', - width: 20, - height: 20, + width: 18, + height: 18, colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn), ), ], @@ -327,7 +361,7 @@ class _StylesheetPageState extends State<StylesheetPage> { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( - child: Text( + child: Text( title, style: const TextStyle( fontFamily: 'GeneralSans', @@ -340,15 +374,12 @@ class _StylesheetPageState extends State<StylesheetPage> { ), if (showArrow) Transform.rotate( - angle: 3.14159, // 180 degrees + angle: 3.14159, child: SvgPicture.asset( 'assets/icons/arrow-left-s-line.svg', width: 24, height: 24, - colorFilter: const ColorFilter.mode( - Variables.textPrimary, - BlendMode.srcIn, - ), + colorFilter: const ColorFilter.mode(Variables.textPrimary, BlendMode.srcIn), ), ), ], @@ -356,6 +387,7 @@ class _StylesheetPageState extends State<StylesheetPage> { ); } + // --- Logos --- Future<void> _pickLogo() async { try { final XFile? pickedFile = await _picker.pickImage(source: ImageSource.gallery); @@ -366,41 +398,30 @@ class _StylesheetPageState extends State<StylesheetPage> { } } catch (e) { debugPrint("Error picking logo: $e"); - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Error picking logo: $e')), - ); - } } } Widget _buildLogosSection(dynamic data) { // Use manually uploaded logos List<String> logoPaths = List.from(_logoPaths); - + // Also add any logos from data if present if (data is List) { for (var item in data) { - if (item is Map && item.containsKey('path')) { - logoPaths.add(item['path'].toString()); - } else if (item is String) { - logoPaths.add(item); - } + if (item is Map && item.containsKey('path')) logoPaths.add(item['path'].toString()); + else if (item is String) logoPaths.add(item); } - } else if (data is String) { - logoPaths.add(data); - } + } else if (data is String) logoPaths.add(data); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildSectionHeader("Logos"), - const SizedBox(height: 12), SizedBox( height: 76, child: ListView.separated( scrollDirection: Axis.horizontal, - itemCount: logoPaths.length + 1, // +1 for add button + itemCount: logoPaths.length + 1, separatorBuilder: (_, __) => const SizedBox(width: 8), itemBuilder: (context, index) { if (index == logoPaths.length) { @@ -417,18 +438,14 @@ class _StylesheetPageState extends State<StylesheetPage> { child: Center( child: SvgPicture.asset( 'assets/icons/add-line.svg', - width: 20, - height: 20, - colorFilter: const ColorFilter.mode( - Variables.textSecondary, - BlendMode.srcIn, - ), + width: 20, height: 20, + colorFilter: const ColorFilter.mode(Variables.textSecondary, BlendMode.srcIn), ), ), ), ); } - return _buildLogoCard(logoPaths[index]); + return _buildGraphicCard(logoPaths[index], size: 76); }, ), ), @@ -437,50 +454,15 @@ class _StylesheetPageState extends State<StylesheetPage> { ); } - Widget _buildLogoCard(String savedPath) { - return FutureBuilder<File?>( - future: _resolveFile(savedPath), - builder: (context, snapshot) { - final File? file = snapshot.data; - return Container( - width: 76, - height: 76, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: Colors.white, - ), - clipBehavior: Clip.antiAlias, - child: file != null - ? Image.file(file, fit: BoxFit.cover) - : Container( - color: Variables.surfaceSubtle, - child: const Center( - child: Icon(Icons.broken_image, color: Colors.grey, size: 20), - ), - ), - ); - }, - ); - } - + // --- Graphics --- Widget _buildGraphicsSection(dynamic data) { - List<String> graphicPaths = []; - - // Add project assets (subjects and assets) to graphics - graphicPaths.addAll(_projectAssets); - - // Add graphics from data if present + 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); - } + 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); - } + } else if (data is String) graphicPaths.add(data); if (graphicPaths.isEmpty) return const SizedBox(); @@ -488,7 +470,6 @@ class _StylesheetPageState extends State<StylesheetPage> { crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildSectionHeader("Graphics"), - const SizedBox(height: 12), SizedBox( height: 106, child: ListView.separated( @@ -503,14 +484,14 @@ class _StylesheetPageState extends State<StylesheetPage> { ); } - Widget _buildGraphicCard(String savedPath) { + Widget _buildGraphicCard(String savedPath, {double size = 104}) { return FutureBuilder<File?>( future: _resolveFile(savedPath), builder: (context, snapshot) { final File? file = snapshot.data; return Container( - width: 104, - height: 106, + width: size, + height: size, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), color: Variables.surfaceSubtle, @@ -529,68 +510,17 @@ class _StylesheetPageState extends State<StylesheetPage> { ); } - - Widget _buildFontCard(String rawFontName) { - final String correctFontName = _resolveGoogleFontName(rawFontName); - TextStyle sampleStyle; - try { - sampleStyle = GoogleFonts.getFont(correctFontName); - } catch (_) { - sampleStyle = const TextStyle(fontFamily: 'GeneralSans'); - } - - // Get font info (styles count) - simplified for now - String fontInfo = "16 styles + variable cut"; - - return Builder( - builder: (context) => Container( - width: (MediaQuery.of(context).size.width - 32 - 16) / 3, // 3 cards with spacing - height: 120, - padding: const EdgeInsets.all(9), - decoration: BoxDecoration( - border: Border.all(color: Variables.borderSubtle), - borderRadius: BorderRadius.circular(8), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Aa", - style: sampleStyle.copyWith( - fontSize: 28, - height: 1.2, - fontWeight: FontWeight.w400, - color: Variables.textPrimary, - ), - ), - Text( - fontInfo, - style: const TextStyle( - fontFamily: 'GeneralSans', - fontSize: 12, - height: 16 / 12, - color: Variables.textSecondary, - fontWeight: FontWeight.w400, - ), - ), - ], - ), - ), - ); - } - + // --- 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()); + 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()); - } else if (data is String) fontNames.add(data.trim()); + } if (fontNames.isEmpty) return const SizedBox(); @@ -598,7 +528,6 @@ class _StylesheetPageState extends State<StylesheetPage> { crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildSectionHeader("Fonts"), - const SizedBox(height: 12), SizedBox( height: 120, child: ListView.separated( @@ -613,18 +542,60 @@ class _StylesheetPageState extends State<StylesheetPage> { ); } + Widget _buildFontCard(String rawFontName) { + final String correctFontName = _resolveGoogleFontName(rawFontName); + final String displayFontName = _formatLabel(rawFontName); + + TextStyle sampleStyle; + try { + sampleStyle = GoogleFonts.getFont(correctFontName); + } catch (_) { + sampleStyle = const TextStyle(fontFamily: 'GeneralSans'); + } + + return Container( + width: (MediaQuery.of(context).size.width - 32 - 16) / 3, + height: 120, + padding: const EdgeInsets.all(9), + decoration: BoxDecoration( + border: Border.all(color: Variables.borderSubtle), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Aa", + style: sampleStyle.copyWith( + fontSize: 28, + height: 1.2, + fontWeight: FontWeight.w400, + color: Variables.textPrimary, + ), + ), + Text( + displayFontName, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontFamily: 'GeneralSans', fontSize: 12, height: 16 / 12, + color: Variables.textSecondary, fontWeight: FontWeight.w400, + ), + ), + ], + ), + ); + } + + // --- 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 (item is Map && item.containsKey('label')) colorList.add(item['label'].toString()); + else if (item is String) colorList.add(item); } - } else if (data is String) { - colorList.add(data); } if (colorList.isEmpty) return const SizedBox(); @@ -633,10 +604,8 @@ class _StylesheetPageState extends State<StylesheetPage> { crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildSectionHeader("Colors", showArrow: false), - const SizedBox(height: 12), Wrap( - spacing: 8, - runSpacing: 8, + spacing: 8, runSpacing: 8, children: colorList.take(5).map((color) => _buildColorSwatch(color)).toList(), ), const SizedBox(height: 16), @@ -647,161 +616,125 @@ class _StylesheetPageState extends State<StylesheetPage> { Widget _buildColorSwatch(String label) { Color color = _getColorFromLabel(label); return Container( - width: 104, - height: 52, - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.circular(8), - ), + width: 104, height: 52, + decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(8)), ); } - Widget _buildCompositionsSection(dynamic data) { - List<String> compositionPaths = []; - if (data is List) { - for (var item in data) { - if (item is Map && item.containsKey('path')) { - compositionPaths.add(item['path'].toString()); - } else if (item is String) { - compositionPaths.add(item); - } - } - } else if (data is String) { - compositionPaths.add(data); + 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; + } - if (compositionPaths.isEmpty) return const SizedBox(); + // --- Unified Card Builder --- + Widget _buildUnifiedCard(String label, String? assetPath) { + final formattedLabel = _formatLabel(label); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildSectionHeader("Compositions"), - const SizedBox(height: 12), - SizedBox( - height: 106, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: compositionPaths.length, - separatorBuilder: (_, __) => const SizedBox(width: 8), - itemBuilder: (context, index) => _buildCompositionCard(compositionPaths[index]), - ), + if (assetPath != null) { + return Container( + width: 106, + height: 106, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + border: Border.all(color: Variables.borderSubtle.withOpacity(0.5)), ), - const SizedBox(height: 16), - ], - ); - } - - Widget _buildCompositionCard(String savedPath) { - return FutureBuilder<File?>( - future: _resolveFile(savedPath), - builder: (context, snapshot) { - final File? file = snapshot.data; - return Container( - width: 104, - height: 106, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: Variables.surfaceSubtle, - ), - clipBehavior: Clip.antiAlias, - child: file != null - ? Image.file(file, fit: BoxFit.cover) - : Container( - color: Variables.surfaceSubtle, - child: const Center( - child: Icon(Icons.broken_image, color: Colors.grey, size: 20), + clipBehavior: Clip.antiAlias, + child: Stack( + children: [ + Positioned.fill( + child: Image.asset( + assetPath, + fit: BoxFit.cover, + errorBuilder: (c, o, s) => Container(color: Variables.surfaceSubtle), + ), + ), + Positioned( + left: 0, right: 0, bottom: 0, + height: 40, + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Colors.transparent, Colors.black.withOpacity(0.7)], ), ), - ); - }, - ); - } - - Widget _buildMaterialLookSection(dynamic data) { - List<String> materialPaths = []; - if (data is List) { - for (var item in data) { - if (item is Map && item.containsKey('path')) { - materialPaths.add(item['path'].toString()); - } else if (item is String) { - materialPaths.add(item); - } - } - } else if (data is String) { - materialPaths.add(data); - } - - if (materialPaths.isEmpty) return const SizedBox(); - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildSectionHeader("Material look"), - const SizedBox(height: 12), - Wrap( - spacing: 8, - runSpacing: 8, - children: materialPaths.take(2).map((path) => _buildMaterialCard(path)).toList(), + ), + ), + Positioned( + left: 4, right: 4, bottom: 4, + child: Text( + formattedLabel, + style: const TextStyle( + fontFamily: 'GeneralSans', + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], ), - const SizedBox(height: 16), - ], - ); - } - - Widget _buildMaterialCard(String savedPath) { - return FutureBuilder<File?>( - future: _resolveFile(savedPath), - builder: (context, snapshot) { - final File? file = snapshot.data; - return Container( - width: 106, - height: 106, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: Variables.surfaceSubtle, + ); + } else { + return Container( + width: 120, + height: 48, + padding: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: Variables.surfaceSubtle, + borderRadius: BorderRadius.circular(8), + ), + alignment: Alignment.center, + child: Text( + formattedLabel, + style: const TextStyle( + fontFamily: 'GeneralSans', + fontSize: 14, + fontWeight: FontWeight.w500, + color: Variables.textPrimary, ), - clipBehavior: Clip.antiAlias, - child: file != null - ? Image.file(file, fit: BoxFit.cover) - : Container( - color: Variables.surfaceSubtle, - child: const Center( - child: Icon(Icons.broken_image, color: Colors.grey, size: 20), - ), - ), - ); - }, - ); + textAlign: TextAlign.center, + ), + ); + } } - Widget _buildTexturesSection(dynamic data) { - List<String> texturePaths = []; + 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('path')) { - texturePaths.add(item['path'].toString()); - } else if (item is String) { - texturePaths.add(item); - } + 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) { - texturePaths.add(data); - } + } else if (data is String) items.add(data); - if (texturePaths.isEmpty) return const SizedBox(); + if (items.isEmpty) return const SizedBox(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildSectionHeader("Textures"), - const SizedBox(height: 12), + _buildSectionHeader(title), SizedBox( - height: 106, + height: (assetMap != null) ? 106 : 54, child: ListView.separated( scrollDirection: Axis.horizontal, - itemCount: texturePaths.length, + itemCount: items.length, separatorBuilder: (_, __) => const SizedBox(width: 8), - itemBuilder: (context, index) => _buildTextureCard(texturePaths[index]), + itemBuilder: (context, index) { + String label = items[index]; + String? assetPath = assetMap != null ? _findAssetPath(assetMap, label) : null; + return _buildUnifiedCard(label, assetPath); + }, ), ), const SizedBox(height: 16), @@ -809,213 +742,31 @@ class _StylesheetPageState extends State<StylesheetPage> { ); } - Widget _buildTextureCard(String savedPath) { - return FutureBuilder<File?>( - future: _resolveFile(savedPath), - builder: (context, snapshot) { - final File? file = snapshot.data; - return Container( - width: 104, - height: 106, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: Variables.surfaceSubtle, - ), - clipBehavior: Clip.antiAlias, - child: file != null - ? Image.file(file, fit: BoxFit.cover) - : Container( - color: Variables.surfaceSubtle, - child: const Center( - child: Icon(Icons.broken_image, color: Colors.grey, size: 20), - ), - ), - ); - }, - ); + Widget _buildCompositionsSection(dynamic data) { + return _buildAttributeSection("Compositions", data, null); } - Widget _buildLightingSection(dynamic data) { - List<String> lightingPaths = []; - if (data is List) { - for (var item in data) { - if (item is Map && item.containsKey('path')) { - lightingPaths.add(item['path'].toString()); - } else if (item is String) { - lightingPaths.add(item); - } - } - } else if (data is String) { - lightingPaths.add(data); - } - - if (lightingPaths.isEmpty) return const SizedBox(); + Widget _buildMaterialLookSection(dynamic data) { + return _buildAttributeSection("Material Look", data, _materialAssets); + } - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildSectionHeader("Lighting"), - const SizedBox(height: 12), - Wrap( - spacing: 8, - runSpacing: 8, - children: lightingPaths.take(3).map((path) => _buildLightingCard(path)).toList(), - ), - const SizedBox(height: 16), - ], - ); + Widget _buildTexturesSection(dynamic data) { + return _buildAttributeSection("Textures", data, _textureAssets); } - Widget _buildLightingCard(String savedPath) { - return FutureBuilder<File?>( - future: _resolveFile(savedPath), - builder: (context, snapshot) { - final File? file = snapshot.data; - return Container( - width: 106, - height: 106, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: Variables.surfaceSubtle, - ), - clipBehavior: Clip.antiAlias, - child: file != null - ? Image.file(file, fit: BoxFit.cover) - : Container( - color: Variables.surfaceSubtle, - child: const Center( - child: Icon(Icons.broken_image, color: Colors.grey, size: 20), - ), - ), - ); - }, - ); + Widget _buildLightingSection(dynamic data) { + return _buildAttributeSection("Lighting", data, _lightingAssets); } Widget _buildStyleSection(dynamic data) { - List<String> styleTags = []; - if (data is List) { - for (var item in data) { - if (item is Map && item.containsKey('label')) { - styleTags.add(item['label'].toString()); - } else if (item is String) { - styleTags.add(item); - } - } - } else if (data is String) { - styleTags.add(data); - } - - if (styleTags.isEmpty) return const SizedBox(); - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildSectionHeader("Style"), - const SizedBox(height: 12), - Wrap( - spacing: 8, - runSpacing: 8, - children: styleTags.take(4).map((tag) => _buildTagCard(tag)).toList(), - ), - const SizedBox(height: 16), - ], - ); + return _buildAttributeSection("Style", data, null); } Widget _buildEraSection(dynamic data) { - List<String> eraTags = []; - if (data is List) { - for (var item in data) { - if (item is Map && item.containsKey('label')) { - eraTags.add(item['label'].toString()); - } else if (item is String) { - eraTags.add(item); - } - } - } else if (data is String) { - eraTags.add(data); - } - - if (eraTags.isEmpty) return const SizedBox(); - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildSectionHeader("Era"), - const SizedBox(height: 12), - Wrap( - spacing: 8, - runSpacing: 8, - children: eraTags.take(4).map((tag) => _buildTagCard(tag)).toList(), - ), - const SizedBox(height: 16), - ], - ); + return _buildAttributeSection("Era", data, null); } Widget _buildEmotionsSection(dynamic data) { - List<String> emotionTags = []; - if (data is List) { - for (var item in data) { - if (item is Map && item.containsKey('label')) { - emotionTags.add(item['label'].toString()); - } else if (item is String) { - emotionTags.add(item); - } - } - } else if (data is String) { - emotionTags.add(data); - } - - if (emotionTags.isEmpty) return const SizedBox(); - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildSectionHeader("Emotions"), - const SizedBox(height: 12), - Wrap( - spacing: 8, - runSpacing: 8, - children: emotionTags.take(4).map((tag) => _buildTagCard(tag)).toList(), - ), - const SizedBox(height: 16), - ], - ); - } - - Widget _buildTagCard(String label) { - return Container( - width: 160, - height: 54, - decoration: BoxDecoration( - color: Variables.surfaceSubtle, - borderRadius: BorderRadius.circular(8), - ), - alignment: Alignment.center, - child: Text( - label, - style: const TextStyle( - fontFamily: 'GeneralSans', - fontSize: 16, - fontWeight: FontWeight.w500, - height: 24 / 16, - color: Variables.textPrimary, - ), - ), - ); - } - - 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; // Fallback + return _buildAttributeSection("Emotions", data, null); } } diff --git a/pubspec.yaml b/pubspec.yaml @@ -47,6 +47,9 @@ flutter: - assets/fonts/ - assets/icons/ - assets/templates/ + - assets/stylesheet/lighting/ + - assets/stylesheet/material-look/ + - assets/stylesheet/texture/ - assets/moodboard_blob.png - assets/stylesheet_blob.png - assets/files_blob.png