495 lines
20 KiB
Dart
495 lines
20 KiB
Dart
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<String, dynamic> pageData;
|
|
|
|
const NotificationSettings({super.key, required this.pageData});
|
|
|
|
@override
|
|
State<NotificationSettings> createState() => _NotificationSettingsState();
|
|
}
|
|
|
|
class _NotificationSettingsState extends State<NotificationSettings>
|
|
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<NotificationTimeState>();
|
|
customizeState = context.read<CustomizeCategoryState>();
|
|
|
|
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<Offset>(
|
|
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<CustomizeCategoryState>(
|
|
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<NotificationTimeState>(
|
|
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: state.isAnytime,
|
|
onTimeChanged: (newTime) {
|
|
state.selectedTime = newTime;
|
|
state.update();
|
|
},
|
|
),
|
|
// Padding(
|
|
// padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
// child: Container(
|
|
// padding: const EdgeInsets.all(16),
|
|
// decoration: BoxDecoration(
|
|
// color: Theme.of(context).colorScheme.surface,
|
|
// border: Border.all(
|
|
// color: Theme.of(context).colorScheme.border,
|
|
// width: 1),
|
|
// borderRadius: BorderRadius.circular(18)),
|
|
// child: DidvanSwitch(
|
|
// value: state.isAnytime,
|
|
// title: "دریافت آنی اعلانات",
|
|
// onChanged: (val) {
|
|
// state.isAnytime = val;
|
|
// state.update();
|
|
// },
|
|
// ),
|
|
// ),
|
|
// ),
|
|
const SizedBox(height: 16),
|
|
Padding(
|
|
padding: const EdgeInsets.all(15.0),
|
|
child: DidvanButton(
|
|
onPressed: () {
|
|
context
|
|
.read<CustomizeCategoryState>()
|
|
.putFavourites(context);
|
|
context
|
|
.read<NotificationTimeState>()
|
|
.putTime(context);
|
|
},
|
|
title: 'ذخیره تغییرات',
|
|
imagepath: 'lib/assets/icons/verify.svg',
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
: const SizedBox(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _CategoryExpansionGroup extends StatefulWidget {
|
|
final String title;
|
|
final List<dynamic> 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(),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|