import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../theme/app_theme.dart'; import 'tooth_logo.dart'; class GradientAppBar extends StatelessWidget implements PreferredSizeWidget { const GradientAppBar({ super.key, required this.title, required this.category, this.actions = const [], this.searchController, this.onSearchChanged, this.searchHint, }); final String title; final String category; final List actions; final TextEditingController? searchController; final ValueChanged? onSearchChanged; final String? searchHint; bool get _hasSearch => searchController != null && onSearchChanged != null; @override Size get preferredSize => Size.fromHeight(kToolbarHeight + (_hasSearch ? 52.0 : 0.0)); @override Widget build(BuildContext context) { final isDesktop = MediaQuery.sizeOf(context).width > AppLayout.sidebarBreakpoint; final searchBottom = _hasSearch ? _SearchBarBottom( controller: searchController!, onChanged: onSearchChanged!, hint: searchHint ?? 'Ara...', ) : null; if (isDesktop) { return AppBar( backgroundColor: AppColors.surface, foregroundColor: AppColors.textPrimary, elevation: 0, scrolledUnderElevation: 0, automaticallyImplyLeading: false, titleSpacing: 24, title: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( 'DLS', style: TextStyle( fontSize: 11, color: AppColors.textSecondary.withValues(alpha: 0.8), letterSpacing: 0.3, ), ), Text( title, style: const TextStyle( fontSize: 17, fontWeight: FontWeight.w700, color: AppColors.textPrimary, ), ), ], ), actions: [ ...actions, if (actions.isNotEmpty) const SizedBox(width: 8), ], iconTheme: const IconThemeData(color: AppColors.textSecondary, size: 22), actionsIconTheme: const IconThemeData(color: AppColors.textSecondary, size: 22), bottom: searchBottom, ); } return AppBar( backgroundColor: AppColors.primary, foregroundColor: Colors.white, elevation: 0, systemOverlayStyle: SystemUiOverlayStyle.light, automaticallyImplyLeading: false, leadingWidth: 60, leading: Padding( padding: const EdgeInsets.only(left: 16, top: 8, bottom: 8), child: Container( decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(10), ), child: const Center(child: ToothLogo(size: 20, color: Colors.white)), ), ), titleSpacing: 8, title: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( category, style: TextStyle( color: Colors.white.withValues(alpha: 0.65), fontSize: 11, fontWeight: FontWeight.w600, letterSpacing: 1.5, ), ), Text( title, style: const TextStyle( color: Colors.white, fontSize: 15, fontWeight: FontWeight.w700, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), actions: actions.isNotEmpty ? [...actions, const SizedBox(width: 4)] : null, iconTheme: const IconThemeData(color: Colors.white, size: 22), actionsIconTheme: const IconThemeData(color: Colors.white, size: 22), flexibleSpace: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFF0F172A), AppColors.primary], ), ), ), bottom: searchBottom, ); } } // ── iOS-26-style search bar shown below the AppBar title ───────────────────── class _SearchBarBottom extends StatelessWidget implements PreferredSizeWidget { const _SearchBarBottom({ required this.controller, required this.onChanged, required this.hint, }); final TextEditingController controller; final ValueChanged onChanged; final String hint; @override Size get preferredSize => const Size.fromHeight(52); @override Widget build(BuildContext context) { final isDesktop = MediaQuery.sizeOf(context).width > AppLayout.sidebarBreakpoint; final bg = isDesktop ? AppColors.surfaceVariant : Colors.white.withValues(alpha: 0.15); final textColor = isDesktop ? AppColors.textPrimary : Colors.white; final iconColor = isDesktop ? AppColors.textMuted : Colors.white.withValues(alpha: 0.65); final hintColor = isDesktop ? AppColors.textMuted : Colors.white.withValues(alpha: 0.5); return SizedBox( height: 52, child: Padding( padding: const EdgeInsets.fromLTRB(16, 4, 16, 10), child: ListenableBuilder( listenable: controller, builder: (context, _) => Container( height: 38, decoration: BoxDecoration( color: bg, borderRadius: BorderRadius.circular(12), border: isDesktop ? Border.all(color: AppColors.border) : null, ), child: TextField( controller: controller, onChanged: onChanged, style: TextStyle(color: textColor, fontSize: 15), decoration: InputDecoration( hintText: hint, hintStyle: TextStyle(color: hintColor, fontSize: 15), prefixIcon: Padding( padding: const EdgeInsets.only(left: 10, right: 6), child: Icon(Icons.search_rounded, size: 18, color: iconColor), ), prefixIconConstraints: const BoxConstraints(minWidth: 36, minHeight: 36), suffixIcon: controller.text.isNotEmpty ? GestureDetector( onTap: () { controller.clear(); onChanged(''); }, child: Padding( padding: const EdgeInsets.only(right: 10), child: Icon(Icons.close_rounded, size: 16, color: iconColor), ), ) : null, suffixIconConstraints: const BoxConstraints(minWidth: 32, minHeight: 36), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric(vertical: 10), isDense: true, ), ), ), ), ), ); } } // ── Sort / filter bottom sheet ──────────────────────────────────────────────── Future showSortSheet( BuildContext context, { required String title, required List options, required int current, }) { return showModalBottomSheet( context: context, backgroundColor: Colors.transparent, builder: (ctx) => _SortSheet( title: title, options: options, current: current, ), ); } class _SortSheet extends StatelessWidget { const _SortSheet({ required this.title, required this.options, required this.current, }); final String title; final List options; final int current; @override Widget build(BuildContext context) { return Container( decoration: const BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 12), Container( width: 40, height: 4, decoration: BoxDecoration( color: AppColors.border, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Align( alignment: Alignment.centerLeft, child: Text( title, style: const TextStyle( fontSize: 17, fontWeight: FontWeight.w700, color: AppColors.textPrimary, ), ), ), ), const SizedBox(height: 8), for (int i = 0; i < options.length; i++) ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 20), title: Text( options[i], style: TextStyle( color: i == current ? AppColors.primary : AppColors.textPrimary, fontWeight: i == current ? FontWeight.w600 : FontWeight.normal, ), ), trailing: i == current ? const Icon(Icons.check_rounded, color: AppColors.primary, size: 20) : null, onTap: () => Navigator.pop(context, i), ), SizedBox(height: MediaQuery.paddingOf(context).bottom + 8), ], ), ); } }