creek

The AI Image Editor of 2030

lib/ui/widgets/top_bar.dart (15046B)


  1import 'package:flutter/material.dart';
  2import 'package:flutter_svg/flutter_svg.dart';
  3import 'package:creekui/data/models/project_model.dart';
  4import 'package:creekui/data/repos/project_repo.dart';
  5import 'package:creekui/ui/styles/variables.dart';
  6import 'package:creekui/ui/pages/settings_page.dart';
  7import 'package:creekui/ui/pages/home_page.dart';
  8
  9class TopBar extends StatefulWidget implements PreferredSizeWidget {
 10  final int currentProjectId;
 11  final VoidCallback? onBack;
 12  final Function(ProjectModel)? onProjectChanged;
 13  final VoidCallback? onSettingsPressed;
 14  final String? titleOverride;
 15  final VoidCallback? onLayoutPressed;
 16  final VoidCallback? onFilterPressed;
 17  final VoidCallback? onAIPressed;
 18  final bool? isAlternateView;
 19  final VoidCallback? onLayoutToggle;
 20  final bool hideSecondRow;
 21  final bool hideSelector;
 22  final bool showSettings;
 23
 24  const TopBar({
 25    super.key,
 26    required this.currentProjectId,
 27    this.onBack,
 28    this.onProjectChanged,
 29    this.onSettingsPressed,
 30    this.titleOverride,
 31    this.onLayoutPressed,
 32    this.onFilterPressed,
 33    this.onAIPressed,
 34    this.isAlternateView,
 35    this.onLayoutToggle,
 36    this.hideSecondRow = false,
 37    this.hideSelector = false,
 38    this.showSettings = true,
 39  });
 40
 41  @override
 42  State<TopBar> createState() => _TopBarState();
 43
 44  @override
 45  Size get preferredSize =>
 46      hideSecondRow ? const Size.fromHeight(48.0) : const Size.fromHeight(88.0);
 47}
 48
 49class _TopBarState extends State<TopBar> {
 50  final ProjectRepo _projectRepo = ProjectRepo();
 51
 52  bool _isLoading = true;
 53  ProjectModel? _currentProject;
 54  ProjectModel? _rootProject;
 55  List<ProjectModel> _contextList = [];
 56
 57  @override
 58  void initState() {
 59    super.initState();
 60    _loadData();
 61  }
 62
 63  @override
 64  void didUpdateWidget(covariant TopBar oldWidget) {
 65    super.didUpdateWidget(oldWidget);
 66    if (oldWidget.currentProjectId != widget.currentProjectId) {
 67      _loadData();
 68    }
 69  }
 70
 71  Future<void> _loadData() async {
 72    try {
 73      final current = await _projectRepo.getProjectById(
 74        widget.currentProjectId,
 75      );
 76      if (current == null) {
 77        if (mounted) setState(() => _isLoading = false);
 78        return;
 79      }
 80
 81      ProjectModel? root;
 82      List<ProjectModel> events = [];
 83
 84      if (current.parentId == null) {
 85        root = current;
 86        events = await _projectRepo.getEvents(current.id!);
 87      } else {
 88        root = await _projectRepo.getProjectById(current.parentId!);
 89        if (root != null) {
 90          events = await _projectRepo.getEvents(root.id!);
 91        }
 92      }
 93      root ??= current;
 94      final List<ProjectModel> fullList = [root, ...events];
 95      if (mounted) {
 96        setState(() {
 97          _currentProject = current;
 98          _rootProject = root;
 99          _contextList = fullList;
100          _isLoading = false;
101        });
102      }
103    } catch (e) {
104      debugPrint("TopBar Error: $e");
105      if (mounted) setState(() => _isLoading = false);
106    }
107  }
108
109  void _handleSafeBack() {
110    if (widget.onBack != null) {
111      widget.onBack!();
112      return;
113    }
114
115    if (Navigator.canPop(context)) {
116      Navigator.pop(context);
117    } else {
118      // Fallback: Go to HomePage to prevent app exit/crash
119      Navigator.pushReplacement(
120        context,
121        MaterialPageRoute(builder: (_) => const HomePage()),
122      );
123    }
124  }
125
126  void _openSettings() {
127    if (widget.onSettingsPressed != null) {
128      widget.onSettingsPressed!();
129    } else {
130      Navigator.push(
131        context,
132        MaterialPageRoute(builder: (_) => const SettingsPage()),
133      );
134    }
135  }
136
137  @override
138  Widget build(BuildContext context) {
139    return Container(
140      color: Colors.white,
141      child: SafeArea(
142        bottom: false,
143        child: Column(
144          mainAxisSize: MainAxisSize.min,
145          children: [
146            // Row 1: Title and Settings
147            Container(
148              height: 48.0,
149              padding: const EdgeInsets.symmetric(horizontal: 16.0),
150              child: Row(
151                children: [
152                  // Back Button
153                  GestureDetector(
154                    onTap: _handleSafeBack,
155                    behavior: HitTestBehavior.opaque,
156                    child: Container(
157                      width: 24,
158                      height: 24,
159                      alignment: Alignment.center,
160                      child: const Icon(
161                        Icons.arrow_back,
162                        size: 20,
163                        color: Variables.textPrimary,
164                      ),
165                    ),
166                  ),
167                  const SizedBox(width: 10),
168
169                  // Title
170                  Expanded(
171                    child:
172                        widget.titleOverride != null
173                            ? Text(
174                              widget.titleOverride!,
175                              style: Variables.headerStyle.copyWith(
176                                fontSize: 20,
177                                height: 24 / 20,
178                              ),
179                              overflow: TextOverflow.ellipsis,
180                            )
181                            : (!_isLoading &&
182                                _currentProject != null &&
183                                _rootProject != null)
184                            ? Text(
185                              _rootProject!.title,
186                              style: Variables.headerStyle.copyWith(
187                                fontSize: 20,
188                                height: 24 / 20,
189                              ),
190                              overflow: TextOverflow.ellipsis,
191                            )
192                            : const SizedBox(),
193                  ),
194
195                  // Settings Icon
196                  if (widget.showSettings)
197                    Material(
198                      color: Colors.transparent,
199                      child: InkWell(
200                        onTap: _openSettings,
201                        borderRadius: BorderRadius.circular(24),
202                        child: Container(
203                          width: 48,
204                          height: 48,
205                          alignment: Alignment.center,
206                          child: SvgPicture.asset(
207                            'assets/icons/settings-line.svg',
208                            width: 24,
209                            height: 24,
210                            colorFilter: const ColorFilter.mode(
211                              Variables.textPrimary,
212                              BlendMode.srcIn,
213                            ),
214                          ),
215                        ),
216                      ),
217                    ),
218                ],
219              ),
220            ),
221            // Row 2: Global Dropdown and Action Buttons
222            if (!widget.hideSecondRow)
223              Container(
224                height: 40.0,
225                padding: const EdgeInsets.symmetric(horizontal: 16.0),
226                child: Row(
227                  children: [
228                    // Left group: Global Dropdown and Layout Button
229                    Row(
230                      children: [
231                        // Global Dropdown
232                        if (!widget.hideSelector &&
233                            !_isLoading &&
234                            _currentProject != null &&
235                            _rootProject != null)
236                          PopupMenuButton<ProjectModel>(
237                            padding: EdgeInsets.zero,
238                            onSelected:
239                                (project) =>
240                                    widget.onProjectChanged?.call(project),
241                            color: Colors.white,
242                            shape: RoundedRectangleBorder(
243                              borderRadius: BorderRadius.circular(8),
244                            ),
245                            offset: const Offset(0, 42),
246                            child: Container(
247                              padding: const EdgeInsets.symmetric(
248                                horizontal: 16,
249                                vertical: 10,
250                              ),
251                              decoration: BoxDecoration(
252                                color: Variables.borderSubtle,
253                                borderRadius: BorderRadius.circular(1000),
254                              ),
255                              child: Row(
256                                mainAxisSize: MainAxisSize.min,
257                                children: [
258                                  Text(
259                                    _currentProject!.id == _rootProject!.id
260                                        ? "Global"
261                                        : _currentProject!.title,
262                                    style: const TextStyle(
263                                      fontFamily: 'GeneralSans',
264                                      fontSize: 14,
265                                      fontWeight: FontWeight.w500,
266                                      height: 20 / 14,
267                                      color: Variables.textPrimary,
268                                    ),
269                                  ),
270                                  const SizedBox(width: 8),
271                                  const Icon(
272                                    Icons.keyboard_arrow_down_rounded,
273                                    size: 20,
274                                    color: Variables.textPrimary,
275                                  ),
276                                ],
277                              ),
278                            ),
279                            itemBuilder: (context) {
280                              return _contextList.map((project) {
281                                final isSelected =
282                                    project.id == _currentProject!.id;
283                                final isRoot = project.id == _rootProject!.id;
284                                return PopupMenuItem<ProjectModel>(
285                                  value: project,
286                                  child: Text(
287                                    isRoot ? "Global" : project.title,
288                                    style: Variables.bodyStyle.copyWith(
289                                      fontWeight:
290                                          isSelected
291                                              ? FontWeight.bold
292                                              : FontWeight.normal,
293                                      color:
294                                          isSelected
295                                              ? Variables.textPrimary
296                                              : Variables.textSecondary,
297                                    ),
298                                  ),
299                                );
300                              }).toList();
301                            },
302                          ),
303                        if (widget.onLayoutToggle != null ||
304                            widget.onLayoutPressed != null) ...[
305                          const SizedBox(width: 8),
306                          GestureDetector(
307                            onTap:
308                                widget.onLayoutToggle ?? widget.onLayoutPressed,
309                            child: Container(
310                              padding: const EdgeInsets.symmetric(
311                                horizontal: 16,
312                                vertical: 10,
313                              ),
314                              decoration: BoxDecoration(
315                                color: Variables.borderSubtle,
316                                borderRadius: BorderRadius.circular(1000),
317                              ),
318                              child: Icon(
319                                widget.onLayoutToggle != null
320                                    ? (widget.isAlternateView == true
321                                        ? Icons.dashboard
322                                        : Icons.view_agenda_outlined)
323                                    : Icons.edit,
324                                size: 20,
325                                color: Variables.textPrimary,
326                              ),
327                            ),
328                          ),
329                        ],
330                      ],
331                    ),
332                    const Spacer(),
333                    // Right group: Filter and AI Buttons
334                    if (widget.onFilterPressed != null ||
335                        widget.onAIPressed != null)
336                      Row(
337                        children: [
338                          // Filter Button
339                          if (widget.onFilterPressed != null)
340                            GestureDetector(
341                              onTap: widget.onFilterPressed,
342                              child: Container(
343                                padding: const EdgeInsets.symmetric(
344                                  horizontal: 16,
345                                  vertical: 10,
346                                ),
347                                decoration: BoxDecoration(
348                                  color: Variables.borderSubtle,
349                                  borderRadius: BorderRadius.circular(1000),
350                                ),
351                                child: const Icon(
352                                  Icons.tune,
353                                  size: 20,
354                                  color: Variables.textPrimary,
355                                ),
356                              ),
357                            ),
358                          if (widget.onFilterPressed != null &&
359                              widget.onAIPressed != null)
360                            const SizedBox(width: 8),
361                          // AI Button
362                          if (widget.onAIPressed != null)
363                            GestureDetector(
364                              onTap: widget.onAIPressed,
365                              child: Container(
366                                padding: const EdgeInsets.symmetric(
367                                  horizontal: 16,
368                                  vertical: 10,
369                                ),
370                                decoration: BoxDecoration(
371                                  color: Variables.borderSubtle,
372                                  borderRadius: BorderRadius.circular(1000),
373                                ),
374                                child: const Icon(
375                                  Icons.auto_awesome,
376                                  size: 20,
377                                  color: Variables.textPrimary,
378                                ),
379                              ),
380                            ),
381                        ],
382                      ),
383                  ],
384                ),
385              ),
386          ],
387        ),
388      ),
389    );
390  }
391}