creek

The AI Image Editor of 2030
commit d175b99e260d085e66cab1a492ad5e43aaa9c8eb
parent 308917ae2f196caa7796b4d74a7ee3b237b6a366
Author: aditya-samal <samaladitya2004@gmail.com>
Date:   Mon,  1 Dec 2025 15:03:27 +0530

Fixed some screens

Diffstat:
Aassets/files_blob.png | 0
Aassets/moodboard_blob.png | 0
Aassets/stylesheet_blob.png | 0
Mlib/ui/pages/project_detail_page.dart | 503++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mpubspec.yaml | 3+++
5 files changed, 312 insertions(+), 194 deletions(-)

diff --git a/assets/files_blob.png b/assets/files_blob.png Binary files differ. diff --git a/assets/moodboard_blob.png b/assets/moodboard_blob.png Binary files differ. diff --git a/assets/stylesheet_blob.png b/assets/stylesheet_blob.png Binary files differ. diff --git a/lib/ui/pages/project_detail_page.dart b/lib/ui/pages/project_detail_page.dart @@ -1,6 +1,10 @@ +import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import '../../data/models/project_model.dart'; +import '../../data/models/image_model.dart'; import '../../data/repos/project_repo.dart'; +import '../../data/repos/image_repo.dart'; import '../../services/project_service.dart'; import 'project_board_page.dart'; import 'stylesheet_page.dart'; @@ -18,9 +22,11 @@ class ProjectDetailPage extends StatefulWidget { class _ProjectDetailPageState extends State<ProjectDetailPage> { final _projectRepo = ProjectRepo(); final _projectService = ProjectService(); + final _imageRepo = ImageRepo(); ProjectModel? _project; List<ProjectModel> _events = []; + Map<int, List<ImageModel>> _eventImages = {}; // eventId -> recent images bool _isLoading = true; @override @@ -48,10 +54,21 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> { // Fetch sub-events final events = await _projectRepo.getEvents(widget.projectId); + // Fetch recent images for each event (up to 3 most recent) + final Map<int, List<ImageModel>> eventImagesMap = {}; + for (final event in events) { + if (event.id != null) { + final images = await _imageRepo.getImages(event.id!); + // Get the 3 most recent images (already ordered by created_at DESC) + eventImagesMap[event.id!] = images.take(3).toList(); + } + } + if (mounted) { setState(() { _project = project; _events = events; + _eventImages = eventImagesMap; _isLoading = false; }); } @@ -178,8 +195,7 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> { if (_isLoading) { return Scaffold( - backgroundColor: theme.scaffoldBackgroundColor, - appBar: AppBar(title: const Text("Loading...")), + backgroundColor: const Color(0xFFFAFAFA), body: const Center(child: CircularProgressIndicator()), ); } @@ -187,79 +203,73 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> { if (_project == null) return const Scaffold(body: SizedBox()); return Scaffold( - backgroundColor: theme.scaffoldBackgroundColor, + backgroundColor: const Color(0xFFFAFAFA), appBar: AppBar( - title: Text(_project!.title), - backgroundColor: theme.appBarTheme.backgroundColor, + title: Text( + _project!.title, + style: const TextStyle( + fontFamily: 'GeneralSans', + fontSize: 20, + fontWeight: FontWeight.w500, + color: Color(0xFF27272A), + ), + ), + backgroundColor: const Color(0xFFFAFAFA), + elevation: 0, + actions: [ + IconButton( + icon: SvgPicture.asset( + 'assets/icons/settings-line.svg', + width: 24, + height: 24, + ), + onPressed: () {}, + ), + ], ), body: RefreshIndicator( onRefresh: _loadData, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.only(left: 16, right: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 1. Main Project Details & Actions - _buildSectionHeader("Project Overview", theme), - const SizedBox(height: 8), - if (_project!.description != null && - _project!.description!.isNotEmpty) - Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Text( - _project!.description!, - style: TextStyle( - fontSize: 14, - color: theme.colorScheme.onSurface.withValues(alpha: 0.7), - fontFamily: 'GeneralSans', - ), - ), - ), + const SizedBox(height: 16), + + // Action Cards Row + _buildActionCardsRow(_project!.id!), - // Actions for Main Project - _buildActionRow(_project!.id!, theme, isDark), + const SizedBox(height: 16), - const SizedBox(height: 32), + // Events Header + _buildEventsHeader(), - // 2. Events Header - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _buildSectionHeader("Events", theme), - IconButton( - onPressed: _createEventDialog, - icon: Icon( - Icons.add_circle, - color: theme.colorScheme.primary, - ), - tooltip: "Add Event", - ), - ], - ), - const SizedBox(height: 8), + const SizedBox(height: 16), - // 3. Events List + // Events List if (_events.isEmpty) - Container( - width: double.infinity, - padding: const EdgeInsets.all(32), - decoration: BoxDecoration( - color: isDark ? Colors.grey[850] : Colors.grey[100], - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: isDark ? Colors.grey[800]! : Colors.grey[300]!, - ), - ), - child: Column( - children: [ - Icon(Icons.event_note, size: 48, color: Colors.grey[400]), - const SizedBox(height: 8), - Text( - "No events created yet", - style: TextStyle(color: Colors.grey[500]), + Center( + child: Container( + width: 328, + padding: const EdgeInsets.all(32), + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: Colors.grey[300]!, ), - ], + ), + child: Column( + children: [ + Icon(Icons.event_note, size: 48, color: Colors.grey[400]), + const SizedBox(height: 8), + Text( + "No events created yet", + style: TextStyle(color: Colors.grey[500]), + ), + ], + ), ), ) else @@ -267,10 +277,12 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> { shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: _events.length, - separatorBuilder: (_, __) => const SizedBox(height: 12), + separatorBuilder: (_, __) => const SizedBox(height: 16), itemBuilder: (context, index) { final event = _events[index]; - return _buildEventCard(event, theme, isDark); + return Center( + child: _buildEventCard(event), + ); }, ), @@ -284,163 +296,266 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> { // --- Widgets --- - Widget _buildSectionHeader(String title, ThemeData theme) { - return Text( - title, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - fontFamily: 'GeneralSans', - color: theme.colorScheme.onSurface, - ), - ); - } - - Widget _buildEventCard(ProjectModel event, ThemeData theme, bool isDark) { - return Container( - decoration: BoxDecoration( - color: isDark ? Colors.grey[850] : Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: isDark ? Colors.grey[700]! : Colors.grey[300]!, - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.03), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon(Icons.event, size: 20, color: theme.colorScheme.primary), - const SizedBox(width: 8), - Expanded( - child: Text( - event.title, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - fontFamily: 'GeneralSans', - ), - ), - ), - ], - ), - if (event.description != null && event.description!.isNotEmpty) ...[ - const SizedBox(height: 6), - Text( - event.description!, - style: TextStyle( - fontSize: 13, - color: theme.colorScheme.onSurface.withValues(alpha: 0.6), - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ], - const SizedBox(height: 16), - // Actions for this specific Event - _buildActionRow(event.id!, theme, isDark, isSmall: true), - ], - ), - ); - } - - /// Reusable row of actions (Moodboard, Stylesheet, Files) - Widget _buildActionRow( - int targetId, - ThemeData theme, - bool isDark, { - bool isSmall = false, - }) { + Widget _buildActionCardsRow(int projectId) { return Row( children: [ Expanded( - child: _buildActionButton( - label: "Moodboard", - icon: Icons.dashboard_outlined, - color: Colors.purple, // Distinct color for main action - theme: theme, - isDark: isDark, - isSmall: isSmall, - onTap: () => _navigateToBoard(targetId), + child: _buildActionCard( + blobImage: 'assets/moodboard_blob.png', + iconPath: 'assets/icons/moodboard_icon.svg', + label: 'Moodboard', + onTap: () => _navigateToBoard(projectId), ), ), - const SizedBox(width: 8), + const SizedBox(width: 12), Expanded( - child: _buildActionButton( - label: "Stylesheet", - icon: Icons.palette_outlined, - color: Colors.blue, - theme: theme, - isDark: isDark, - isSmall: isSmall, - onTap: () => _navigateToStylesheet(targetId), + child: _buildActionCard( + blobImage: 'assets/stylesheet_blob.png', + iconPath: 'assets/icons/stylesheet_icon.svg', + label: 'Stylesheet', + onTap: () => _navigateToStylesheet(projectId), ), ), - const SizedBox(width: 8), + const SizedBox(width: 12), Expanded( - child: _buildActionButton( - label: "Files", - icon: Icons.folder_open_outlined, - color: Colors.orange, - theme: theme, - isDark: isDark, - isSmall: isSmall, - onTap: () => _navigateToFiles(targetId), + child: _buildActionCard( + blobImage: 'assets/files_blob.png', + iconPath: 'assets/icons/files_icon.svg', + label: 'Files', + onTap: () => _navigateToFiles(projectId), ), ), ], ); } - Widget _buildActionButton({ + Widget _buildActionCard({ + required String blobImage, + required String iconPath, required String label, - required IconData icon, - required Color color, - required ThemeData theme, - required bool isDark, required VoidCallback onTap, - bool isSmall = false, }) { - return Material( - color: isDark ? Colors.grey[800] : Colors.grey[100], - borderRadius: BorderRadius.circular(12), - child: InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(12), - child: Container( - padding: EdgeInsets.symmetric(vertical: isSmall ? 10 : 16), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: isDark ? Colors.grey[700]! : Colors.grey[300]!, + return GestureDetector( + onTap: onTap, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: const Color(0xFFE4E4E7), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Blob image container with dark background + Container( + height: 115, + decoration: const BoxDecoration( + color: Color(0xFF27272A), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + child: Stack( + children: [ + // Blob image + Positioned.fill( + child: ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + child: Image.asset( + blobImage, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Container( + color: const Color(0xFF27272A), + ); + }, + ), + ), + ), + // Gradient overlay + Positioned.fill( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Colors.white.withOpacity(0.6), + const Color(0xFF111EB6).withOpacity(0.82), + ], + ), + backgroundBlendMode: BlendMode.hue, + ), + ), + ), + ], + ), + ), + // Icon and label section + Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Icon in circular background + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: const Color(0xFFE0E7FF), + borderRadius: BorderRadius.circular(46), + ), + child: SvgPicture.asset( + iconPath, + width: 16, + height: 16, + colorFilter: const ColorFilter.mode( + Color(0xFF7C86FF), + BlendMode.srcIn, + ), + ), + ), + const SizedBox(height: 8), + // Label + Text( + label, + style: const TextStyle( + fontFamily: 'GeneralSans', + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF27272A), + letterSpacing: 0.4, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), ), + ], + ), + ), + ); + } + + Widget _buildEventsHeader() { + return Container( + padding: const EdgeInsets.only(bottom: 8, top: 16), + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: Color(0xFFE4E4E7), + width: 1, ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(icon, size: isSmall ? 20 : 24, color: color), - const SizedBox(height: 6), - Text( - label, - style: TextStyle( - fontSize: isSmall ? 11 : 13, - fontWeight: FontWeight.w500, - color: theme.colorScheme.onSurface, - fontFamily: 'GeneralSans', + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Text( + 'Events', + style: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 16, + fontWeight: FontWeight.w500, + color: Color(0xFF27272A), + ), + ), + Material( + color: Colors.transparent, + child: InkWell( + onTap: _createEventDialog, + borderRadius: BorderRadius.circular(20), + child: Container( + padding: const EdgeInsets.all(8), + child: SvgPicture.asset( + 'assets/icons/add-line.svg', + width: 24, + height: 24, + colorFilter: const ColorFilter.mode( + Color(0xFF27272A), + BlendMode.srcIn, + ), ), ), - ], + ), ), + ], + ), + ); + } + + Widget _buildEventCard(ProjectModel event) { + final eventImages = _eventImages[event.id] ?? []; + + return Container( + width: 328, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: const Color(0xFFE4E4E7), + width: 1, ), ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Three images side by side (or fewer if not available) + Container( + height: 115, + padding: const EdgeInsets.all(4), + child: Row( + children: [ + // Show up to 3 images + for (int i = 0; i < 3; i++) + Expanded( + child: Padding( + padding: EdgeInsets.only( + right: i < 2 ? 4 : 0, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(4), + child: i < eventImages.length + ? Image.file( + File(eventImages[i].filePath), + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Container( + color: Colors.grey[300], + ); + }, + ) + : Container( + color: Colors.grey[200], + ), + ), + ), + ), + ], + ), + ), + // Event title + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + child: Text( + event.title, + style: const TextStyle( + fontFamily: 'GeneralSans', + fontSize: 14, + fontWeight: FontWeight.w500, + color: Color(0xFF27272A), + letterSpacing: 0.25, + ), + ), + ), + ], + ), ); } } diff --git a/pubspec.yaml b/pubspec.yaml @@ -44,6 +44,9 @@ flutter: assets: - assets/fonts/ - assets/icons/ + - assets/moodboard_blob.png + - assets/stylesheet_blob.png + - assets/files_blob.png - assets/dinov2.onnx - assets/dinov2.onnx.data - assets/texture_centroids.json