// SPDX-FileCopyrightText: 2026 FocusFlow contributors // SPDX-License-Identifier: AGPL-3.0-only /// Renders the Sidebar widget for the FocusFlow Flutter UI. library; import 'package:flutter/material.dart'; import '../models/navigation/focus_flow_section.dart'; import '../theme/focus_flow_tokens.dart'; /// Sidebar for primary FocusFlow navigation. class Sidebar extends StatelessWidget { /// Creates the FocusFlow sidebar. const Sidebar({ required this.activeSection, required this.onSectionSelected, super.key, }); /// Currently active app section. final FocusFlowSection activeSection; /// Callback invoked when a section is selected. final ValueChanged onSectionSelected; /// Builds the widget subtree for this component. /// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data. @override Widget build(BuildContext context) { return DecoratedBox( decoration: const BoxDecoration(color: FocusFlowTokens.sidebarBackground), child: Padding( padding: const EdgeInsets.fromLTRB(18, 22, 18, 28), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const _BrandRow(), const SizedBox(height: 34), _NavItem( label: 'Today', icon: Icons.calendar_today, section: FocusFlowSection.today, active: activeSection == FocusFlowSection.today, onTap: onSectionSelected, ), const SizedBox(height: 16), _NavItem( label: 'Backlog', icon: Icons.layers, section: FocusFlowSection.backlog, active: activeSection == FocusFlowSection.backlog, onTap: onSectionSelected, ), const SizedBox(height: 16), _NavItem( label: 'Projects', icon: Icons.work_outline, section: FocusFlowSection.projects, active: activeSection == FocusFlowSection.projects, onTap: onSectionSelected, ), const SizedBox(height: 16), _NavItem( label: 'Reports', icon: Icons.bar_chart, section: FocusFlowSection.reports, active: activeSection == FocusFlowSection.reports, onTap: onSectionSelected, ), const SizedBox(height: 30), const Divider(color: FocusFlowTokens.subtleBorder), const SizedBox(height: 18), _NavItem( label: 'Settings', icon: Icons.settings, section: FocusFlowSection.settings, active: activeSection == FocusFlowSection.settings, onTap: onSectionSelected, ), ], ), ), ); } } /// Private implementation type for `_BrandRow` in this library. /// It keeps supporting state and behavior scoped to the owning file so the public API stays focused on the scheduler concepts that callers use directly. class _BrandRow extends StatelessWidget { /// Creates a `_BrandRow` instance with the values required by its invariants. /// Callers should pass already-normalized scheduler data because validation and defaulting belong at the model or service boundary, not in arbitrary UI code. const _BrandRow(); /// Builds the widget subtree for this component. /// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data. @override Widget build(BuildContext context) { return Row( children: [ Container( width: 42, height: 42, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all(color: FocusFlowTokens.accentMagenta), color: FocusFlowTokens.glassPanelStrong, ), child: const Icon( Icons.auto_awesome, color: FocusFlowTokens.accentMagenta, ), ), const SizedBox(width: 14), Expanded( child: Text( 'FocusFlow', maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.titleMedium, ), ), ], ); } } /// Private implementation type for `_NavItem` in this library. /// It keeps supporting state and behavior scoped to the owning file so the public API stays focused on the scheduler concepts that callers use directly. class _NavItem extends StatelessWidget { /// Creates a `_NavItem` instance with the values required by its invariants. /// Callers should pass already-normalized scheduler data because validation and defaulting belong at the model or service boundary, not in arbitrary UI code. const _NavItem({ required this.label, required this.icon, required this.section, required this.onTap, this.active = false, }); /// Stores the `label` value for this object. /// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior. final String label; /// Stores the `icon` value for this object. /// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior. final IconData icon; /// Stores the `section` value for this object. /// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior. final FocusFlowSection section; /// Stores the `onTap` value for this object. /// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior. final ValueChanged onTap; /// Stores the `active` value for this object. /// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior. final bool active; /// Builds the widget subtree for this component. /// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data. @override Widget build(BuildContext context) { final color = active ? FocusFlowTokens.accentMagenta : FocusFlowTokens.textPrimary; return Material( color: Colors.transparent, child: InkWell( key: active ? ValueKey('nav-${section.name}-active') : null, borderRadius: BorderRadius.circular(FocusFlowTokens.navRadius), onTap: () => onTap(section), child: Container( height: 54, padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(FocusFlowTokens.navRadius), border: Border.all( color: active ? FocusFlowTokens.navBorder : Colors.transparent, ), color: active ? FocusFlowTokens.glassPanelStrong : Colors.transparent, ), child: Row( children: [ Icon(icon, color: color, size: 24), const SizedBox(width: 16), Expanded( child: Text( label, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: color.withValues(alpha: active ? 1 : 0.88), fontSize: FocusFlowTokens.sidebarNavSize, fontWeight: FontWeight.w500, ), ), ), ], ), ), ), ); } }