commit 8c99ca2761b4b7abd7b84f826e242da9acd74ace
parent d854e4692eda0ca3fa70bbf835d157ebaa259e52
Author: aditya-samal <samaladitya2004@gmail.com>
Date: Sun, 30 Nov 2025 15:44:23 +0530
Home Screen Done
Diffstat:
2 files changed, 426 insertions(+), 254 deletions(-)
diff --git a/lib/data/repos/file_repo.dart b/lib/data/repos/file_repo.dart
@@ -69,4 +69,14 @@ class FileRepo {
);
return res.map((e) => e['file_path'] as String).toList();
}
+
+ Future<List<FileModel>> getRecentFiles({int limit = 10}) async {
+ final db = await AppDatabase.db;
+ final res = await db.query(
+ 'files',
+ orderBy: 'last_updated DESC',
+ limit: limit,
+ );
+ return res.map((e) => FileModel.fromMap(e)).toList();
+ }
}
diff --git a/lib/ui/pages/home_page.dart b/lib/ui/pages/home_page.dart
@@ -1,9 +1,13 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:adobe/data/models/project_model.dart';
+import 'package:adobe/data/models/file_model.dart';
import 'package:adobe/data/repos/project_repo.dart';
import 'package:adobe/data/repos/image_repo.dart';
+import 'package:adobe/data/repos/file_repo.dart';
import 'package:adobe/services/project_service.dart';
+import 'package:image/image.dart' as img;
+import 'package:intl/intl.dart';
import 'project_detail_page.dart';
import 'image_analysis_page.dart';
import 'define_brand_page.dart';
@@ -18,15 +22,16 @@ class HomePage extends StatefulWidget {
class _HomePageState extends State<HomePage> {
final _projectRepo = ProjectRepo();
final _imageRepo = ImageRepo();
+ final _fileRepo = FileRepo();
final _projectService = ProjectService();
- List<ProjectModel> _recentProjects = [];
List<ProjectModel> _allProjects = [];
- Map<int, List<String>> _projectPreviews =
- {}; // projectId -> list of image paths
+ List<FileModel> _recentFiles = [];
+ Map<int, ProjectModel> _projectMap = {};
+ Map<int, List<String>> _projectPreviews = {};
+ Map<String, String> _fileDimensions = {};
bool _isLoading = true;
- final String _userName =
- "User"; // Default name, can be loaded from preferences later
+ final String _userName = "Alex"; // Can be loaded from preferences later
@override
void initState() {
@@ -37,27 +42,39 @@ class _HomePageState extends State<HomePage> {
Future<void> _loadData() async {
setState(() => _isLoading = true);
try {
- // Load recent projects (limit to 10 for horizontal scroll)
- final recent = await _projectRepo.getRecentProjects(10);
-
- // Load all projects
- final all = await _projectRepo.getAllProjects();
-
- // Load preview images for each project
- final Map<int, List<String>> previews = {};
- for (final project in all) {
+ // 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!);
- previews[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 {
+ final dimensions = await _getImageDimensions(file.filePath);
+ fileDimensions[file.id] = dimensions;
+ } catch (e) {
+ debugPrint('Error loading dimensions for ${file.id}: $e');
+ }
+ }
+
if (mounted) {
setState(() {
- _recentProjects = recent;
- _allProjects = all;
- _projectPreviews = previews;
+ _allProjects = allProjects;
+ _recentFiles = recentFiles;
+ _projectMap = projectMap;
+ _fileDimensions = fileDimensions;
_isLoading = false;
});
}
@@ -67,6 +84,48 @@ class _HomePageState extends State<HomePage> {
}
}
+ Future<String> _getImageDimensions(String filePath) async {
+ try {
+ final bytes = await File(filePath).readAsBytes();
+ final image = img.decodeImage(bytes);
+ if (image != null) {
+ return '${image.width} x ${image.height} px';
+ }
+ } catch (e) {
+ debugPrint('Error getting dimensions: $e');
+ }
+ return 'Unknown';
+ }
+
+ String _formatTimeAgo(DateTime dateTime) {
+ final now = DateTime.now();
+ final difference = now.difference(dateTime);
+
+ if (difference.inDays > 0) {
+ return 'Edited ${difference.inDays} ${difference.inDays == 1 ? 'day' : 'days'} ago';
+ } else if (difference.inHours > 0) {
+ return 'Edited ${difference.inHours} ${difference.inHours == 1 ? 'hour' : 'hours'} ago';
+ } else if (difference.inMinutes > 0) {
+ return 'Edited ${difference.inMinutes} ${difference.inMinutes == 1 ? 'minute' : 'minutes'} ago';
+ } else {
+ return 'Edited just now';
+ }
+ }
+
+ 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) {
+ return '${parentProject.title} / ${project.title}';
+ }
+ }
+ return project.title;
+ }
+
Future<void> _createNewProject() async {
Navigator.push(
context,
@@ -97,6 +156,14 @@ 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);
+ }
+ }
+
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
@@ -115,51 +182,34 @@ class _HomePageState extends State<HomePage> {
// Header Section
SliverToBoxAdapter(
child: Padding(
- padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
+ padding: const EdgeInsets.fromLTRB(16, 20, 16, 16),
child: Row(
children: [
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- "Hi, $_userName",
- style: TextStyle(
- fontSize: 28,
- fontWeight: FontWeight.w600,
- fontFamily: 'GeneralSans',
- color: theme.colorScheme.onSurface,
- ),
- ),
- ],
- ),
- const Spacer(),
- // Image Analysis Button
- IconButton(
- onPressed: () {
- Navigator.push(
- context,
- MaterialPageRoute(
- builder: (_) => const ImageAnalysisPage(),
- ),
- );
- },
- icon: Icon(
- Icons.analytics_outlined,
+ Text(
+ "Hello, $_userName!",
+ style: TextStyle(
+ fontSize: 24,
+ fontWeight: FontWeight.w500,
+ fontFamily: 'GeneralSans',
color: theme.colorScheme.onSurface,
),
- tooltip: 'Image Analysis',
),
- const SizedBox(width: 8),
- // Profile Picture Placeholder
+ const Spacer(),
+ // Profile Picture
Container(
- width: 48,
- height: 48,
+ 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,
),
),
@@ -168,119 +218,108 @@ class _HomePageState extends State<HomePage> {
),
),
- // Recent Section
- if (_recentProjects.isNotEmpty) ...[
- SliverToBoxAdapter(
- child: Padding(
- padding: const EdgeInsets.fromLTRB(20, 8, 20, 12),
- child: Text(
- "Recent",
+ // Search Bar
+ SliverToBoxAdapter(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: Container(
+ decoration: BoxDecoration(
+ color: isDark ? Colors.grey[800] : Colors.grey[200],
+ borderRadius: BorderRadius.circular(8),
+ ),
+ child: TextField(
+ decoration: InputDecoration(
+ hintText: 'Search',
+ hintStyle: TextStyle(
+ fontSize: 12,
+ color: theme.colorScheme.onSurface.withOpacity(0.5),
+ fontFamily: 'GeneralSans',
+ ),
+ prefixIcon: Icon(
+ Icons.search,
+ size: 18,
+ color: theme.colorScheme.onSurface.withOpacity(0.5),
+ ),
+ border: InputBorder.none,
+ contentPadding: const EdgeInsets.symmetric(
+ horizontal: 16,
+ vertical: 12,
+ ),
+ ),
style: TextStyle(
- fontSize: 20,
- fontWeight: FontWeight.w600,
+ fontSize: 12,
fontFamily: 'GeneralSans',
color: theme.colorScheme.onSurface,
),
),
),
),
- SliverToBoxAdapter(
- child: SizedBox(
- height: 140,
- child: ListView.builder(
- scrollDirection: Axis.horizontal,
- padding: const EdgeInsets.symmetric(
- horizontal: 20,
- ),
- itemCount: _recentProjects.length,
- itemBuilder: (context, index) {
- final project = _recentProjects[index];
- return _buildRecentCard(project, theme, isDark);
- },
- ),
- ),
- ),
- const SliverToBoxAdapter(child: SizedBox(height: 24)),
- ],
+ ),
+
+ const SliverToBoxAdapter(child: SizedBox(height: 12)),
- // Your Project Section
+ // Content Sections
SliverToBoxAdapter(
child: Padding(
- padding: const EdgeInsets.fromLTRB(20, 8, 20, 16),
- child: Row(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Text(
- "Your Projects",
- style: TextStyle(
- fontSize: 20,
- fontWeight: FontWeight.w600,
- fontFamily: 'GeneralSans',
- color: theme.colorScheme.onSurface,
+ // Recent Files Section
+ if (_recentFiles.isNotEmpty) ...[
+ _buildSectionHeader(
+ 'Recent Files',
+ theme,
+ onTap: () {
+ // Navigate to recent files page
+ },
),
+ const SizedBox(height: 12),
+ ...(_recentFiles.take(2).map((file) => Padding(
+ padding: const EdgeInsets.only(bottom: 12),
+ child: _buildRecentFileCard(file, theme, isDark),
+ )).toList()),
+ const SizedBox(height: 24),
+ ],
+
+ // Projects Section
+ _buildSectionHeader(
+ 'Projects',
+ theme,
+ onTap: () {
+ // Navigate to all projects
+ },
+ ),
+ const SizedBox(height: 12),
+ SizedBox(
+ height: 100,
+ child: ListView.builder(
+ scrollDirection: Axis.horizontal,
+ itemCount: _allProjects.length,
+ itemBuilder: (context, index) {
+ final project = _allProjects[index];
+ return _buildProjectCard(project, theme, isDark);
+ },
+ ),
+ ),
+ 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),
],
),
),
),
- // Projects Grid
- if (_allProjects.isEmpty)
- SliverFillRemaining(
- child: Center(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Icon(
- Icons.folder_outlined,
- size: 64,
- color: theme.colorScheme.onSurface
- .withOpacity(0.3),
- ),
- const SizedBox(height: 16),
- Text(
- "No projects yet",
- style: TextStyle(
- fontSize: 18,
- color: theme.colorScheme.onSurface
- .withOpacity(0.6),
- fontFamily: 'GeneralSans',
- ),
- ),
- const SizedBox(height: 8),
- Text(
- "Tap the + button to create your first project",
- style: TextStyle(
- fontSize: 14,
- color: theme.colorScheme.onSurface
- .withOpacity(0.5),
- fontFamily: 'GeneralSans',
- ),
- ),
- ],
- ),
- ),
- )
- else
- SliverPadding(
- padding: const EdgeInsets.symmetric(horizontal: 20),
- sliver: SliverGrid(
- gridDelegate:
- const SliverGridDelegateWithFixedCrossAxisCount(
- crossAxisCount: 2,
- crossAxisSpacing: 16,
- mainAxisSpacing: 16,
- childAspectRatio: 0.85,
- ),
- delegate: SliverChildBuilderDelegate((
- context,
- index,
- ) {
- final project = _allProjects[index];
- return _buildProjectCard(project, theme, isDark);
- }, childCount: _allProjects.length),
- ),
- ),
-
// Bottom padding
const SliverToBoxAdapter(child: SizedBox(height: 100)),
],
@@ -289,85 +328,148 @@ class _HomePageState extends State<HomePage> {
),
floatingActionButton: FloatingActionButton(
onPressed: _createNewProject,
- backgroundColor: theme.colorScheme.primary,
- foregroundColor: theme.colorScheme.onPrimary,
- child: const Icon(Icons.add),
+ backgroundColor: isDark ? Colors.grey[900] : Colors.grey[900],
+ foregroundColor: Colors.white,
+ child: const Icon(Icons.add, size: 24),
),
);
}
- Widget _buildRecentCard(ProjectModel project, ThemeData theme, bool isDark) {
- final previewImages =
- project.id != null ? (_projectPreviews[project.id!] ?? []) : [];
+ Widget _buildSectionHeader(String title, ThemeData theme, {VoidCallback? onTap}) {
+ return Row(
+ children: [
+ Text(
+ title,
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ fontFamily: 'GeneralSans',
+ color: theme.colorScheme.onSurface,
+ ),
+ ),
+ const Spacer(),
+ if (onTap != null)
+ GestureDetector(
+ onTap: onTap,
+ child: Icon(
+ Icons.chevron_right,
+ size: 24,
+ color: theme.colorScheme.onSurface.withOpacity(0.6),
+ ),
+ ),
+ ],
+ );
+ }
+
+ Widget _buildRecentFileCard(FileModel file, ThemeData theme, bool isDark) {
+ final breadcrumb = _getProjectBreadcrumb(file);
+ final dimensions = _fileDimensions[file.id] ?? 'Unknown';
+ final timeAgo = _formatTimeAgo(file.lastUpdated);
return GestureDetector(
- onTap: () => _openProject(project),
+ onTap: () => _openFile(file),
child: Container(
- width: 200,
- margin: const EdgeInsets.only(right: 12),
decoration: BoxDecoration(
- color: isDark ? Colors.grey[800] : Colors.grey[100],
- borderRadius: BorderRadius.circular(16),
- border: Border.all(
- color: isDark ? Colors.grey[700]! : Colors.grey[300]!,
- width: 1,
- ),
+ color: theme.scaffoldBackgroundColor,
+ borderRadius: BorderRadius.circular(8),
),
- child: Column(
+ child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- // Preview Image
- Expanded(
+ // Thumbnail
+ Container(
+ width: 104,
+ height: 106,
+ decoration: BoxDecoration(
+ color: isDark ? Colors.grey[800] : Colors.grey[200],
+ borderRadius: BorderRadius.circular(8),
+ ),
child: ClipRRect(
- borderRadius: const BorderRadius.vertical(
- top: Radius.circular(16),
+ borderRadius: BorderRadius.circular(8),
+ child: Image.file(
+ File(file.filePath),
+ fit: BoxFit.cover,
+ errorBuilder: (context, error, stackTrace) => Container(
+ color: isDark ? Colors.grey[800] : Colors.grey[200],
+ child: Icon(
+ Icons.broken_image,
+ size: 24,
+ color: theme.colorScheme.onSurface.withOpacity(0.3),
+ ),
+ ),
),
- child:
- previewImages.isEmpty
- ? Container(
- color: isDark ? Colors.grey[700] : Colors.grey[200],
- child: Center(
- child: Icon(
- Icons.image_outlined,
- size: 40,
- color: theme.colorScheme.onSurface.withOpacity(
- 0.3,
- ),
- ),
- ),
- )
- : Image.file(
- File(previewImages.first),
- fit: BoxFit.cover,
- width: double.infinity,
- errorBuilder:
- (context, error, stackTrace) => Container(
- color:
- isDark
- ? Colors.grey[700]
- : Colors.grey[200],
- child: Icon(
- Icons.broken_image,
- color: theme.colorScheme.onSurface
- .withOpacity(0.3),
+ ),
+ ),
+ const SizedBox(width: 8),
+ // File Info
+ Expanded(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 8),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ if (breadcrumb.isNotEmpty)
+ Text(
+ breadcrumb,
+ style: TextStyle(
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ fontFamily: 'Inter',
+ color: theme.colorScheme.onSurface.withOpacity(0.6),
+ ),
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
),
+ const SizedBox(height: 4),
+ Text(
+ file.name,
+ style: TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.w500,
+ fontFamily: 'GeneralSans',
+ color: theme.colorScheme.onSurface,
+ ),
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
),
+ ],
+ ),
),
- ),
- ),
- // Project Title
- Padding(
- padding: const EdgeInsets.all(12),
- child: Text(
- project.title,
- style: TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.w600,
- fontFamily: 'GeneralSans',
- color: theme.colorScheme.onSurface,
+ const SizedBox(width: 3),
+ Icon(
+ Icons.more_vert,
+ size: 16,
+ color: theme.colorScheme.onSurface.withOpacity(0.6),
+ ),
+ ],
+ ),
+ const SizedBox(height: 8),
+ Text(
+ dimensions,
+ style: TextStyle(
+ fontSize: 10,
+ fontFamily: 'GeneralSans',
+ color: theme.colorScheme.onSurface.withOpacity(0.6),
+ ),
+ ),
+ const SizedBox(height: 2),
+ Text(
+ timeAgo,
+ style: TextStyle(
+ fontSize: 10,
+ fontFamily: 'GeneralSans',
+ color: theme.colorScheme.onSurface.withOpacity(0.4),
+ ),
+ ),
+ ],
),
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
),
),
],
@@ -383,9 +485,11 @@ class _HomePageState extends State<HomePage> {
return GestureDetector(
onTap: () => _openProject(project),
child: Container(
+ width: 156,
+ margin: const EdgeInsets.only(right: 8),
decoration: BoxDecoration(
- color: isDark ? Colors.grey[800] : Colors.grey[100],
- borderRadius: BorderRadius.circular(16),
+ color: theme.scaffoldBackgroundColor,
+ borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isDark ? Colors.grey[700]! : Colors.grey[300]!,
width: 1,
@@ -394,29 +498,27 @@ class _HomePageState extends State<HomePage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- // Preview Image(s)
+ // Preview Image
Expanded(
- flex: 3,
child: ClipRRect(
borderRadius: const BorderRadius.vertical(
- top: Radius.circular(16),
+ top: Radius.circular(8),
),
child:
previewImages.isEmpty
? Container(
- color: isDark ? Colors.grey[700] : Colors.grey[200],
+ color: isDark ? Colors.grey[800] : Colors.grey[200],
child: Center(
child: Icon(
Icons.folder_outlined,
- size: 48,
+ size: 32,
color: theme.colorScheme.onSurface.withOpacity(
0.3,
),
),
),
)
- : previewImages.length == 1
- ? Image.file(
+ : Image.file(
File(previewImages.first),
fit: BoxFit.cover,
width: double.infinity,
@@ -424,7 +526,7 @@ class _HomePageState extends State<HomePage> {
(context, error, stackTrace) => Container(
color:
isDark
- ? Colors.grey[700]
+ ? Colors.grey[800]
: Colors.grey[200],
child: Icon(
Icons.broken_image,
@@ -432,45 +534,34 @@ class _HomePageState extends State<HomePage> {
.withOpacity(0.3),
),
),
- )
- : GridView.builder(
- physics: const NeverScrollableScrollPhysics(),
- gridDelegate:
- const SliverGridDelegateWithFixedCrossAxisCount(
- crossAxisCount: 2,
- mainAxisSpacing: 2,
- crossAxisSpacing: 2,
- ),
- itemCount: previewImages.length.clamp(0, 4),
- itemBuilder: (context, index) {
- return Image.file(
- File(previewImages[index]),
- fit: BoxFit.cover,
- errorBuilder:
- (context, error, stackTrace) => Container(
- color:
- isDark
- ? Colors.grey[700]
- : Colors.grey[200],
- ),
- );
- },
),
),
),
- // Project Title
+ // Project Title and Actions
Padding(
- padding: const EdgeInsets.all(12),
- child: Text(
- project.title,
- style: TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.w600,
- fontFamily: 'GeneralSans',
- color: theme.colorScheme.onSurface,
- ),
- maxLines: 2,
- overflow: TextOverflow.ellipsis,
+ padding: const EdgeInsets.all(8),
+ child: Row(
+ children: [
+ Expanded(
+ child: Text(
+ project.title,
+ style: TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.w500,
+ fontFamily: 'GeneralSans',
+ color: theme.colorScheme.onSurface,
+ ),
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ const SizedBox(width: 8),
+ Icon(
+ Icons.more_vert,
+ size: 16,
+ color: theme.colorScheme.onSurface.withOpacity(0.6),
+ ),
+ ],
),
),
],
@@ -478,4 +569,74 @@ 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'},
+ {'title': 'Birthday Party', 'subtitle': 'Invitation'},
+ ];
+
+ return SizedBox(
+ height: 160,
+ child: ListView.builder(
+ scrollDirection: Axis.horizontal,
+ itemCount: templates.length,
+ itemBuilder: (context, index) {
+ final template = templates[index];
+ return Container(
+ width: 104,
+ margin: const EdgeInsets.only(right: 8),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Container(
+ width: 104,
+ height: 106,
+ decoration: BoxDecoration(
+ color: isDark ? Colors.grey[800] : Colors.grey[200],
+ borderRadius: BorderRadius.circular(8),
+ ),
+ child: Center(
+ child: Icon(
+ Icons.image_outlined,
+ size: 32,
+ color: theme.colorScheme.onSurface.withOpacity(0.3),
+ ),
+ ),
+ ),
+ const SizedBox(height: 4),
+ Text(
+ template['title']!,
+ style: TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.w500,
+ fontFamily: 'GeneralSans',
+ color: theme.colorScheme.onSurface,
+ height: 1.2,
+ ),
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ ),
+ const SizedBox(height: 2),
+ Text(
+ template['subtitle']!,
+ style: TextStyle(
+ fontSize: 12,
+ fontFamily: 'GeneralSans',
+ color: theme.colorScheme.onSurface.withOpacity(0.6),
+ height: 1.2,
+ ),
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ],
+ ),
+ );
+ },
+ ),
+ );
+ }
+}
+\ No newline at end of file