creek
The AI Image Editor of 2030git clone https://github.com/maydayv7/creek
lib/ui/pages/project_tag_page.dart (5475B)
1import 'dart:io';
2import 'package:flutter/material.dart';
3import 'package:image_picker/image_picker.dart';
4import 'package:creekui/ui/styles/variables.dart';
5import 'package:creekui/ui/widgets/top_bar.dart';
6import 'package:creekui/ui/widgets/bottom_bar.dart';
7import 'package:creekui/ui/widgets/moodboard_image_card.dart';
8import 'package:creekui/ui/widgets/empty_state.dart';
9import 'package:creekui/data/models/image_model.dart';
10import 'package:creekui/data/repos/image_repo.dart';
11import 'package:creekui/data/repos/project_repo.dart';
12import 'package:creekui/data/repos/note_repo.dart';
13import 'image_save_page.dart';
14import 'image_details_page.dart';
15
16class ProjectTagPage extends StatefulWidget {
17 final int projectId;
18 final String tag;
19
20 const ProjectTagPage({super.key, required this.projectId, required this.tag});
21
22 @override
23 State<ProjectTagPage> createState() => _ProjectTagPageState();
24}
25
26class _ProjectTagPageState extends State<ProjectTagPage> {
27 final _imageRepo = ImageRepo();
28 final _projectRepo = ProjectRepo();
29 final _noteRepo = NoteRepo();
30 final ImagePicker _picker = ImagePicker();
31
32 List<ImageModel> _images = [];
33 String _projectName = "Project";
34 bool _isLoading = true;
35
36 @override
37 void initState() {
38 super.initState();
39 _loadData();
40 }
41
42 Future<void> _loadData() async {
43 final project = await _projectRepo.getProjectById(widget.projectId);
44 if (project != null) _projectName = project.title;
45
46 final allImages = await _imageRepo.getImages(widget.projectId);
47 final List<ImageModel> filtered = [];
48
49 await Future.wait(
50 allImages.map((img) async {
51 bool matches = false;
52 if (widget.tag == 'Uncategorized') {
53 if (img.tags.isEmpty) {
54 final notes = await _noteRepo.getNotesForImage(img.id);
55 if (!notes.any((n) => n.category.isNotEmpty)) matches = true;
56 }
57 } else {
58 if (img.tags.contains(widget.tag))
59 matches = true;
60 else {
61 final notes = await _noteRepo.getNotesForImage(img.id);
62 if (notes.any((n) => n.category == widget.tag)) matches = true;
63 }
64 }
65 if (matches) filtered.add(img);
66 }),
67 );
68
69 if (mounted) {
70 setState(() {
71 _images = filtered;
72 _isLoading = false;
73 });
74 }
75 }
76
77 Future<void> _pickAndRedirect() async {
78 try {
79 final List<XFile> pickedFiles = await _picker.pickMultiImage();
80 if (pickedFiles.isNotEmpty) {
81 if (!mounted) return;
82 Navigator.push(
83 context,
84 MaterialPageRoute(
85 builder:
86 (_) => ImageSavePage(
87 imagePaths: pickedFiles.map((e) => e.path).toList(),
88 projectId: widget.projectId,
89 projectName: _projectName,
90 isFromShare: false,
91 ),
92 ),
93 ).then((_) => _loadData());
94 }
95 } catch (e) {
96 debugPrint("Error picking images: $e");
97 }
98 }
99
100 @override
101 Widget build(BuildContext context) {
102 final leftColumn = <Widget>[];
103 final rightColumn = <Widget>[];
104
105 for (int i = 0; i < _images.length; i++) {
106 final item = MoodboardImageCard(
107 image: _images[i],
108 height: (i % 3 == 0) ? 240 : 180,
109 onTap:
110 () => Navigator.push(
111 context,
112 MaterialPageRoute(
113 builder:
114 (_) => ImageDetailsPage(
115 imagePath: _images[i].filePath,
116 imageId: _images[i].id,
117 projectId: widget.projectId,
118 ),
119 ),
120 ).then((_) => _loadData()),
121 onDeleted: _loadData,
122 );
123
124 if (i % 2 == 0) {
125 leftColumn.add(
126 Padding(padding: const EdgeInsets.only(bottom: 12), child: item),
127 );
128 } else {
129 rightColumn.add(
130 Padding(padding: const EdgeInsets.only(bottom: 12), child: item),
131 );
132 }
133 }
134
135 return Scaffold(
136 backgroundColor: Variables.background,
137 appBar: TopBar(
138 currentProjectId: widget.projectId,
139 titleOverride: widget.tag.toUpperCase(),
140 onBack: () => Navigator.pop(context),
141 hideSecondRow: true,
142 ),
143 bottomNavigationBar: BottomBar(
144 currentTab: BottomBarItem.moodboard,
145 projectId: widget.projectId,
146 ),
147 floatingActionButton: FloatingActionButton(
148 onPressed: _pickAndRedirect,
149 backgroundColor: Variables.textPrimary,
150 foregroundColor: Variables.background,
151 child: const Icon(Icons.add_photo_alternate_outlined),
152 ),
153 body:
154 _isLoading
155 ? const Center(child: CircularProgressIndicator())
156 : _images.isEmpty
157 ? EmptyState(
158 icon: Icons.image_not_supported_outlined,
159 title: "No images found",
160 subtitle: "No images found for '${widget.tag}'",
161 )
162 : SingleChildScrollView(
163 padding: const EdgeInsets.fromLTRB(16, 16, 16, 80),
164 child: Row(
165 crossAxisAlignment: CrossAxisAlignment.start,
166 children: [
167 Expanded(child: Column(children: leftColumn)),
168 const SizedBox(width: 12),
169 Expanded(child: Column(children: rightColumn)),
170 ],
171 ),
172 ),
173 );
174 }
175}