commit d175b99e260d085e66cab1a492ad5e43aaa9c8eb
parent 308917ae2f196caa7796b4d74a7ee3b237b6a366
Author: aditya-samal <samaladitya2004@gmail.com>
Date: Mon, 1 Dec 2025 15:03:27 +0530
Fixed some screens
Diffstat:
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