creek

The AI Image Editor of 2030

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}