import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/assets.dart'; import 'package:didvan/models/day_time.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/routes/routes.dart'; import 'package:didvan/views/customize_category/customize_category_state.dart'; import 'package:didvan/views/notification_time/notification_time_state.dart'; import 'package:didvan/views/widgets/didvan/button.dart'; import 'package:didvan/views/widgets/didvan/divider.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart'; import 'package:didvan/views/widgets/didvan/time_sky_animation.dart'; import 'package:didvan/views/widgets/didvan/time_slider_picker.dart'; import 'package:didvan/views/widgets/shimmer_placeholder.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.dart'; class NotificationSettings extends StatefulWidget { final Map pageData; const NotificationSettings({super.key, required this.pageData}); @override State createState() => _NotificationSettingsState(); } class _NotificationSettingsState extends State with SingleTickerProviderStateMixin { late AnimationController _animationController; late NotificationTimeState notificationTimeState; late CustomizeCategoryState customizeState; bool _hasAnimated = false; @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 1200), ); notificationTimeState = context.read(); customizeState = context.read(); Future.delayed(Duration.zero, () { notificationTimeState.getTime(); customizeState.getFavourites(); }); } @override void dispose() { _animationController.dispose(); super.dispose(); } void _startAnimationIfNeeded() { if (!_hasAnimated && customizeState.appState == AppState.idle && notificationTimeState.appState == AppState.idle) { _hasAnimated = true; WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { _animationController.forward(); } }); } } Widget _buildAnimatedSection({required Widget child, required int index}) { final double start = (index * 0.15).clamp(0.0, 1.0); final double end = (start + 0.4).clamp(0.0, 1.0); final animation = CurvedAnimation( parent: _animationController, curve: Interval(start, end, curve: Curves.easeOutQuint), ); return FadeTransition( opacity: animation, child: SlideTransition( position: Tween( begin: const Offset(0, 0.1), end: Offset.zero, ).animate(animation), child: child, ), ); } @override Widget build(BuildContext context) { return Material( child: DidvanScaffold( appBarData: null, showSliversFirst: true, slivers: [ SliverAppBar( pinned: true, backgroundColor: Theme.of(context).colorScheme.surface, automaticallyImplyLeading: false, leadingWidth: 200, leading: Padding( padding: const EdgeInsetsDirectional.only(start: 0.0), child: SvgPicture.asset( Assets.horizontalLogoWithText, fit: BoxFit.contain, height: 90, ), ), actions: [ IconButton( onPressed: () { Navigator.of(context).pushNamed(Routes.bookmarks); }, icon: SvgPicture.asset( 'lib/assets/icons/hugeicons_telescope-01.svg', color: DesignConfig.isDark ? Theme.of(context).colorScheme.caption : null)), IconButton( onPressed: () => Navigator.of(context).pop(), icon: SvgPicture.asset( 'lib/assets/icons/arrow-left.svg', color: Theme.of(context).colorScheme.caption, )), const SizedBox(width: 8), ], ), ], physics: const BouncingScrollPhysics(), hidePlayer: true, padding: const EdgeInsets.all(0).copyWith(bottom: 92), backgroundColor: Theme.of(context).colorScheme.background, children: [ Padding( padding: const EdgeInsets.only(bottom: 24), child: Consumer( builder: (context, state, child) { if (state.appState == AppState.idle) { _startAnimationIfNeeded(); final mediaKeywords = [ 'پادکست', 'ویدئوکست', 'اینفوگرافی', 'آمار و داده' ]; final mediaFaves = state.faves.where((fav) { return mediaKeywords.any((keyword) => fav.name != null && fav.name!.contains(keyword)); }).toList(); final exploreFaves = state.faves.where((fav) { return !mediaKeywords.any((keyword) => fav.name != null && fav.name!.contains(keyword)); }).toList(); return _buildAnimatedSection( index: 0, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(18.0), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 10), Text( 'دسته‌بندی اعلان‌ها', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: DesignConfig.isDark ? Colors.white : Colors.black), ), const SizedBox(height: 10), const Text( 'در این بخش می‌توانید تعیین کنید از کدام دسته از محتواهای منتشرشده در اپلیکیشن دیدوان، اعلان دریافت کنید.', style: TextStyle( fontSize: 14, color: Color.fromARGB(255, 128, 128, 128), )), ], ), ), if (mediaFaves.isNotEmpty) _CategoryExpansionGroup( title: "رسانه", items: mediaFaves, state: state, ), if (exploreFaves.isNotEmpty) _CategoryExpansionGroup( title: "کاوش", items: exploreFaves, state: state, ), ], ), ); } else { return Column( children: List.generate( 3, (index) => Padding( padding: const EdgeInsets.only( bottom: 16, left: 18, right: 18, top: 16), child: ShimmerPlaceholder( height: 48, width: MediaQuery.sizeOf(context).width, borderRadius: DesignConfig.mediumBorderRadius, ), ), ), ); } }, ), ), Padding( padding: const EdgeInsets.only(bottom: 24), child: Consumer( builder: (context, state, child) => state.appState == AppState.idle ? _buildAnimatedSection( index: 1, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(18.0), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 10), Text( 'زمان‌بندی دریافت اعلان‌ها', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: DesignConfig.isDark ? Colors.white : Colors.black), ), const SizedBox(height: 10), const Text( 'در این بخش می‌توانید تعیین کنید اعلان‌های دسته‌بندی‌هایی که انتخاب کرده‌اید، در چه بازه‌های زمانی برای شما ارسال شوند..', style: TextStyle( fontSize: 14, color: Color.fromARGB(255, 128, 128, 128), )), const SizedBox( height: 15, ) ], ), ), Builder( builder: (context) { int hour24 = int.tryParse(state.selectedTime.hour) ?? 12; if (state.selectedTime.meridiem == Meridiem.PM && hour24 != 12) { hour24 += 12; } if (state.selectedTime.meridiem == Meridiem.AM && hour24 == 12) { hour24 = 0; } return Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: TimeSkyAnimation( hour: hour24, ), ); }, ), TimeSliderPicker( selectedTime: state.selectedTime, isDisabled: false, onTimeChanged: (newTime) { state.selectedTime = newTime; state.update(); }, ), const SizedBox(height: 16), Padding( padding: const EdgeInsets.all(15.0), child: DidvanButton( onPressed: () { context .read() .putFavourites(context); context .read() .putTime(context); }, title: 'ذخیره تغییرات', imagepath: 'lib/assets/icons/verify.svg', ), ), ], ), ) : const SizedBox(), ), ), ], ), ); } } class _CategoryExpansionGroup extends StatefulWidget { final String title; final List items; final CustomizeCategoryState state; const _CategoryExpansionGroup({ required this.title, required this.items, required this.state, }); @override State<_CategoryExpansionGroup> createState() => _CategoryExpansionGroupState(); } class _CategoryExpansionGroupState extends State<_CategoryExpansionGroup> { bool _isExpanded = true; @override Widget build(BuildContext context) { final expansionColor = Theme.of(context).colorScheme.focused; final grayTextColor = Theme.of(context).colorScheme.caption; return Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ GestureDetector( onTap: () { setState(() { _isExpanded = !_isExpanded; }); }, child: Container( decoration: BoxDecoration( color: expansionColor, borderRadius: BorderRadius.circular(16), ), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( widget.title, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.normal, color: DesignConfig.isDark ? Theme.of(context).colorScheme.caption : const Color.fromARGB(255, 41, 41, 41), ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 8), AnimatedRotation( turns: _isExpanded ? 0.5 : 0, duration: const Duration(milliseconds: 200), child: SvgPicture.asset( 'lib/assets/icons/arrow-down.svg', color: DesignConfig.isDark ? Theme.of(context).colorScheme.caption : const Color.fromARGB(255, 41, 41, 41), ), ), ], ), ), ), AnimatedCrossFade( duration: const Duration(milliseconds: 200), crossFadeState: _isExpanded ? CrossFadeState.showFirst : CrossFadeState.showSecond, firstChild: Column( children: [ const SizedBox(height: 12), ...widget.items.map((item) { return Padding( padding: const EdgeInsets.only(bottom: 12), child: Container( height: 70, padding: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), color: DesignConfig.isDark ? null : Theme.of(context).colorScheme.surface, ), child: Column( children: [ Center( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: Text( item.name!, style: Theme.of(context) .textTheme .bodyMedium ?.copyWith( color: grayTextColor, fontWeight: FontWeight.w500, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ), Directionality( textDirection: TextDirection.ltr, child: CupertinoSwitch( value: item.selected ?? false, // ignore: deprecated_member_use activeColor: Theme.of(context).colorScheme.primary, onChanged: (value) { setState(() { item.selected = value; }); widget.state.update(); }, ), ), ], ), ), const SizedBox( height: 15, ), const DidvanDivider( verticalPadding: 5, ) ], ), ), ); }).toList(), ], ), secondChild: const SizedBox.shrink(), ), ], ), ); } }