creek

The AI Image Editor of 2030

lib/ui/pages/project_board_page_alternate.dart (5016B)


  1import 'package:flutter/material.dart';
  2import 'package:creekui/ui/styles/variables.dart';
  3import 'package:creekui/data/models/image_model.dart';
  4import 'package:creekui/data/repos/image_repo.dart';
  5import 'package:creekui/ui/widgets/moodboard_image_card.dart';
  6import 'package:creekui/ui/widgets/empty_state.dart';
  7import 'image_details_page.dart';
  8
  9class ProjectBoardPageAlternate extends StatefulWidget {
 10  final int projectId;
 11  const ProjectBoardPageAlternate({super.key, required this.projectId});
 12
 13  @override
 14  State<ProjectBoardPageAlternate> createState() =>
 15      ProjectBoardPageAlternateState();
 16}
 17
 18class ProjectBoardPageAlternateState extends State<ProjectBoardPageAlternate> {
 19  final _imageRepo = ImageRepo();
 20  List<ImageModel> _allImages = [];
 21  List<ImageModel> _filteredImages = [];
 22  List<String> _allTags = [];
 23  final Set<String> _selectedTags = {};
 24  bool _isLoading = true;
 25
 26  @override
 27  void initState() {
 28    super.initState();
 29    _loadData();
 30  }
 31
 32  @override
 33  void didUpdateWidget(covariant ProjectBoardPageAlternate oldWidget) {
 34    super.didUpdateWidget(oldWidget);
 35    if (oldWidget.projectId != widget.projectId) _loadData();
 36  }
 37
 38  void refreshData() => _loadData();
 39
 40  Future<void> _loadData() async {
 41    setState(() => _isLoading = true);
 42    final images = await _imageRepo.getImages(widget.projectId);
 43    final Set<String> tags = {};
 44    for (var img in images) {
 45      tags.addAll(img.tags);
 46    }
 47
 48    if (mounted) {
 49      setState(() {
 50        _allImages = images;
 51        _filteredImages = images;
 52        _allTags = tags.toList()..sort();
 53        _isLoading = false;
 54      });
 55      if (_selectedTags.isNotEmpty) _applyFilter();
 56    }
 57  }
 58
 59  void _applyFilter() {
 60    setState(() {
 61      _filteredImages =
 62          _selectedTags.isEmpty
 63              ? _allImages
 64              : _allImages
 65                  .where(
 66                    (img) =>
 67                        img.tags.toSet().intersection(_selectedTags).isNotEmpty,
 68                  )
 69                  .toList();
 70    });
 71  }
 72
 73  @override
 74  Widget build(BuildContext context) {
 75    if (_isLoading) return const Center(child: CircularProgressIndicator());
 76
 77    final leftColumn = <Widget>[];
 78    final rightColumn = <Widget>[];
 79
 80    for (int i = 0; i < _filteredImages.length; i++) {
 81      final item = MoodboardImageCard(
 82        image: _filteredImages[i],
 83        height: (i % 3 == 0) ? 240 : 180,
 84        showTags: true,
 85        onTap:
 86            () => Navigator.push(
 87              context,
 88              MaterialPageRoute(
 89                builder:
 90                    (_) => ImageDetailsPage(
 91                      imagePath: _filteredImages[i].filePath,
 92                      imageId: _filteredImages[i].id,
 93                      projectId: widget.projectId,
 94                    ),
 95              ),
 96            ),
 97        onDeleted: refreshData,
 98      );
 99
100      if (i % 2 == 0) {
101        leftColumn.add(
102          Padding(padding: const EdgeInsets.only(bottom: 12), child: item),
103        );
104      } else {
105        rightColumn.add(
106          Padding(padding: const EdgeInsets.only(bottom: 12), child: item),
107        );
108      }
109    }
110
111    return Column(
112      children: [
113        // Context Info Row (Image Count + Active Filters)
114        Padding(
115          padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
116          child: Row(
117            children: [
118              const Spacer(),
119              if (_selectedTags.isNotEmpty)
120                Container(
121                  padding: const EdgeInsets.symmetric(
122                    horizontal: 8,
123                    vertical: 4,
124                  ),
125                  decoration: BoxDecoration(
126                    color: Variables.textPrimary,
127                    borderRadius: BorderRadius.circular(12),
128                  ),
129                  child: Text(
130                    "${_selectedTags.length} Active",
131                    style: Variables.captionStyle.copyWith(
132                      color: Colors.white,
133                      fontWeight: FontWeight.bold,
134                    ),
135                  ),
136                ),
137            ],
138          ),
139        ),
140
141        // Masonry Grid
142        Expanded(
143          child:
144              _filteredImages.isEmpty
145                  ? const EmptyState(
146                    icon: Icons.image_not_supported_outlined,
147                    title: "No images found",
148                    subtitle: "Try removing filters or adding new images",
149                  )
150                  : SingleChildScrollView(
151                    padding: const EdgeInsets.fromLTRB(16, 0, 16, 80),
152                    child: Row(
153                      crossAxisAlignment: CrossAxisAlignment.start,
154                      children: [
155                        Expanded(child: Column(children: leftColumn)),
156                        const SizedBox(width: 12),
157                        Expanded(child: Column(children: rightColumn)),
158                      ],
159                    ),
160                  ),
161        ),
162      ],
163    );
164  }
165}