creek

The AI Image Editor of 2030
commit 2aae2370e1061099646ab683303b497f31b882cd
parent b60aa5f4900976cc2152df00bdfc8c44db3e544f
Author: V7 <maydayv7@gmail.com>
Date:   Mon,  1 Dec 2025 16:31:53 +0530

Merge pull request #26 from nilotpal-n7/abhinav


Diffstat:
Mlib/ui/pages/home_page.dart | 138++++++++++++++++++++++++++++++++++++-------------------------------------------
Mlib/ui/pages/stylesheet_page.dart | 433+++++++++++++++++--------------------------------------------------------------
Mpubspec.yaml | 2+-
3 files changed, 157 insertions(+), 416 deletions(-)

diff --git a/lib/ui/pages/home_page.dart b/lib/ui/pages/home_page.dart @@ -29,7 +29,7 @@ class _HomePageState extends State<HomePage> { final Map<int, List<String>> _projectPreviews = {}; Map<String, String> _fileDimensions = {}; bool _isLoading = true; - final String _userName = "Alex"; // Can be loaded from preferences later + final String _userName = "Alex"; @override void initState() { @@ -40,23 +40,18 @@ class _HomePageState extends State<HomePage> { Future<void> _loadData() async { setState(() => _isLoading = true); try { - // Load all projects and create a map for quick lookup final allProjects = await _projectRepo.getAllProjects(); final Map<int, ProjectModel> projectMap = {}; for (final project in allProjects) { if (project.id != null) { projectMap[project.id!] = project; - // Load preview images for each project final images = await _imageRepo.getImages(project.id!); _projectPreviews[project.id!] = images.take(4).map((img) => img.filePath).toList(); } } - // Load recent files final recentFiles = await _fileRepo.getRecentFiles(limit: 10); - - // Load dimensions for files final Map<String, String> fileDimensions = {}; for (final file in recentFiles) { try { @@ -113,8 +108,7 @@ class _HomePageState extends State<HomePage> { String _getProjectBreadcrumb(FileModel file) { final project = _projectMap[file.projectId]; if (project == null) return ''; - - // Check if project is an event (has parentId) + if (project.isEvent) { final parentProject = _projectMap[project.parentId!]; if (parentProject != null) { @@ -139,12 +133,12 @@ class _HomePageState extends State<HomePage> { }); } + // --- NEW METHOD: Test the Canvas Page --- + + void _openProject(ProjectModel project) { - // Update last accessed time if (project.id != null) { _projectService.openProject(project.id!); - - // Navigate to project detail page Navigator.push( context, MaterialPageRoute( @@ -155,7 +149,6 @@ class _HomePageState extends State<HomePage> { } void _openFile(FileModel file) { - // Navigate to file detail or project detail final project = _projectMap[file.projectId]; if (project != null) { _openProject(project); @@ -169,52 +162,52 @@ class _HomePageState extends State<HomePage> { return Scaffold( backgroundColor: theme.scaffoldBackgroundColor, - body: - _isLoading - ? const Center(child: CircularProgressIndicator()) - : SafeArea( - child: RefreshIndicator( - onRefresh: _loadData, - child: CustomScrollView( - slivers: [ - // Header Section - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.fromLTRB(16, 20, 16, 16), - child: Row( - children: [ - Text( - "Hello, $_userName!", - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.w500, - fontFamily: 'GeneralSans', - color: theme.colorScheme.onSurface, - ), + body: _isLoading + ? const Center(child: CircularProgressIndicator()) + : SafeArea( + child: RefreshIndicator( + onRefresh: _loadData, + child: CustomScrollView( + slivers: [ + // Header Section + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 20, 16, 16), + child: Row( + children: [ + Text( + "Hello, $_userName!", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w500, + fontFamily: 'GeneralSans', + color: theme.colorScheme.onSurface, ), - const Spacer(), - // Profile Picture - Container( - width: 30, - height: 30, - decoration: BoxDecoration( - color: theme.colorScheme.primaryContainer, - shape: BoxShape.circle, - border: Border.all( - color: theme.scaffoldBackgroundColor, - width: 1.25, - ), - ), - child: Icon( - Icons.person, - size: 16, - color: theme.colorScheme.onPrimaryContainer, + ), + const Spacer(), + + // Profile Picture + Container( + width: 30, + height: 30, + decoration: BoxDecoration( + color: theme.colorScheme.primaryContainer, + shape: BoxShape.circle, + border: Border.all( + color: theme.scaffoldBackgroundColor, + width: 1.25, ), ), - ], - ), + child: Icon( + Icons.person, + size: 16, + color: theme.colorScheme.onPrimaryContainer, + ), + ), + ], ), ), + ), // Search Bar SliverToBoxAdapter( @@ -270,7 +263,7 @@ class _HomePageState extends State<HomePage> { ), ), - const SliverToBoxAdapter(child: SizedBox(height: 12)), + const SliverToBoxAdapter(child: SizedBox(height: 12)), // Content Sections SliverToBoxAdapter( @@ -333,28 +326,26 @@ class _HomePageState extends State<HomePage> { ), const SizedBox(height: 24), - // Explore Templates Section - _buildSectionHeader( - 'Explore templates', - theme, - onTap: () { - // Navigate to templates - }, - ), - const SizedBox(height: 12), - _buildTemplatesSection(theme, isDark), - const SizedBox(height: 24), - ], - ), + // Explore Templates Section + _buildSectionHeader( + 'Explore templates', + theme, + onTap: () {}, + ), + const SizedBox(height: 12), + _buildTemplatesSection(theme, isDark), + const SizedBox(height: 24), + ], ), ), + ), - // Bottom padding - const SliverToBoxAdapter(child: SizedBox(height: 100)), - ], - ), + // Bottom padding + const SliverToBoxAdapter(child: SizedBox(height: 100)), + ], ), ), + ), floatingActionButton: FloatingActionButton( onPressed: _createNewProject, backgroundColor: isDark ? Colors.grey[900] : Colors.grey[900], @@ -409,7 +400,6 @@ class _HomePageState extends State<HomePage> { child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Thumbnail Container( width: 104, height: 106, @@ -437,7 +427,6 @@ class _HomePageState extends State<HomePage> { ), ), const SizedBox(width: 8), - // File Info Expanded( child: Padding( padding: const EdgeInsets.symmetric(vertical: 8), @@ -541,7 +530,6 @@ class _HomePageState extends State<HomePage> { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Preview Image Expanded( child: ClipRRect( borderRadius: const BorderRadius.vertical( @@ -581,7 +569,6 @@ class _HomePageState extends State<HomePage> { ), ), ), - // Project Title and Actions Padding( padding: const EdgeInsets.all(8), child: Row( @@ -615,7 +602,6 @@ class _HomePageState extends State<HomePage> { } Widget _buildTemplatesSection(ThemeData theme, bool isDark) { - // Static templates for now - can be made dynamic later final templates = [ {'title': 'Diwali Lights', 'subtitle': 'Instagram Post'}, {'title': 'Business Opening', 'subtitle': 'Flyer'}, diff --git a/lib/ui/pages/stylesheet_page.dart b/lib/ui/pages/stylesheet_page.dart @@ -28,10 +28,7 @@ class _StylesheetPageState extends State<StylesheetPage> { Map<String, dynamic>? _stylesheetMap; String? _rawJsonString; - // New state variable to hold assets from ProjectRepo List<String> _projectAssets = []; - - // Cache for font name lookups final Map<String, String> _fontNameCache = {}; @override @@ -43,24 +40,14 @@ class _StylesheetPageState extends State<StylesheetPage> { // --- PARSING LOGIC --- String _cleanJsonString(String raw) { - String cleaned = 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]}', ); - cleaned = cleaned.replaceAllMapped( - RegExp(r'(:\s*)([a-zA-Z0-9_\-\.\/\s]+)(?=\s*[,}])'), - (match) { - String val = match[2]!.trim(); - if (val == 'true' || - val == 'false' || - val == 'null' || - double.tryParse(val) != null) { - return match[0]!; - } - return '${match[1]}"$val"'; - }, - ); return cleaned; } @@ -68,19 +55,10 @@ class _StylesheetPageState extends State<StylesheetPage> { if (_fontNameCache.containsKey(dirtyName)) { return _fontNameCache[dirtyName]!; } - - String cleanInput = dirtyName - .toLowerCase() - .replaceAll(RegExp(r'[-_]regular$'), '') - .replaceAll(RegExp(r'[^a-z0-9]'), ''); - + String cleanInput = dirtyName.toLowerCase().replaceAll(RegExp(r'[-_]regular$'), '').replaceAll(RegExp(r'[^a-z0-9]'), ''); final allFonts = GoogleFonts.asMap().keys; - for (String officialName in allFonts) { - String cleanOfficial = officialName.toLowerCase().replaceAll( - RegExp(r'[^a-z0-9]'), - '', - ); + String cleanOfficial = officialName.toLowerCase().replaceAll(RegExp(r'[^a-z0-9]'), ''); if (cleanOfficial == cleanInput) { _fontNameCache[dirtyName] = officialName; return officialName; @@ -90,51 +68,37 @@ class _StylesheetPageState extends State<StylesheetPage> { } Future<File?> _resolveFile(String path) async { - // 1. Try the path exactly as saved final file = File(path); if (await file.exists()) return file; - - // 2. If that fails (iOS UUID change?), try to find it in the current docs dir try { - final filename = p.basename( - path, - ); // Get "image_123.png" from the long path + final filename = p.basename(path); final dir = await getApplicationDocumentsDirectory(); - - // Reconstruct path: CurrentDir + generated_images + filename final fixedPath = p.join(dir.path, 'generated_images', filename); - final fixedFile = File(fixedPath); - if (await fixedFile.exists()) { - return fixedFile; - } + if (await fixedFile.exists()) return fixedFile; } catch (e) { debugPrint("Error resolving file path: $e"); } - return null; } Future<void> _loadSavedStylesheet() async { final project = await ProjectRepo().getProjectById(_currentProjectId); - if (project == null) return; - // 1. Load Assets directly from Project Model List<String> currentAssets = project.assetsPath; - - // 2. Load Stylesheet JSON Map<String, dynamic>? parsedMap; String? rawJson; - if (project.globalStylesheet != null && - project.globalStylesheet!.isNotEmpty) { + if (project.globalStylesheet != null && project.globalStylesheet!.isNotEmpty) { rawJson = project.globalStylesheet!; dynamic parsed; + // Try standard decode first try { parsed = jsonDecode(rawJson); } catch (e) { + // Try cleaning markdown and loose keys try { parsed = jsonDecode(_cleanJsonString(rawJson)); } catch (_) {} @@ -172,32 +136,16 @@ class _StylesheetPageState extends State<StylesheetPage> { }); try { - // 1. Fetch Image Analysis Data final images = await ImageRepo().getImages(_currentProjectId); - final List<String> analysisData = - images - .map((img) => img.analysisData) - .where((data) => data != null && data.isNotEmpty) - .cast<String>() - .toList(); - - // 2. Fetch Note Analysis Data + final List<String> analysisData = images.map((img) => img.analysisData).where((data) => data != null && data.isNotEmpty).cast<String>().toList(); final notes = await NoteRepo().getNotesByProjectId(_currentProjectId); - final List<String> noteAnalysisData = - notes - .map((n) => n.analysisData) - .where((data) => data != null && data.isNotEmpty) - .cast<String>() - .toList(); - - // 3. Combine both + final List<String> noteAnalysisData = notes.map((n) => n.analysisData).where((data) => data != null && data.isNotEmpty).cast<String>().toList(); + analysisData.addAll(noteAnalysisData); if (analysisData.isEmpty) { if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("No analyzed images or notes found.")), - ); + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("No analyzed images or notes found."))); } return; } @@ -207,8 +155,6 @@ class _StylesheetPageState extends State<StylesheetPage> { if (mounted && result != null) { final jsonString = jsonEncode(result); await ProjectRepo().updateStylesheet(_currentProjectId, jsonString); - - // Reload everything (assets + stylesheet) to keep sync await _loadSavedStylesheet(); } } catch (e) { @@ -223,8 +169,7 @@ class _StylesheetPageState extends State<StylesheetPage> { for (var k in keys) { if (_stylesheetMap!.containsKey(k)) return _stylesheetMap![k]; for (var mapKey in _stylesheetMap!.keys) { - if (mapKey.toLowerCase() == k.toLowerCase()) - return _stylesheetMap![mapKey]; + if (mapKey.toLowerCase() == k.toLowerCase()) return _stylesheetMap![mapKey]; } } return null; @@ -237,28 +182,19 @@ class _StylesheetPageState extends State<StylesheetPage> { appBar: TopBar( currentProjectId: _currentProjectId, onBack: () => Navigator.of(context).pop(), - onProjectChanged: - (p) => setState(() { - _currentProjectId = p.id!; - _stylesheetMap = null; - _projectAssets = []; - _loadSavedStylesheet(); - }), + onProjectChanged: (p) => setState(() { + _currentProjectId = p.id!; + _stylesheetMap = null; + _projectAssets = []; + _loadSavedStylesheet(); + }), ), - body: - _isLoading - ? const Center( - child: CircularProgressIndicator(color: Variables.textPrimary), - ) - : (_stylesheetMap == null && - _rawJsonString == null && - _projectAssets.isEmpty) + body: _isLoading + ? const Center(child: CircularProgressIndicator(color: Variables.textPrimary)) + : (_stylesheetMap == null && _rawJsonString == null && _projectAssets.isEmpty) ? _buildEmptyState() : _buildContent(), - bottomNavigationBar: BottomBar( - currentTab: BottomBarItem.stylesheet, - projectId: _currentProjectId, - ), + bottomNavigationBar: BottomBar(currentTab: BottomBarItem.stylesheet, projectId: _currentProjectId), ); } @@ -267,10 +203,7 @@ class _StylesheetPageState extends State<StylesheetPage> { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - "No stylesheet data.", - style: Variables.headerStyle.copyWith(fontSize: 18), - ), + Text("No stylesheet data.", style: Variables.headerStyle.copyWith(fontSize: 18)), const SizedBox(height: 24), _buildGenerateButton("Generate Stylesheet"), ], @@ -279,24 +212,39 @@ class _StylesheetPageState extends State<StylesheetPage> { } Widget _buildContent() { - final style = _getData(['Style', 'style']); + // Added 'Composition' to style lookup to match your screenshot + final style = _getData(['Style', 'style', 'Composition', 'composition']); final lighting = _getData(['Lighting', 'lighting']); final colors = _getData(['Colour Palette', 'Color Palette', 'colors']); final emotions = _getData(['Emotions', 'emotions']); final era = _getData(['Era/Cultural Reference', 'era']); final typography = _getData(['Typography', 'fonts']); - // We check _projectAssets.isNotEmpty to determine if we show the section final hasAssets = _projectAssets.isNotEmpty; - final foundAny = - (style != null || - lighting != null || - colors != null || - emotions != null || - era != null || - typography != null || - hasAssets); + // --- DYNAMIC SECTION GENERATOR --- + // If the map contains keys we haven't hardcoded above, generate sliders for them. + // This prevents the "Parsing Error" when valid data exists but the key is new. + List<Widget> dynamicSections = []; + List<String> handledKeys = [ + 'Style', 'style', 'Composition', 'composition', + 'Lighting', 'lighting', + 'Colour Palette', 'Color Palette', 'colors', + 'Emotions', 'emotions', + 'Era/Cultural Reference', 'era', + 'Typography', 'fonts' + ]; + + if (_stylesheetMap != null) { + for (var key in _stylesheetMap!.keys) { + // If we haven't handled this key yet and it looks like a list (slider data) + if (!handledKeys.any((k) => k.toLowerCase() == key.toLowerCase()) && _stylesheetMap![key] is List) { + dynamicSections.add(_buildSliderSection(key, _stylesheetMap![key])); + } + } + } + + final foundAny = (style != null || lighting != null || colors != null || emotions != null || era != null || typography != null || hasAssets || dynamicSections.isNotEmpty); return RefreshIndicator( onRefresh: _generateStylesheet, @@ -310,31 +258,16 @@ class _StylesheetPageState extends State<StylesheetPage> { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text( - "Visual Identity", - style: TextStyle( - fontFamily: 'GeneralSans', - fontSize: 24, - fontWeight: FontWeight.w600, - ), - ), - IconButton( - icon: const Icon(Icons.refresh), - onPressed: _generateStylesheet, - ), + const Text("Visual Identity", style: TextStyle(fontFamily: 'GeneralSans', fontSize: 24, fontWeight: FontWeight.w600)), + IconButton(icon: const Icon(Icons.refresh), onPressed: _generateStylesheet), ], ), ), const SizedBox(height: 32), if (foundAny) ...[ - // 1. Assets / Subjects Section (From Project Repo) if (hasAssets) _buildAssetsSection(_projectAssets), - - // 2. Typography (Now a Slider) if (typography != null) _buildTypographySection(typography), - - // 3. Color Palette if (colors != null) ...[ Padding( padding: const EdgeInsets.symmetric(horizontal: 20), @@ -348,28 +281,24 @@ class _StylesheetPageState extends State<StylesheetPage> { ), const SizedBox(height: 32), ], - - // 4. Slider Sections - if (style != null) - _buildSliderSection("Style & Aesthetic", style), - if (emotions != null) - _buildSliderSection("Mood & Emotions", emotions), + if (style != null) _buildSliderSection("Style & Aesthetic", style), // Matches Composition now + if (emotions != null) _buildSliderSection("Mood & Emotions", emotions), if (lighting != null) _buildSliderSection("Lighting", lighting), if (era != null) _buildSliderSection("Era & Culture", era), + + // Render any extra data found in the JSON + ...dynamicSections, + ] else ...[ Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Container( padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.red.shade50, - borderRadius: BorderRadius.circular(12), - ), + decoration: BoxDecoration(color: Colors.red.shade50, borderRadius: BorderRadius.circular(12)), child: Text("Parsing Error. Raw Data:\n\n$_rawJsonString"), ), ), ], - const SizedBox(height: 40), Center(child: _buildGenerateButton("Regenerate")), const SizedBox(height: 40), @@ -383,12 +312,8 @@ class _StylesheetPageState extends State<StylesheetPage> { return GestureDetector( onTap: _generateStylesheet, child: Container( - width: 200, - height: 44, - decoration: BoxDecoration( - color: Variables.textPrimary, - borderRadius: BorderRadius.circular(112), - ), + width: 200, height: 44, + decoration: BoxDecoration(color: Variables.textPrimary, borderRadius: BorderRadius.circular(112)), alignment: Alignment.center, child: Text(label, style: Variables.buttonTextStyle), ), @@ -400,18 +325,11 @@ class _StylesheetPageState extends State<StylesheetPage> { padding: const EdgeInsets.only(bottom: 16), child: Text( title.toUpperCase(), - style: const TextStyle( - fontFamily: 'GeneralSans', - fontSize: 12, - fontWeight: FontWeight.bold, - letterSpacing: 1.2, - color: Variables.textSecondary, - ), + style: const TextStyle(fontFamily: 'GeneralSans', fontSize: 12, fontWeight: FontWeight.bold, letterSpacing: 1.2, color: Variables.textSecondary), ), ); } - // --- ASSETS SECTION --- Widget _buildAssetsSection(List<String> imagePaths) { return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -425,12 +343,7 @@ class _StylesheetPageState extends State<StylesheetPage> { child: GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 12, - mainAxisSpacing: 12, - childAspectRatio: 0.8, - ), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 12, mainAxisSpacing: 12, childAspectRatio: 0.8), itemCount: imagePaths.length, itemBuilder: (context, index) => _buildAssetCard(imagePaths[index]), ), @@ -445,101 +358,46 @@ class _StylesheetPageState extends State<StylesheetPage> { future: _resolveFile(savedPath), builder: (context, snapshot) { final File? file = snapshot.data; - final bool exists = file != null; - return Container( decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular( - 12, - ), // Matching color card radius + borderRadius: BorderRadius.circular(12), border: Border.all(color: Variables.borderSubtle), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.03), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], + boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 8, offset: const Offset(0, 2))], ), clipBehavior: Clip.antiAlias, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: - exists - ? Image.file(file, fit: BoxFit.cover) - : Container( - color: Colors.grey.shade100, - child: const Center( - child: Icon( - Icons.broken_image, - color: Colors.grey, - size: 20, - ), - ), - ), - ), - // Container( - // padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), - // color: Colors.white, - // child: const Text( - // "Asset", - // textAlign: TextAlign.center, - // style: TextStyle( - // fontFamily: 'GeneralSans', - // fontSize: 10, - // fontWeight: FontWeight.w600, - // color: Variables.textPrimary, - // ), - // maxLines: 1, - // overflow: TextOverflow.ellipsis, - // ), - // ) - ], - ), + child: file != null ? Image.file(file, fit: BoxFit.cover) : Container(color: Colors.grey.shade100, child: const Center(child: Icon(Icons.broken_image, color: Colors.grey, size: 20))), ); }, ); } - // --- TYPOGRAPHY SECTION (UPDATED TO SLIDER) --- Widget _buildTypographySection(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 (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()); - } + } else if (data is String) fontNames.add(data.trim()); if (fontNames.isEmpty) return const SizedBox(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: _buildSectionHeader("Typography"), - ), + Padding(padding: const EdgeInsets.symmetric(horizontal: 20), child: _buildSectionHeader("Typography")), SizedBox( - height: 150, // Height for the cards + height: 150, child: ListView.separated( padding: const EdgeInsets.symmetric(horizontal: 20), scrollDirection: Axis.horizontal, itemCount: fontNames.length, separatorBuilder: (_, __) => const SizedBox(width: 12), - itemBuilder: - (context, index) => _buildTypographyCard(fontNames[index]), + itemBuilder: (context, index) => _buildTypographyCard(fontNames[index]), ), ), const SizedBox(height: 32), @@ -550,7 +408,6 @@ class _StylesheetPageState extends State<StylesheetPage> { Widget _buildTypographyCard(String rawFontName) { final String correctFontName = _resolveGoogleFontName(rawFontName); TextStyle sampleStyle; - try { sampleStyle = GoogleFonts.getFont(correctFontName); } catch (_) { @@ -558,58 +415,19 @@ class _StylesheetPageState extends State<StylesheetPage> { } return Container( - width: 160, // Fixed Width for Horizontal List - padding: const EdgeInsets.all(20), + width: 160, padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), + color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: Variables.borderSubtle), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.03), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], + boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 8, offset: const Offset(0, 2))], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 1. Big "Aa" Preview - Expanded( - child: Text( - "Aa", - style: sampleStyle.copyWith( - fontSize: 56, - height: 1, - fontWeight: FontWeight.w400, - color: Colors.black, - ), - ), - ), - // 2. Font Name - Text( - correctFontName, - style: const TextStyle( - fontFamily: 'GeneralSans', - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.black, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), + Expanded(child: Text("Aa", style: sampleStyle.copyWith(fontSize: 56, height: 1, fontWeight: FontWeight.w400, color: Colors.black))), + Text(correctFontName, style: const TextStyle(fontFamily: 'GeneralSans', fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black), maxLines: 1, overflow: TextOverflow.ellipsis), const SizedBox(height: 4), - // 3. Label - const Text( - "Primary Typeface", - style: TextStyle( - fontFamily: 'GeneralSans', - fontSize: 11, - color: Variables.textSecondary, - fontWeight: FontWeight.w500, - ), - ), + const Text("Primary Typeface", style: TextStyle(fontFamily: 'GeneralSans', fontSize: 11, color: Variables.textSecondary, fontWeight: FontWeight.w500)), ], ), ); @@ -619,44 +437,24 @@ class _StylesheetPageState extends State<StylesheetPage> { List<Map<String, dynamic>> palette = []; if (data is List) { for (var item in data) { - if (item is Map) { - palette.add({ - 'label': item['label']?.toString() ?? '', - 'score': item['score'] ?? 0, - }); - } + if (item is Map) palette.add({'label': item['label']?.toString() ?? '', 'score': item['score'] ?? 0}); } } - if (palette.isEmpty) return const SizedBox(); return GridView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 12, - mainAxisSpacing: 12, - childAspectRatio: 0.8, - ), + shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 12, mainAxisSpacing: 12, childAspectRatio: 0.8), itemCount: palette.length, - itemBuilder: (context, index) { - return _buildColorCard(palette[index]['label']); - }, + itemBuilder: (context, index) => _buildColorCard(palette[index]['label']), ); } Widget _buildColorCard(String label) { Color color = _getColorFromLabel(label); - String hexCode = - "#${color.value.toRadixString(16).substring(2).toUpperCase()}"; - + String hexCode = "#${color.value.toRadixString(16).substring(2).toUpperCase()}"; return Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Variables.borderSubtle), - ), + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: Variables.borderSubtle)), clipBehavior: Clip.antiAlias, child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -670,26 +468,9 @@ class _StylesheetPageState extends State<StylesheetPage> { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - hexCode, - style: const TextStyle( - fontFamily: 'GeneralSans', - fontSize: 12, - fontWeight: FontWeight.bold, - color: Variables.textPrimary, - ), - ), + Text(hexCode, style: const TextStyle(fontFamily: 'GeneralSans', fontSize: 12, fontWeight: FontWeight.bold, color: Variables.textPrimary)), const SizedBox(height: 2), - Text( - label.toUpperCase(), - style: const TextStyle( - fontFamily: 'GeneralSans', - fontSize: 10, - color: Variables.textSecondary, - overflow: TextOverflow.ellipsis, - ), - maxLines: 1, - ), + Text(label.toUpperCase(), style: const TextStyle(fontFamily: 'GeneralSans', fontSize: 10, color: Variables.textSecondary, overflow: TextOverflow.ellipsis), maxLines: 1), ], ), ), @@ -701,7 +482,6 @@ class _StylesheetPageState extends State<StylesheetPage> { Widget _buildSliderSection(String title, dynamic data) { if (data is! List) return const SizedBox(); - List<Map> items = []; for (var i in data) { if (i is Map) items.add(i); @@ -711,10 +491,7 @@ class _StylesheetPageState extends State<StylesheetPage> { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: _buildSectionHeader(title), - ), + Padding(padding: const EdgeInsets.symmetric(horizontal: 20), child: _buildSectionHeader(title)), SizedBox( height: 120, child: ListView.separated( @@ -724,38 +501,15 @@ class _StylesheetPageState extends State<StylesheetPage> { separatorBuilder: (_, __) => const SizedBox(width: 12), itemBuilder: (context, index) { final item = items[index]; - final label = item['label']?.toString() ?? ''; - return Container( - width: 120, - height: 120, - padding: const EdgeInsets.all(12), + width: 120, height: 120, padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), + color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: Variables.borderSubtle), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.03), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], + boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 8, offset: const Offset(0, 2))], ), child: Center( - child: Text( - label.toUpperCase(), - textAlign: TextAlign.center, - style: const TextStyle( - fontFamily: 'GeneralSans', - fontSize: 13, - fontWeight: FontWeight.w600, - color: Variables.textPrimary, - height: 1.2, - ), - maxLines: 3, - overflow: TextOverflow.ellipsis, - ), + child: Text(item['label']?.toString().toUpperCase() ?? '', textAlign: TextAlign.center, style: const TextStyle(fontFamily: 'GeneralSans', fontSize: 13, fontWeight: FontWeight.w600, color: Variables.textPrimary, height: 1.2), maxLines: 3, overflow: TextOverflow.ellipsis), ), ); }, @@ -779,4 +533,4 @@ class _StylesheetPageState extends State<StylesheetPage> { if (label.contains('dark')) return const Color(0xFF1a1a1a); return Colors.grey.shade400; } -} +} +\ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml @@ -26,10 +26,10 @@ dependencies: google_mlkit_text_recognition: ^0.15.0 flutter_svg: ^2.2.3 google_fonts: ^6.1.0 + flutter_colorpicker: ^1.1.0 undo: ^1.0.1 flutter_box_transform: ^0.4.7 dotted_border: ^2.0.0 - flutter_colorpicker: ^1.1.0 share_plus: ^12.0.1 dev_dependencies: