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:
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: