creek

The AI Image Editor of 2030
commit 8adada1000fb2a389a32ef7cd473df5c8dfaa2c2
parent f2fdfa1b34ded69320e81c20521382c253d3838e
Author: Abhinav Rai <69450646+AbhinavRai01@users.noreply.github.com>
Date:   Sat, 29 Nov 2025 15:03:40 +0530

Merge pull request #19 from nilotpal-n7/abhinav

ui fixes
image clicking now works
header fixed

Diffstat:
Mlib/ui/pages/project_board_page.dart | 430++++++++++++++++++++++++++++++++++++-------------------------------------------
Mlib/ui/pages/project_board_page_alternate.dart | 79++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mlib/ui/pages/project_tag_page.dart | 193+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mlib/ui/widgets/top_bar.dart | 201++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
4 files changed, 477 insertions(+), 426 deletions(-)

diff --git a/lib/ui/pages/project_board_page.dart b/lib/ui/pages/project_board_page.dart @@ -1,13 +1,17 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:adobe/ui/styles/variables.dart'; +import 'package:adobe/ui/widgets/top_bar.dart'; +import 'package:adobe/ui/widgets/bottom_bar.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 'project_tag_page.dart'; import 'project_board_page_alternate.dart'; -import 'image_save_page.dart'; // Import the Save Page +import 'image_save_page.dart'; +import 'image_details_page.dart'; class ProjectBoardPage extends StatefulWidget { final int projectId; @@ -25,13 +29,9 @@ class _ProjectBoardPageState extends State<ProjectBoardPage> { final GlobalKey<ProjectBoardPageAlternateState> _alternatePageKey = GlobalKey(); - ProjectModel? _mainProject; - List<ProjectModel> _events = []; - ProjectModel? _selectedProject; - + ProjectModel? _currentProject; Map<String, List<ImageModel>> _categorizedImages = {}; bool _isLoading = true; - bool _showAlternateView = false; @override @@ -42,31 +42,28 @@ class _ProjectBoardPageState extends State<ProjectBoardPage> { Future<void> _initData() async { try { - final mainProject = await _projectRepo.getProjectById(widget.projectId); - final events = await _projectRepo.getEvents(widget.projectId); - - if (mainProject != null) { - _mainProject = mainProject; - _events = events; - _selectedProject = _mainProject; - await _loadImagesForSelected(); - } else { - if (mounted) Navigator.pop(context); + final project = await _projectRepo.getProjectById(widget.projectId); + if (project != null) { + if (mounted) { + setState(() { + _currentProject = project; + }); + await _loadImagesForSelected(); + } } } catch (e) { - debugPrint("Error loading board data: $e"); - setState(() => _isLoading = false); + debugPrint("Error loading board: $e"); + if (mounted) setState(() => _isLoading = false); } } Future<void> _loadImagesForSelected() async { - if (_selectedProject?.id == null) return; - + if (_currentProject?.id == null) return; if (!_showAlternateView) { setState(() => _isLoading = true); - final images = await _imageRepo.getImages(_selectedProject!.id!); + final images = await _imageRepo.getImages(_currentProject!.id!); _categorizeImages(images); - setState(() => _isLoading = false); + if (mounted) setState(() => _isLoading = false); } } @@ -89,35 +86,39 @@ class _ProjectBoardPageState extends State<ProjectBoardPage> { } } - void _onProjectChanged(ProjectModel? newValue) { - if (newValue != null && newValue != _selectedProject) { + void _onProjectChanged(ProjectModel newProject) { + if (newProject.id != _currentProject?.id) { setState(() { - _selectedProject = newValue; + _currentProject = newProject; }); - if (!_showAlternateView) _loadImagesForSelected(); + if (!_showAlternateView) { + _loadImagesForSelected(); + } else { + WidgetsBinding.instance.addPostFrameCallback((_) { + _alternatePageKey.currentState?.refreshData(); + }); + } } } - // --- Image Picker Logic --- Future<void> _pickAndRedirect() async { try { - final XFile? pickedFile = await _picker.pickImage(source: ImageSource.gallery); - if (pickedFile != null && _selectedProject != null) { + final List<XFile> pickedFiles = await _picker.pickMultiImage(); + + if (pickedFiles.isNotEmpty && _currentProject != null) { if (!mounted) return; - // Navigate to ImageSavePage Navigator.push( context, MaterialPageRoute( builder: (_) => ImageSavePage( - imagePaths: [pickedFile.path], - projectId: _selectedProject!.id!, - projectName: _selectedProject!.title, + imagePaths: pickedFiles.map((e) => e.path).toList(), + projectId: _currentProject!.id!, + projectName: _currentProject!.title, isFromShare: false, ), ), ).then((_) { - // Refresh data upon return if (!_showAlternateView) { _loadImagesForSelected(); } else { @@ -126,286 +127,251 @@ class _ProjectBoardPageState extends State<ProjectBoardPage> { }); } } catch (e) { - debugPrint("Error picking image: $e"); - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Error picking image: $e")), - ); - } + debugPrint("Error picking images: $e"); } } @override Widget build(BuildContext context) { - final theme = Theme.of(context); - final isDark = theme.brightness == Brightness.dark; - final buttonColor = isDark ? Colors.white.withOpacity(0.1) : Colors.grey[100]; - - final List<DropdownMenuItem<ProjectModel>> dropdownItems = []; - if (_mainProject != null) { - dropdownItems.add( - DropdownMenuItem( - value: _mainProject, - child: Text( - _mainProject!.title, - style: const TextStyle(fontFamily: 'GeneralSans'), - ), - ), - ); - } - for (var event in _events) { - dropdownItems.add( - DropdownMenuItem( - value: event, - child: Text( - event.title, - style: const TextStyle(fontFamily: 'GeneralSans'), - ), - ), + if (_currentProject == null) { + return const Scaffold( + backgroundColor: Variables.background, + body: Center(child: CircularProgressIndicator()), ); } return Scaffold( - backgroundColor: theme.scaffoldBackgroundColor, - appBar: AppBar( - backgroundColor: theme.appBarTheme.backgroundColor, - centerTitle: false, - title: Text( - _selectedProject?.title ?? "Loading...", - style: TextStyle( - fontFamily: 'GeneralSans', - fontWeight: FontWeight.w600, - color: theme.colorScheme.onSurface, - ), - ), - actions: [ - IconButton( - icon: Icon(Icons.settings_outlined, color: theme.iconTheme.color), - onPressed: () {}, - ), - const SizedBox(width: 8), - ], + backgroundColor: Variables.background, + appBar: TopBar( + currentProjectId: _currentProject!.id!, + onBack: () => Navigator.pop(context), + onProjectChanged: _onProjectChanged, + onSettingsPressed: () {}, + ), + bottomNavigationBar: BottomBar( + currentTab: BottomBarItem.moodboard, + projectId: _currentProject!.id!, ), - // --- FAB for Adding Image --- floatingActionButton: FloatingActionButton( onPressed: _pickAndRedirect, - backgroundColor: isDark ? Colors.white : Colors.black, - foregroundColor: isDark ? Colors.black : Colors.white, + backgroundColor: Variables.textPrimary, + foregroundColor: Variables.background, child: const Icon(Icons.add_photo_alternate_outlined), ), body: Column( children: [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), child: Row( children: [ - Expanded( + InkWell( + onTap: () { + setState(() { + _showAlternateView = !_showAlternateView; + if (!_showAlternateView) _loadImagesForSelected(); + }); + }, + borderRadius: BorderRadius.circular(20), child: Container( - height: 40, - padding: const EdgeInsets.symmetric(horizontal: 12), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( - color: buttonColor, - borderRadius: BorderRadius.circular(10), + color: Variables.surfaceSubtle, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: Variables.borderSubtle), ), - child: DropdownButtonHideUnderline( - child: DropdownButton<ProjectModel>( - value: _selectedProject, - isExpanded: true, - items: dropdownItems, - onChanged: _onProjectChanged, - style: TextStyle( - color: theme.colorScheme.onSurface, - fontFamily: 'GeneralSans', - fontWeight: FontWeight.w500, - fontSize: 14, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + _showAlternateView ? Icons.dashboard : Icons.view_agenda_outlined, + size: 18, + color: Variables.textPrimary, ), - icon: Icon(Icons.keyboard_arrow_down, size: 18, color: theme.iconTheme.color), - dropdownColor: theme.cardColor, - ), + const SizedBox(width: 8), + Text( + _showAlternateView ? "Categorized" : "All Images", + style: Variables.bodyStyle.copyWith( + fontWeight: FontWeight.w600, + fontSize: 13, + ), + ), + ], ), ), ), - const SizedBox(width: 8), - _buildControlIcon( - theme, buttonColor, Icons.palette_outlined, "Stylesheet", - () => ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Stylesheet"))) - ), - const SizedBox(width: 8), - _buildControlIcon( - theme, buttonColor, Icons.tune_outlined, "Filter", - () { - if (_showAlternateView) { + const Spacer(), + if (_showAlternateView) + InkWell( + onTap: () { _alternatePageKey.currentState?.showFilterDialog(); - } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("Filter for categories not implemented")), - ); - } - } - ), - const SizedBox(width: 8), - - _buildControlIcon( - theme, - _showAlternateView ? Colors.black : buttonColor, - _showAlternateView ? Icons.dashboard : Icons.view_agenda_outlined, - "Switch View", - () { - setState(() { - _showAlternateView = !_showAlternateView; - if (!_showAlternateView) _loadImagesForSelected(); - }); - }, - iconColor: _showAlternateView ? Colors.white : theme.iconTheme.color, - ), + }, + borderRadius: BorderRadius.circular(20), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: Variables.surfaceSubtle, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: Variables.borderSubtle), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.tune_outlined, + size: 18, + color: Variables.textPrimary, + ), + const SizedBox(width: 8), + Text( + "Filter", + style: Variables.bodyStyle.copyWith( + fontWeight: FontWeight.w600, + fontSize: 13, + ), + ), + ], + ), + ), + ), ], ), ), - Expanded( child: _showAlternateView ? ProjectBoardPageAlternate( key: _alternatePageKey, - projectId: _selectedProject?.id ?? widget.projectId, + projectId: _currentProject!.id!, ) - : _buildCategorizedView(theme, isDark), + : _buildCategorizedView(), ), ], ), ); } - Widget _buildCategorizedView(ThemeData theme, bool isDark) { + Widget _buildCategorizedView() { if (_isLoading) return const Center(child: CircularProgressIndicator()); if (_categorizedImages.isEmpty) { return Center( child: Text( "No images found", - style: TextStyle( - fontFamily: 'GeneralSans', - color: theme.colorScheme.onSurface.withOpacity(0.6), - ), + style: Variables.bodyStyle.copyWith(color: Variables.textSecondary), ), ); } return ListView.builder( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + padding: const EdgeInsets.fromLTRB(16, 0, 16, 80), itemCount: _categorizedImages.keys.length, itemBuilder: (context, index) { final category = _categorizedImages.keys.elementAt(index); final images = _categorizedImages[category]!; - return GestureDetector( - onTap: () { - if (_selectedProject?.id != null) { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => ProjectTagPage( - projectId: _selectedProject!.id!, - tag: category, - ), - ), - ); - } - }, - child: Container( - margin: const EdgeInsets.only(bottom: 16), - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration( - color: isDark ? const Color(0xFF1E1E1E) : Colors.white, - border: Border.all(color: Colors.grey.withOpacity(0.3)), - borderRadius: BorderRadius.circular(16), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( + return Container( + margin: const EdgeInsets.only(bottom: 16), + // Keep this clipping so the scrolling list cuts off cleanly at the border + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Variables.borderSubtle), + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InkWell( + onTap: () { + if (_currentProject?.id != null) { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ProjectTagPage( + projectId: _currentProject!.id!, + tag: category, + ), + ), + ); + } + }, + child: Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( category.toUpperCase(), - style: TextStyle( + style: Variables.headerStyle.copyWith( fontSize: 14, - fontWeight: FontWeight.bold, letterSpacing: 1.0, - fontFamily: 'GeneralSans', - color: theme.colorScheme.onSurface, ), ), - Icon( + const Icon( Icons.arrow_forward, size: 16, - color: theme.colorScheme.onSurface.withOpacity(0.4), + color: Variables.textSecondary, ), ], ), ), - SizedBox( - height: 140, - child: ListView.separated( - clipBehavior: Clip.none, - padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), - scrollDirection: Axis.horizontal, - itemCount: images.length, - separatorBuilder: (_, __) => const SizedBox(width: 8), - itemBuilder: (context, imgIndex) { - final image = images[imgIndex]; - return Container( + ), + SizedBox( + height: 140, + child: ListView.separated( + clipBehavior: Clip.none, + padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + scrollDirection: Axis.horizontal, + itemCount: images.length, + separatorBuilder: (_, __) => const SizedBox(width: 8), + itemBuilder: (context, imgIndex) { + final image = images[imgIndex]; + return GestureDetector( + onTap: () { + if (_currentProject?.id != null) { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ImageDetailsPage( + imagePath: image.filePath, + imageId: image.id, + projectId: _currentProject!.id!, + ), + ), + ).then((_) => _loadImagesForSelected()); + } + }, + child: Container( width: 120, - clipBehavior: Clip.antiAlias, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), - color: isDark ? Colors.black26 : Colors.grey[200], - border: Border.all(color: theme.dividerColor.withOpacity(0.1)), + color: Variables.surfaceSubtle, + border: Border.all(color: Variables.borderSubtle), ), - child: Stack( - fit: StackFit.expand, - children: [ - Image.file( - File(image.filePath), - fit: BoxFit.cover, - errorBuilder: (_, __, ___) => Container( - color: Colors.grey[300], - child: const Icon(Icons.broken_image, color: Colors.grey), + // FIX: Explicitly clip image to border radius + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Stack( + fit: StackFit.expand, + children: [ + Image.file( + File(image.filePath), + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => Container( + color: Variables.surfaceSubtle, + child: const Icon(Icons.broken_image, color: Variables.textDisabled), + ), ), - ), - ], + ], + ), ), - ); - }, - ), + ), + ); + }, ), - ], - ), + ), + ], ), ); }, ); } - - Widget _buildControlIcon( - ThemeData theme, - Color? bgColor, - IconData icon, - String tooltip, - VoidCallback onTap, - {Color? iconColor} - ) { - return GestureDetector( - onTap: onTap, - child: Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: bgColor, - borderRadius: BorderRadius.circular(10), - ), - child: Icon(icon, size: 20, color: iconColor ?? theme.iconTheme.color), - ), - ); - } } \ No newline at end of file diff --git a/lib/ui/pages/project_board_page_alternate.dart b/lib/ui/pages/project_board_page_alternate.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:adobe/ui/styles/variables.dart'; import '../../data/models/image_model.dart'; import '../../data/repos/image_repo.dart'; import 'image_details_page.dart'; @@ -29,6 +30,15 @@ class ProjectBoardPageAlternateState extends State<ProjectBoardPageAlternate> { _loadData(); } + // Called when project changes in parent + @override + void didUpdateWidget(covariant ProjectBoardPageAlternate oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.projectId != widget.projectId) { + _loadData(); + } + } + void refreshData() { _loadData(); } @@ -75,9 +85,9 @@ class ProjectBoardPageAlternateState extends State<ProjectBoardPageAlternate> { builder: (context, setModalState) { return Container( height: MediaQuery.of(context).size.height * 0.6, - decoration: BoxDecoration( - color: Theme.of(context).scaffoldBackgroundColor, - borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), padding: const EdgeInsets.all(24), child: Column( @@ -86,9 +96,9 @@ class ProjectBoardPageAlternateState extends State<ProjectBoardPageAlternate> { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text( + Text( "Filter by Tags", - style: TextStyle(fontFamily: 'GeneralSans', fontSize: 20, fontWeight: FontWeight.bold), + style: Variables.headerStyle.copyWith(fontSize: 20), ), IconButton( icon: const Icon(Icons.close), @@ -98,7 +108,7 @@ class ProjectBoardPageAlternateState extends State<ProjectBoardPageAlternate> { ), const SizedBox(height: 20), if (_allTags.isEmpty) - const Text("No tags available."), + Text("No tags available.", style: Variables.bodyStyle), Expanded( child: SingleChildScrollView( @@ -118,18 +128,17 @@ class ProjectBoardPageAlternateState extends State<ProjectBoardPageAlternate> { _selectedTags.remove(tag); } }); + // Update main state this.setState(() { _applyFilter(); }); }, - labelStyle: TextStyle( - fontFamily: 'GeneralSans', - color: isSelected ? Colors.white : null, - fontSize: 12, - fontWeight: FontWeight.w500, + labelStyle: Variables.captionStyle.copyWith( + color: isSelected ? Colors.white : Variables.textPrimary, + fontWeight: FontWeight.w600, ), - backgroundColor: Colors.grey[200], - selectedColor: Colors.black87, + backgroundColor: Variables.surfaceSubtle, + selectedColor: Variables.textPrimary, checkmarkColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), @@ -159,7 +168,7 @@ class ProjectBoardPageAlternateState extends State<ProjectBoardPageAlternate> { child: ElevatedButton( onPressed: () => Navigator.pop(context), style: ElevatedButton.styleFrom( - backgroundColor: Colors.black, + backgroundColor: Variables.textPrimary, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), @@ -195,18 +204,17 @@ class ProjectBoardPageAlternateState extends State<ProjectBoardPageAlternate> { return Column( children: [ + // Context Info Row (Image Count + Active Filters) Padding( - padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), + padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), child: Row( children: [ Text( "ALL IMAGES (${_filteredImages.length})", - style: TextStyle( - fontFamily: 'GeneralSans', + style: Variables.captionStyle.copyWith( fontWeight: FontWeight.bold, - fontSize: 12, letterSpacing: 1.2, - color: Colors.grey[600], + color: Variables.textSecondary, ), ), const Spacer(), @@ -214,22 +222,29 @@ class ProjectBoardPageAlternateState extends State<ProjectBoardPageAlternate> { Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: Colors.black, + color: Variables.textPrimary, borderRadius: BorderRadius.circular(12), ), child: Text( - "${_selectedTags.length} Filters", - style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold), + "${_selectedTags.length} Active", + style: Variables.captionStyle.copyWith(color: Colors.white, fontWeight: FontWeight.bold), ), ) ], ), ), + + // Masonry Grid Expanded( child: _filteredImages.isEmpty - ? Center(child: Text("No images found", style: TextStyle(color: Colors.grey[500]))) + ? Center( + child: Text( + "No images found", + style: Variables.bodyStyle.copyWith(color: Variables.textSecondary), + ), + ) : SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.fromLTRB(16, 0, 16, 80), // Bottom padding for FAB child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -266,7 +281,7 @@ class ProjectBoardPageAlternateState extends State<ProjectBoardPageAlternate> { height: (index % 3 == 0) ? 240 : 180, decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), - color: Colors.grey[200], + color: Variables.surfaceSubtle, ), clipBehavior: Clip.antiAlias, child: Stack( @@ -276,7 +291,9 @@ class ProjectBoardPageAlternateState extends State<ProjectBoardPageAlternate> { File(image.filePath), fit: BoxFit.cover, width: double.infinity, - errorBuilder: (_,__,___) => const Center(child: Icon(Icons.broken_image, color: Colors.grey)), + errorBuilder: (_,__,___) => const Center( + child: Icon(Icons.broken_image, color: Variables.textDisabled), + ), ), if (image.tags.isNotEmpty) Positioned( @@ -300,7 +317,15 @@ class ProjectBoardPageAlternateState extends State<ProjectBoardPageAlternate> { borderRadius: BorderRadius.circular(4), border: Border.all(color: Colors.white.withOpacity(0.1)), ), - child: Text(tag.toUpperCase(), style: const TextStyle(color: Colors.white, fontSize: 9, fontFamily: 'GeneralSans', fontWeight: FontWeight.w600)), + child: Text( + tag.toUpperCase(), + style: const TextStyle( + color: Colors.white, + fontSize: 9, + fontFamily: 'GeneralSans', + fontWeight: FontWeight.w600 + ), + ), ); }).toList(), ), diff --git a/lib/ui/pages/project_tag_page.dart b/lib/ui/pages/project_tag_page.dart @@ -1,10 +1,14 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:adobe/ui/styles/variables.dart'; +import 'package:adobe/ui/widgets/top_bar.dart'; +import 'package:adobe/ui/widgets/bottom_bar.dart'; import '../../data/models/image_model.dart'; import '../../data/repos/image_repo.dart'; -import '../../data/repos/project_repo.dart'; // Added to fetch project name -import 'image_save_page.dart'; // Import Save Page +import '../../data/repos/project_repo.dart'; +import 'image_save_page.dart'; +import 'image_details_page.dart'; class ProjectTagPage extends StatefulWidget { final int projectId; @@ -26,7 +30,7 @@ class _ProjectTagPageState extends State<ProjectTagPage> { final ImagePicker _picker = ImagePicker(); List<ImageModel> _images = []; - String _projectName = "Project"; // Default + String _projectName = "Project"; bool _isLoading = true; @override @@ -36,7 +40,9 @@ class _ProjectTagPageState extends State<ProjectTagPage> { } Future<void> _loadData() async { - // 1. Fetch Images + final project = await _projectRepo.getProjectById(widget.projectId); + if (project != null) _projectName = project.title; + final allImages = await _imageRepo.getImages(widget.projectId); final filtered = allImages.where((img) { if (widget.tag == 'Uncategorized') { @@ -45,13 +51,9 @@ class _ProjectTagPageState extends State<ProjectTagPage> { return img.tags.contains(widget.tag); }).toList(); - // 2. Fetch Project Name (for the Save Page) - final project = await _projectRepo.getProjectById(widget.projectId); - if (mounted) { setState(() { _images = filtered; - if (project != null) _projectName = project.title; _isLoading = false; }); } @@ -59,52 +61,62 @@ class _ProjectTagPageState extends State<ProjectTagPage> { Future<void> _pickAndRedirect() async { try { - final XFile? pickedFile = await _picker.pickImage(source: ImageSource.gallery); - if (pickedFile != null) { + final List<XFile> pickedFiles = await _picker.pickMultiImage(); + if (pickedFiles.isNotEmpty) { if (!mounted) return; Navigator.push( context, MaterialPageRoute( builder: (_) => ImageSavePage( - imagePaths: [pickedFile.path], + imagePaths: pickedFiles.map((e) => e.path).toList(), projectId: widget.projectId, projectName: _projectName, isFromShare: false, ), ), - ).then((_) => _loadData()); // Refresh upon return + ).then((_) => _loadData()); } } catch (e) { - debugPrint("Error picking image: $e"); + debugPrint("Error picking images: $e"); } } @override Widget build(BuildContext context) { - final theme = Theme.of(context); - final isDark = theme.brightness == Brightness.dark; + // 1. Prepare Columns for Masonry Layout + final leftColumn = <Widget>[]; + final rightColumn = <Widget>[]; + + for (int i = 0; i < _images.length; i++) { + final item = _buildStaggeredImageItem(_images[i], index: i); + if (i % 2 == 0) { + leftColumn.add(item); + } else { + rightColumn.add(item); + } + } return Scaffold( - backgroundColor: theme.scaffoldBackgroundColor, - appBar: AppBar( - title: Text( - widget.tag.toUpperCase(), - style: const TextStyle( - fontFamily: 'GeneralSans', - fontWeight: FontWeight.w600, - letterSpacing: 1.0, - ), - ), - centerTitle: false, - backgroundColor: theme.appBarTheme.backgroundColor, - elevation: 0, + backgroundColor: Variables.background, + + appBar: TopBar( + currentProjectId: widget.projectId, + titleOverride: widget.tag.toUpperCase(), + onBack: () => Navigator.pop(context), ), + + bottomNavigationBar: BottomBar( + currentTab: BottomBarItem.moodboard, + projectId: widget.projectId, + ), + floatingActionButton: FloatingActionButton( onPressed: _pickAndRedirect, - backgroundColor: isDark ? Colors.white : Colors.black, - foregroundColor: isDark ? Colors.black : Colors.white, + backgroundColor: Variables.textPrimary, + foregroundColor: Variables.background, child: const Icon(Icons.add_photo_alternate_outlined), ), + body: _isLoading ? const Center(child: CircularProgressIndicator()) : _images.isEmpty @@ -112,64 +124,91 @@ class _ProjectTagPageState extends State<ProjectTagPage> { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.image_not_supported_outlined, - size: 64, color: Colors.grey[400]), + const Icon(Icons.image_not_supported_outlined, + size: 64, color: Variables.textDisabled), const SizedBox(height: 16), Text( "No images found for '${widget.tag}'", - style: TextStyle( - fontFamily: 'GeneralSans', - fontSize: 16, - color: Colors.grey[600], - ), + style: Variables.bodyStyle.copyWith(color: Variables.textSecondary), ), ], ), ) - : GridView.builder( - padding: const EdgeInsets.all(16), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - crossAxisSpacing: 12, - mainAxisSpacing: 12, - childAspectRatio: 0.8, - ), - itemCount: _images.length, - itemBuilder: (context, index) { - final image = _images[index]; - return GestureDetector( - onTap: () { - // Details logic - }, - child: Container( - decoration: BoxDecoration( - color: isDark ? const Color(0xFF1E1E1E) : Colors.grey[200], - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: isDark ? Colors.white10 : Colors.transparent, - ), + : SingleChildScrollView( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 80), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + children: leftColumn + .map((e) => Padding( + padding: const EdgeInsets.only(bottom: 12), + child: e, + )) + .toList(), ), - clipBehavior: Clip.antiAlias, - child: Stack( - fit: StackFit.expand, - children: [ - Image.file( - File(image.filePath), - fit: BoxFit.cover, - width: double.infinity, - errorBuilder: (_, __, ___) => Container( - color: Colors.grey[300], - child: const Center( - child: Icon(Icons.broken_image, color: Colors.grey), - ), - ), - ), - ], + ), + const SizedBox(width: 12), + Expanded( + child: Column( + children: rightColumn + .map((e) => Padding( + padding: const EdgeInsets.only(bottom: 12), + child: e, + )) + .toList(), ), ), - ); - }, + ], + ), + ), + ); + } + + Widget _buildStaggeredImageItem(ImageModel image, {required int index}) { + return GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ImageDetailsPage( + imagePath: image.filePath, + imageId: image.id, + projectId: widget.projectId, + ), + ), + ).then((_) => _loadData()); + }, + child: Container( + // Simulate staggered heights similar to Alternate Page + height: (index % 3 == 0) ? 240 : 180, + decoration: BoxDecoration( + color: Variables.surfaceSubtle, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Variables.borderSubtle), + ), + // Explicitly clip content to border radius + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: Stack( + fit: StackFit.expand, + children: [ + Image.file( + File(image.filePath), + fit: BoxFit.cover, + width: double.infinity, + errorBuilder: (_, __, ___) => Container( + color: Variables.surfaceSubtle, + child: const Center( + child: Icon(Icons.broken_image, color: Variables.textDisabled), + ), ), + ), + ], + ), + ), + ), ); } } \ No newline at end of file diff --git a/lib/ui/widgets/top_bar.dart b/lib/ui/widgets/top_bar.dart @@ -1,13 +1,14 @@ import 'package:flutter/material.dart'; -import 'package:adobe/data/models/project_model.dart'; -import 'package:adobe/data/repos/project_repo.dart'; -import 'package:adobe/ui/styles/variables.dart'; +import '../../data/models/project_model.dart'; // Adjust path as needed +import '../../data/repos/project_repo.dart'; // Adjust path as needed +import '../styles/variables.dart'; // Adjust path as needed class TopBar extends StatefulWidget implements PreferredSizeWidget { final int currentProjectId; final VoidCallback? onBack; final Function(ProjectModel)? onProjectChanged; final VoidCallback? onSettingsPressed; + final String? titleOverride; // New: For Tag Page title const TopBar({ super.key, @@ -15,13 +16,15 @@ class TopBar extends StatefulWidget implements PreferredSizeWidget { this.onBack, this.onProjectChanged, this.onSettingsPressed, + this.titleOverride, }); @override State<TopBar> createState() => _TopBarState(); + // FIX: Reduced from 80 to 56 (Standard Toolbar Height) @override - Size get preferredSize => const Size.fromHeight(80); + Size get preferredSize => const Size.fromHeight(kToolbarHeight); } class _TopBarState extends State<TopBar> { @@ -55,11 +58,9 @@ class _TopBarState extends State<TopBar> { List<ProjectModel> events = []; if (current.parentId == null) { - // Current IS Project root = current; events = await _projectRepo.getEvents(current.id!); } else { - // Current IS Event root = await _projectRepo.getProjectById(current.parentId!); if (root != null) { events = await _projectRepo.getEvents(root.id!); @@ -86,103 +87,122 @@ class _TopBarState extends State<TopBar> { color: Variables.background, child: SafeArea( bottom: false, - child: Container( - height: 80, - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Row( - children: [ - // 1. Back Button - if (widget.onBack != null) - IconButton( - icon: const Icon(Icons.arrow_back, color: Variables.textPrimary), - onPressed: widget.onBack, - ), - if (widget.onBack != null) const SizedBox(width: 8), + child: SizedBox( + height: kToolbarHeight, // FIX: Use standard height + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + // 1. Back Button + if (widget.onBack != null) ...[ + IconButton( + icon: const Icon(Icons.arrow_back, color: Variables.textPrimary), + onPressed: widget.onBack, + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + ), + const SizedBox(width: 12), + ], - // 2. Title & Selector - if (!_isLoading && _currentProject != null && _rootProject != null) + // 2. Title & Selector Expanded( - child: Row( - children: [ - Expanded( - child: Text( - _rootProject!.title, + child: widget.titleOverride != null + ? Text( + widget.titleOverride!, style: const TextStyle( - fontSize: 26, + fontSize: 20, // Slightly smaller for tags fontWeight: FontWeight.w600, color: Variables.textPrimary, ), overflow: TextOverflow.ellipsis, - ), - ), - const SizedBox(width: 12), - PopupMenuButton<ProjectModel>( - onSelected: (project) { - widget.onProjectChanged?.call(project); - }, - color: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - offset: const Offset(0, 40), - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 14, vertical: 10), - decoration: BoxDecoration( - color: Variables.surfaceSubtle, - borderRadius: BorderRadius.circular(12), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - _currentProject!.id == _rootProject!.id - ? "Main Project" - : _currentProject!.title, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Variables.textPrimary, + ) + : (!_isLoading && _currentProject != null && _rootProject != null) + ? Row( + children: [ + Flexible( + child: Text( + _rootProject!.title, + style: const TextStyle( + fontSize: 22, // Adjusted font size + fontWeight: FontWeight.w600, + color: Variables.textPrimary, + ), + overflow: TextOverflow.ellipsis, + ), ), - ), - const SizedBox(width: 4), - const Icon( - Icons.keyboard_arrow_down_rounded, - color: Variables.textPrimary, - ) - ], - ), - ), - itemBuilder: (context) { - return _contextList.map((ProjectModel project) { - final isRoot = project.id == _rootProject!.id; - final isSelected = project.id == _currentProject!.id; - return PopupMenuItem<ProjectModel>( - value: project, - child: Text( - isRoot ? "Main Project" : project.title, - style: Variables.bodyStyle.copyWith( - fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, - color: isSelected ? Variables.textPrimary : Variables.textSecondary, + const SizedBox(width: 8), + PopupMenuButton<ProjectModel>( + padding: EdgeInsets.zero, + onSelected: (project) { + widget.onProjectChanged?.call(project); + }, + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + offset: const Offset(0, 40), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + decoration: BoxDecoration( + color: Variables.surfaceSubtle, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + _currentProject!.id == _rootProject!.id + ? "Main" + : _currentProject!.title, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Variables.textPrimary, + ), + ), + const SizedBox(width: 4), + const Icon( + Icons.keyboard_arrow_down_rounded, + color: Variables.textPrimary, + size: 18, + ) + ], + ), + ), + itemBuilder: (context) { + return _contextList.map((ProjectModel project) { + final isRoot = project.id == _rootProject!.id; + final isSelected = project.id == _currentProject!.id; + return PopupMenuItem<ProjectModel>( + value: project, + child: Text( + isRoot ? "Main Project" : project.title, + style: Variables.bodyStyle.copyWith( + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + color: isSelected ? Variables.textPrimary : Variables.textSecondary, + ), + ), + ); + }).toList(); + }, ), - ), - ); - }).toList(); - }, - ), - ], - ), + ], + ) + : const SizedBox(), ), - // 3. Settings Icon - IconButton( - icon: const Icon(Icons.settings_outlined, color: Variables.textPrimary), - onPressed: widget.onSettingsPressed, - ), - ], + // 3. Settings Icon + IconButton( + icon: const Icon(Icons.settings_outlined, color: Variables.textPrimary), + onPressed: widget.onSettingsPressed, + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + ), + ], + ), ), ), ), ); } -} +} +\ No newline at end of file