24-02-1403 / Rhmn / changes: Customize category Create.

This commit is contained in:
OkaykOrhmn 2024-05-13 17:08:58 +03:30
parent 08d5564422
commit 48ccf55040
20 changed files with 1269 additions and 11 deletions

View File

@ -33,6 +33,9 @@ class Assets {
static String get stockStatCat =>
'$_baseFeaturesPath/stock-$_themeSuffix.svg';
static String get note =>
'$_baseFeaturesPath/note.svg';
static String get verticalLogoWithText =>
'$_baseLogosPath/logo-vertical-$_themeSuffix.svg';
static String get horizontalLogoWithText =>

View File

@ -0,0 +1,23 @@
class FavoritesResponse {
int? id;
String? name;
bool? selected;
FavoritesResponse({this.id, this.name, this.selected});
FavoritesResponse.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
selected = json['selected'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['name'] = this.name;
data['selected'] = this.selected;
return data;
}
}

11
lib/models/day_time.dart Normal file
View File

@ -0,0 +1,11 @@
class DayTime{
String hour;
String minute;
Meridiem meridiem;
DayTime({this.hour = "1", this.minute = "0", this.meridiem = Meridiem.AM});
}
enum SelectorType { meridiem, hour, minute }
enum Meridiem { AM, PM }

View File

@ -2,6 +2,8 @@ import 'package:didvan/views/authentication/authentication.dart';
import 'package:didvan/views/authentication/authentication_state.dart';
import 'package:didvan/views/comments/comments.dart';
import 'package:didvan/views/comments/comments_state.dart';
import 'package:didvan/views/customize_category/customize_category_state.dart';
import 'package:didvan/views/customize_category/favorites_step.dart';
import 'package:didvan/views/direct/direct.dart';
import 'package:didvan/views/direct/direct_state.dart';
import 'package:didvan/views/hashtag/hashtag.dart';
@ -20,6 +22,7 @@ import 'package:didvan/views/news/news.dart';
import 'package:didvan/views/news/news_details/news_details.dart';
import 'package:didvan/views/news/news_details/news_details_state.dart';
import 'package:didvan/views/news/news_state.dart';
import 'package:didvan/views/notification_time/notification_time_state.dart';
import 'package:didvan/views/podcasts/podcasts.dart';
import 'package:didvan/views/profile/profile.dart';
import 'package:didvan/views/radar/radar.dart';
@ -47,6 +50,9 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../views/customize_category/notification_status_step.dart';
import '../views/notification_time/notification_time.dart';
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
@ -54,6 +60,28 @@ class RouteGenerator {
return _createRoute(
const Splash(),
);
case Routes.notificationTime:
return _createRoute(
ChangeNotifierProvider<NotificationTimeState>(
create: (context) => NotificationTimeState(),
child: NotificationTime(
pageData: settings.arguments as Map<String, dynamic>,
)),
);
case Routes.favouritesStep:
return _createRoute(
ChangeNotifierProvider<CustomizeCategoryState>(
create: (context) => CustomizeCategoryState(),
child: FavoritesStep()),
);
case Routes.notificationStatusStep:
return _createRoute(
ChangeNotifierProvider<CustomizeCategoryState>(
create: (context) => CustomizeCategoryState(),
child: NotificationStatusStep()),
);
case Routes.authenticaion:
return _createRoute(
ChangeNotifierProvider<AuthenticationState>(
@ -263,7 +291,8 @@ class RouteGenerator {
size: Size(
deviceSize.height * 9 / 16,
deviceSize.height,
), textScaler: const TextScaler.linear(1.0),
),
textScaler: const TextScaler.linear(1.0),
),
child: Container(
color: Theme.of(context).colorScheme.background,
@ -273,7 +302,8 @@ class RouteGenerator {
);
}
return MediaQuery(
data: MediaQuery.of(context).copyWith(textScaler: const TextScaler.linear(1.0)),
data: MediaQuery.of(context)
.copyWith(textScaler: const TextScaler.linear(1.0)),
child: Container(
color: Theme.of(context).colorScheme.surface,
child: SafeArea(

View File

@ -25,4 +25,7 @@ class Routes {
static const String hashtag = '/hashtag';
static const String statGeneral = '/general';
static const String stock = '/stock';
static const String favouritesStep = '/favourites-step';
static const String notificationStatusStep = '/notification-status-step';
static const String notificationTime = '/notification-time';
}

View File

@ -1,8 +1,10 @@
import 'dart:convert';
import 'dart:developer';
import 'package:didvan/services/storage/storage.dart';
// ignore: depend_on_referenced_packages
import 'package:http/http.dart' as http;
// ignore: depend_on_referenced_packages
import 'package:http_parser/http_parser.dart' as parser;
import 'package:permission_handler/permission_handler.dart';
@ -11,10 +13,12 @@ class RequestService {
static late String token;
int? statusCode;
dynamic data(String s){
dynamic data(String s) {
return _body?[s] ?? const {};
}
Map<String, dynamic> get result => _body?['result'] ?? const {};
Map<String, dynamic> get errors => _body?['errors'] ?? const {};
String errorMessage =
@ -39,7 +43,11 @@ class RequestService {
}) {
if (body != null) _requestBody = body;
if (requestHeaders != null) _headers.addAll(requestHeaders);
if (useAutherization) _headers.addAll({'Authorization': 'Bearer $token'});
// if (useAutherization) _headers.addAll({'Authorization': 'Bearer $token'});
_headers.addAll({
'Authorization':
'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NCwicm9sZUlkIjo0LCJhcHBJZCI6MCwiaWF0IjoxNzEzOTM1NzkwfQ.i-SO9tLy0M9j-_C2Wh8tdp01vtYGlDZIBFPygglHQF0'
});
if (body != null) _requestBody = body;
}

View File

@ -187,6 +187,9 @@ class RequestHelper {
static String mark(int id, String type) => '$baseUrl/$type/$id/mark';
static String tracking(int id, String type) => '$baseUrl/$type/$id/tracking';
static String comments(int id, String type) => '$baseUrl/$type/$id/comments/v2';
static String favourites() => '$baseUrl/user/favorites';
static String notificationStatus() => '$baseUrl/user/notification/status';
static String notificationTime() => '$baseUrl/user/notification/time';
static String usersMentions(String search) => '$baseUrl/comment/user?search=$search';
static String feedback(int id, int commentId, String type) =>
'$baseUrl/$type/$id/comments/$commentId/feedback/v2';

View File

@ -1,8 +1,41 @@
import 'package:didvan/config/design_config.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:persian_datetime_picker/persian_datetime_picker.dart';
import '../models/day_time.dart';
class DateTimeUtils {
static DayTime handleDayTime(String dayTime) {
DateFormat format = DateFormat("HH:mm");
DateTime dateTime = format.parse(dayTime);
int _selectedTimeHour = dateTime.hour;
String _selectedTimeMinute = dateTime.minute.toString();
Meridiem _selectedMeridiem = Meridiem.AM;
if (_selectedTimeHour > 12) {
_selectedTimeHour -= 12;
_selectedMeridiem = Meridiem.PM;
} else if (_selectedTimeHour == 0) {
_selectedTimeHour = 24;
_selectedMeridiem = Meridiem.AM;
}
return DayTime(
hour: _selectedTimeHour.toString(),
minute: _selectedTimeMinute,
meridiem: _selectedMeridiem);
}
static String handleDayTimeReverse(DayTime dayTime) {
String h = dayTime.hour;
String m = dayTime.minute.padLeft(2, "0");
if (dayTime.meridiem == Meridiem.PM) {
h = (int.parse(h) + 12).toString().padLeft(2, "0");
}
return "$h:$m";
}
static TimeOfDay stringToTimeOfDay(String input) => TimeOfDay(
hour: int.parse(input.split(':')[0]),
minute: int.parse(input.split(':')[1]),
@ -58,6 +91,38 @@ class DateTimeUtils {
return result?.toDateTime().toString();
}
static Future<String?> showCupertinoDatePicker({
String? initialDate,
String? startDate,
String? endDate,
}) async {
final initDate = initialDate == null ? null : DateTime.parse(initialDate);
final initialJalali = Jalali.fromDateTime(initDate ?? DateTime.now());
final firstDate = Jalali.fromDateTime(
startDate == null ? DateTime(2021) : DateTime.parse(startDate),
);
final lastDate = Jalali.fromDateTime(
endDate == null ? DateTime.now() : DateTime.parse(endDate),
);
final Jalali? result = await showPersianDatePicker(
context: DesignConfig.context!,
initialDate: initialJalali,
firstDate: firstDate,
lastDate: lastDate,
builder: (context, child) => Theme(
data: Theme.of(context).copyWith(
textTheme: Theme.of(context).textTheme.copyWith(
headlineMedium: Theme.of(context).textTheme.displaySmall)),
child: child!,
),
);
return result?.toDateTime().toString();
}
static String timeWithAmPm(String input) {
final dateTime = utcToLocalTime(input);
bool isAm = true;

View File

@ -0,0 +1,121 @@
import 'package:didvan/models/customize_categories/favorites_response.dart';
import 'package:didvan/models/enums.dart';
import 'package:flutter/cupertino.dart';
import '../../providers/core.dart';
import '../../routes/routes.dart';
import '../../services/network/request.dart';
import '../../services/network/request_helper.dart';
class CustomizeCategoryState extends CoreProvier {
CustomizeCategoryState() {
appState = AppState.idle;
}
List<int> selectedFavIds = [];
List<int> selectedNotifIds = [];
final List<FavoritesResponse> faves = [];
final List<FavoritesResponse> notifs = [];
Future<void> getFavourites() async {
appState = AppState.busy;
final service = RequestService(
RequestHelper.favourites(),
);
await service.httpGet();
if (service.isSuccess) {
faves.clear();
final favourites = service.data('types');
for (var i = 0; i < favourites.length; i++) {
faves.add(FavoritesResponse.fromJson(favourites[i]));
}
selectedFavIds.clear();
faves.forEach((element) {
if (element.selected!) {
selectedFavIds.add(element.id!);
}
});
appState = AppState.idle;
} else {
appState = AppState.failed;
}
update();
}
Future<void> getNotificationStatuses() async {
appState = AppState.busy;
final service = RequestService(
RequestHelper.notificationStatus(),
);
await service.httpGet();
if (service.isSuccess) {
final notificationsStatuses = service.data('types');
for (var i = 0; i < notificationsStatuses.length; i++) {
notifs.add(FavoritesResponse.fromJson(notificationsStatuses[i]));
}
selectedNotifIds.clear();
notifs.forEach((element) {
if (element.selected!) {
selectedNotifIds.add(element.id!);
}
});
appState = AppState.idle;
} else {
appState = AppState.failed;
}
update();
}
Future<void> putFavourites(BuildContext context) async {
// appState = AppState.busy;
final favoritesRequest = {"favorites": selectedFavIds};
final service = RequestService(
RequestHelper.favourites(),
body: favoritesRequest,
);
await service.put();
if (service.isSuccess) {
Navigator.of(context).pushNamed(
Routes.notificationStatusStep,
);
appState = AppState.idle;
return;
}
appState = AppState.failed;
}
Future<void> putNotificationsStatuses(BuildContext context) async {
// appState = AppState.busy;
final notificationsStatusesRequest = {"notification": selectedNotifIds};
final service = RequestService(
RequestHelper.notificationStatus(),
body: notificationsStatusesRequest,
);
await service.put();
if (service.isSuccess) {
Navigator.of(context).pop();
Navigator.of(context).pop();
appState = AppState.idle;
return;
}
appState = AppState.failed;
}
Future<void> changeSelected(int index, List<FavoritesResponse> list,List<int> selectedIds) async {
list[index].selected = !list[index].selected!;
selectedIds.clear();
for (var ls in list) {
if (ls.selected!) {
selectedIds.add(ls.id!);
}
}
update();
}
}

View File

@ -0,0 +1,128 @@
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/users_mention.dart';
import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/views/customize_category/customize_category_state.dart';
import 'package:didvan/views/customize_category/widgets/customize_category_checkbox.dart';
import 'package:didvan/views/widgets/didvan/checkbox.dart';
import 'package:didvan/views/widgets/didvan/scaffold.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../config/design_config.dart';
import '../../constants/assets.dart';
import '../widgets/didvan/button.dart';
import '../widgets/shimmer_placeholder.dart';
import '../widgets/state_handlers/empty_state.dart';
import '../widgets/state_handlers/sliver_state_handler.dart';
import '../widgets/user_mention.dart';
class FavoritesStep extends StatefulWidget {
const FavoritesStep({super.key});
@override
State<FavoritesStep> createState() => _FavoritesStepState();
}
class _FavoritesStepState extends State<FavoritesStep> {
late CustomizeCategoryState state;
@override
void initState() {
super.initState();
state = context.read<CustomizeCategoryState>();
Future.delayed(
Duration.zero,
() => state.getFavourites(),
);
}
@override
Widget build(BuildContext context) {
state = context.read<CustomizeCategoryState>();
return Material(
child: Stack(
children: [
DidvanScaffold(
appBarData: AppBarData(
hasBack: true,
title: 'شخصی‌سازی برنامه',
),
physics: const BouncingScrollPhysics(),
hidePlayer: true,
padding: const EdgeInsets.all(16).copyWith(bottom: 92),
showSliversFirst: false,
slivers: [
Consumer<CustomizeCategoryState>(
builder: (context, state, child) =>
SliverStateHandler<CustomizeCategoryState>(
onRetry: state.getFavourites,
state: state,
itemPadding: const EdgeInsets.only(bottom: 16),
childCount: state.faves.length,
placeholder: ShimmerPlaceholder(
height: 48,
width: MediaQuery.sizeOf(context).width,
borderRadius: DesignConfig.mediumBorderRadius,
),
centerEmptyState: false,
enableEmptyState: false,
emptyState: EmptyState(
asset: Assets.emptyChat,
title: 'اولین نظر را بنویسید...',
),
builder: (context, state, index) => Center(
child: Container(
height: 48,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
width: 1,
color: state.faves[index].selected!
? Theme.of(context)
.colorScheme
.focusedBorder
: Theme.of(context).colorScheme.cardBorder),
color: state.faves[index].selected!
? Theme.of(context).colorScheme.focused
: Theme.of(context).colorScheme.cardBorder),
child: CustomizeCategoryCheckbox(
title: state.faves[index].name!,
value: state.faves[index].selected,
color: Theme.of(context).colorScheme.title,
onChanged: (val) {
state.changeSelected(index, state.faves,state.selectedFavIds);
})),
),
),
),
],
children: [
DidvanText(
"کاربر گرامی لطفا دسته‌بندی‌های مورد علاقه خود را انتخاب کنید",
style: Theme.of(context).textTheme.bodyMedium,
)
],
),
Positioned(
bottom: 40,
left: 16,
right: 16,
child: DidvanButton(
onPressed: state.appState == AppState.idle
? () async {
await state.putFavourites(context);
}
: null,
title: 'تایید',
style: ButtonStyleMode.primary,
))
],
),
);
}
}

View File

@ -0,0 +1,136 @@
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/users_mention.dart';
import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/views/customize_category/customize_category_state.dart';
import 'package:didvan/views/customize_category/widgets/customize_category_checkbox.dart';
import 'package:didvan/views/widgets/didvan/checkbox.dart';
import 'package:didvan/views/widgets/didvan/scaffold.dart';
import 'package:didvan/views/widgets/didvan/switch.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../config/design_config.dart';
import '../../constants/assets.dart';
import '../widgets/didvan/button.dart';
import '../widgets/shimmer_placeholder.dart';
import '../widgets/state_handlers/empty_state.dart';
import '../widgets/state_handlers/sliver_state_handler.dart';
import '../widgets/user_mention.dart';
class NotificationStatusStep extends StatefulWidget {
const NotificationStatusStep({super.key});
@override
State<NotificationStatusStep> createState() => _NotificationStatusStepState();
}
class _NotificationStatusStepState extends State<NotificationStatusStep> {
late CustomizeCategoryState state;
@override
void initState() {
super.initState();
state = context.read<CustomizeCategoryState>();
Future.delayed(
Duration.zero,
() => state.getNotificationStatuses(),
);
}
@override
Widget build(BuildContext context) {
state = context.watch<CustomizeCategoryState>();
return Material(
child: Stack(
children: [
DidvanScaffold(
appBarData: AppBarData(
hasBack: true,
title: 'شخصی‌سازی برنامه',
),
physics: const BouncingScrollPhysics(),
hidePlayer: true,
padding: const EdgeInsets.all(16),
showSliversFirst: false,
slivers: [
Consumer<CustomizeCategoryState>(
builder: (context, state, child) =>
SliverStateHandler<CustomizeCategoryState>(
onRetry: state.getNotificationStatuses,
state: state,
itemPadding: const EdgeInsets.only(bottom: 16),
childCount: state.notifs.length,
placeholder: ShimmerPlaceholder(
height: 48,
width: MediaQuery.sizeOf(context).width,
borderRadius: DesignConfig.mediumBorderRadius,
),
centerEmptyState: false,
enableEmptyState: false,
emptyState: EmptyState(
asset: Assets.emptyChat,
title: 'اولین نظر را بنویسید...',
),
builder: (context, state, index) => Column(
children: [
Center(
child: Column(
children: [
DidvanSwitch(
value: state.notifs[index].selected!,
title: state.notifs[index].name!,
onChanged: (val) {
state.changeSelected(index, state.notifs,state.selectedNotifIds);
}),
index != state.notifs.length - 1
? Padding(
padding: const EdgeInsets.only(
bottom: 16, top: 16),
child: Divider(
color:
Theme.of(context).colorScheme.border,
height: 1,
),
)
: const SizedBox()
],
),
),
SizedBox(
height: index == state.notifs.length - 1 ? 92 : 0,
)
],
),
),
),
],
children: [
DidvanText(
"همیشه با اعلانات شخصی‌سازی شده خود بروز بمانید",
style: Theme.of(context).textTheme.bodyMedium,
)
],
),
Positioned(
bottom: 40,
left: 16,
right: 16,
child: DidvanButton(
onPressed: state.appState == AppState.idle
? () async {
await state.putNotificationsStatuses(context);
}
: null,
title: 'تایید',
style: ButtonStyleMode.primary,
))
],
),
);
}
}

View File

@ -0,0 +1,79 @@
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/views/widgets/animated_visibility.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class CustomizeCategoryCheckbox extends StatefulWidget {
final String title;
final bool? value;
final double size;
final Color? color;
final void Function(bool value) onChanged;
const CustomizeCategoryCheckbox({
Key? key,
required this.title,
required this.value,
required this.onChanged, this.size = 15, this.color,
}) : super(key: key);
@override
State<CustomizeCategoryCheckbox> createState() => _CustomizeCategoryCheckboxState();
}
class _CustomizeCategoryCheckboxState extends State<CustomizeCategoryCheckbox> {
bool _value = false;
@override
void initState() {
if (widget.value != null) {
_value = widget.value!;
}
super.initState();
}
@override
Widget build(BuildContext context) {
Color? _color = widget.color;
_color ??= Theme.of(context).colorScheme.caption;
return GestureDetector(
onTap: () {
setState(() {
_value = !_value;
});
widget.onChanged(_value);
},
child: Container(
color: Colors.transparent,
child: Row(
children: [
Transform.scale(
scale: widget.size ==15 ? 1 : 0.8,
child: Container(
width: 18,
height: 18,
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(6),
border: Border.all(width: 1.0, color: _value? _color: Theme.of(context).colorScheme.caption),
),
margin: EdgeInsets.all(8),
child: AnimatedVisibility(
isVisible: _value,
duration: DesignConfig.mediumAnimationDuration,
child: Center(child: Icon(Icons.check,size: 12,color: _value? _color: Theme.of(context).colorScheme.caption,))
),
),
), DidvanText(widget.title,fontSize: widget.size,color: _value? _color: Theme.of(context).colorScheme.caption, ),
],
),
),
);
}
}

View File

@ -0,0 +1,63 @@
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/views/widgets/animated_visibility.dart';
import 'package:didvan/views/widgets/didvan/switch.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class CustomizeCategorySwitch extends StatefulWidget {
final String title;
final bool? value;
final double size;
final Color? color;
final void Function(bool value) onChanged;
const CustomizeCategorySwitch({
Key? key,
required this.title,
required this.value,
required this.onChanged,
this.size = 15,
this.color,
}) : super(key: key);
@override
State<CustomizeCategorySwitch> createState() =>
_CustomizeCategorySwitchState();
}
class _CustomizeCategorySwitchState extends State<CustomizeCategorySwitch> {
bool _value = false;
@override
void initState() {
if (widget.value != null) {
_value = widget.value!;
}
super.initState();
}
@override
Widget build(BuildContext context) {
Color? _color = widget.color;
_color ??= Theme.of(context).colorScheme.caption;
return GestureDetector(
onTap: () {
setState(() {
_value = !_value;
});
widget.onChanged(_value);
},
child: Container(
color: Colors.transparent,
child: Transform.scale(
scale: widget.size == 15 ? 1 : 0.8,
child: DidvanSwitch(
value: _value, title: widget.title, onChanged: (val) {}),
),
),
);
}
}

View File

@ -0,0 +1,123 @@
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/models/day_time.dart';
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/users_mention.dart';
import 'package:didvan/models/view/app_bar_data.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/notification_time/widgets/custom_cupertino_date_picker.dart';
import 'package:didvan/views/customize_category/widgets/customize_category_checkbox.dart';
import 'package:didvan/views/widgets/didvan/checkbox.dart';
import 'package:didvan/views/widgets/didvan/scaffold.dart';
import 'package:didvan/views/widgets/didvan/switch.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:persian_datetime_picker/persian_datetime_picker.dart';
import 'package:provider/provider.dart';
import '../../constants/assets.dart';
import '../widgets/date_picker_button.dart';
import '../widgets/didvan/button.dart';
import '../widgets/state_handlers/empty_state.dart';
import '../widgets/state_handlers/sliver_state_handler.dart';
import '../widgets/user_mention.dart';
class NotificationTime extends StatefulWidget {
final Map<String, dynamic> pageData;
const NotificationTime({super.key, required this.pageData});
@override
State<NotificationTime> createState() => _NotificationTimeState();
}
class _NotificationTimeState extends State<NotificationTime> {
late NotificationTimeState state;
@override
void initState() {
super.initState();
state = context.read<NotificationTimeState>();
state.onTimeChanged = widget.pageData['onTimeChanged'];
Future.delayed(
Duration.zero,
() => state.getTime(),
);
}
@override
Widget build(BuildContext context) {
state = context.watch<NotificationTimeState>();
return Material(
child: Stack(
children: [
DidvanScaffold(
appBarData: AppBarData(
hasBack: true,
title: 'شخصی‌سازی برنامه',
),
physics: const BouncingScrollPhysics(),
hidePlayer: true,
padding: const EdgeInsets.all(16),
showSliversFirst: false,
backgroundColor: Theme.of(context).colorScheme.background,
slivers: [],
children: [
Consumer<NotificationTimeState>(
builder: (context, state, child) => Column(
children: [
DidvanText(
"لطفا زمان دریافت اعلانات خود را مشخص کنید",
style: Theme.of(context).textTheme.bodyMedium,
),
state.appState == AppState.idle
? Container(
margin: EdgeInsets.all(24),
height: 210,
child: CustomCupertinoDatePicker(
itemExtent: 64,
selectedTime: state.selectedTime,
selectedStyle: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(
color: Theme.of(context)
.colorScheme
.white),
unselectedStyle:
Theme.of(context).textTheme.titleSmall,
disabledStyle: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(
color: Theme.of(context)
.colorScheme
.disabledText),
onSelectedItemChanged: (date) {
state.selectedTime = date;
}),
)
: SizedBox(),
],
),
)
],
),
Positioned(
bottom: 40,
left: 16,
right: 16,
child: DidvanButton(
onPressed: () {
state.putTime(context);
},
title: 'تایید',
style: ButtonStyleMode.primary,
))
],
),
);
}
}

View File

@ -0,0 +1,61 @@
import 'package:didvan/models/enums.dart';
import 'package:didvan/providers/core.dart';
import 'package:flutter/cupertino.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import '../../models/day_time.dart';
import '../../routes/routes.dart';
import '../../services/network/request.dart';
import '../../services/network/request_helper.dart';
import '../../utils/date_time.dart';
import '../profile/general_settings/settings_state.dart';
class NotificationTimeState extends CoreProvier {
late DayTime selectedTime;
late void Function( ) onTimeChanged;
Future<void> getTime() async {
appState = AppState.busy;
final service = RequestService(
RequestHelper.notificationTime(),
);
await service.httpGet();
if (service.isSuccess) {
final time = service.data('time');
selectedTime = DateTimeUtils.handleDayTime(time);
appState = AppState.idle;
} else {
appState = AppState.failed;
}
update();
}
Future<void> putTime(BuildContext context) async {
appState = AppState.busy;
final favoritesRequest = {
"time": DateTimeUtils.handleDayTimeReverse(selectedTime)
};
final service = RequestService(
RequestHelper.notificationTime(),
body: favoritesRequest,
);
await service.put();
if (service.isSuccess) {
onTimeChanged();
// Navigator.of(context).pushNamed(
// Routes.notificationStatusStep,
// );
appState = AppState.idle;
return;
}
appState = AppState.failed;
}
}

View File

@ -0,0 +1,341 @@
import 'package:day_night_time_picker/lib/state/time.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:intl/intl.dart';
import '../../../models/day_time.dart';
import '../../../utils/date_time.dart';
class CustomCupertinoDatePicker extends StatefulWidget {
final double itemExtent;
final void Function(DayTime)
onSelectedItemChanged; // Text style of selected item
final TextStyle? selectedStyle; // Text style of unselected item
final TextStyle? unselectedStyle; // Text style of disabled item
final TextStyle? disabledStyle; // Minimum selectable date
final DayTime? selectedTime;
const CustomCupertinoDatePicker({
Key? key,
required this.itemExtent,
required this.onSelectedItemChanged,
this.selectedTime,
this.selectedStyle,
this.unselectedStyle,
this.disabledStyle,
}) : super(key: key);
@override
State<CustomCupertinoDatePicker> createState() =>
_CustomCupertinoDatePickerState();
}
class _CustomCupertinoDatePickerState extends State<CustomCupertinoDatePicker> {
late DayTime selectedTime = DateTimeUtils.handleDayTime(
"${DateTime.now().hour.toString()}:${DateTime.now().minute.toString()}");
late int _selectedMeridiemIndex;
late int _selectedHourIndex;
late int _selectedMinuteIndex;
late final FixedExtentScrollController _meridiemScrollController;
late final FixedExtentScrollController _hourScrollController;
late final FixedExtentScrollController _minuteScrollController;
final _meridiem = [
'قبل از ظهر',
'بعد از ظهر',
];
final _timeH = [];
final _timeM = [];
@override
void initState() {
super.initState();
for (int i = 1; i < 12 + 1; i++) {
String twoDigitNumber = i.toString().padLeft(2, '0');
_timeH.add(twoDigitNumber);
}
for (int i = 1; i < 12 + 1; i++) {
String twoDigitNumber = i.toString().padLeft(2, '0');
_timeH.add(twoDigitNumber);
}
for (int i = 0; i < 59 + 1; i++) {
String twoDigitNumber = i.toString().padLeft(2, '0');
_timeM.add(twoDigitNumber);
}
_meridiemScrollController = FixedExtentScrollController();
_hourScrollController = FixedExtentScrollController();
_minuteScrollController = FixedExtentScrollController();
if(widget.selectedTime !=null){
selectedTime = widget.selectedTime!;
}
_initDates();
}
void _initDates() {
_selectedMeridiemIndex = 0;
switch(selectedTime.meridiem){
case Meridiem.AM:
_selectedMeridiemIndex = 0;
_selectedHourIndex = int.parse(selectedTime.hour)-1;
break;
case Meridiem.PM:
_selectedMeridiemIndex = 1;
_selectedHourIndex = (int.parse(selectedTime.hour)+12)-1;
break;
}
_selectedMinuteIndex = int.parse(selectedTime.minute);
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollList(_meridiemScrollController, _selectedMeridiemIndex);
_scrollList(_hourScrollController, _selectedHourIndex);
_scrollList(_minuteScrollController, _selectedMinuteIndex);
});
}
void _scrollList(FixedExtentScrollController controller, int index) {
controller.animateToItem(
index,
curve: Curves.easeIn,
duration: const Duration(milliseconds: 300),
);
}
@override
void dispose() {
_meridiemScrollController.dispose();
_hourScrollController.dispose();
_minuteScrollController.dispose();
super.dispose();
}
void _onSelectedItemChanged(int index, SelectorType type) {
switch (type) {
case SelectorType.meridiem:
_selectedMeridiemIndex = index; // if month is changed to february &
if (_selectedMeridiemIndex == 0) {
if (_selectedHourIndex > 12 - 1) {
_selectedHourIndex -= 12;
_hourScrollController.jumpToItem(_selectedHourIndex);
}
} else {
if (_selectedHourIndex < 12 - 1) {
_selectedHourIndex += 12;
_hourScrollController.jumpToItem(_selectedHourIndex);
}
}
if(index == 0){
selectedTime.meridiem = Meridiem.AM;
}else{
selectedTime.meridiem = Meridiem.PM;
}
break;
case SelectorType.hour:
_selectedHourIndex = index; // if month is changed to february &
if (_selectedHourIndex > 12 - 1) {
_selectedMeridiemIndex = 1;
_meridiemScrollController.jumpToItem(_selectedMeridiemIndex);
} else {
_selectedMeridiemIndex = 0;
_meridiemScrollController.jumpToItem(_selectedMeridiemIndex);
}
selectedTime.hour = _timeH[index];
break;
case SelectorType.minute:
_selectedMinuteIndex = index;
selectedTime.minute = _timeM[index];
break;
}
setState(() {});
widget.onSelectedItemChanged(selectedTime!);
}
/// check if the given day, month or year index is disabled
bool _isDisabled(int index, SelectorType type) {
switch (type) {
case SelectorType.meridiem:
break;
case SelectorType.hour:
break;
case SelectorType.minute:
break;
}
return false;
}
@override
Widget build(BuildContext context) {
Color _shadow = Theme.of(context).colorScheme.background;
return Stack(
children: [
Positioned.fill(
child: Center(
child: Container(
height: 64,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius: BorderRadius.circular(18)),
child: const Padding(
padding: EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
SizedBox(),
VerticalDivider(
color: Colors.white,
),
VerticalDivider(
color: Colors.white,
),
SizedBox(),
],
),
),
),
),
),
Row(
children: [
Expanded(child: _meridiemSelector()),
Expanded(child: _minuteSelector()),
Expanded(child: _hourSelector()),
],
),
Positioned(
top: 0,
left: 0,
right: 0,
child: Container(
height: 32,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
_shadow.withOpacity(1),
_shadow.withOpacity(0.9),
_shadow.withOpacity(0.8),
_shadow.withOpacity(0.6),
_shadow.withOpacity(0.5),
_shadow.withOpacity(0.4),
])),
)),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
height: 32,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
_shadow.withOpacity(1),
_shadow.withOpacity(0.9),
_shadow.withOpacity(0.8),
_shadow.withOpacity(0.6),
_shadow.withOpacity(0.5),
_shadow.withOpacity(0.4),
])),
)),
],
);
}
Widget _selector({
required List<dynamic> values,
required int selectedValueIndex,
required bool Function(int) isDisabled,
required void Function(int) onSelectedItemChanged,
required FixedExtentScrollController scrollController,
}) {
return CupertinoPicker.builder(
childCount: values.length,
itemExtent: widget.itemExtent,
scrollController: scrollController,
squeeze: 1.45,
useMagnifier: false,
diameterRatio: 2.3,
magnification: 1.0,
offAxisFraction: 0.0,
selectionOverlay: const SizedBox(),
onSelectedItemChanged: onSelectedItemChanged,
itemBuilder: (context, index) => Container(
height: widget.itemExtent,
alignment: Alignment.center,
decoration: BoxDecoration(),
child: Text(
'${values[index]}',
style: index == selectedValueIndex
? widget.selectedStyle
: isDisabled(index)
? widget.disabledStyle
: widget.unselectedStyle,
),
),
);
}
Widget _meridiemSelector() {
return _selector(
values: _meridiem,
selectedValueIndex: _selectedMeridiemIndex,
scrollController: _meridiemScrollController,
isDisabled: (index) => _isDisabled(index, SelectorType.meridiem),
onSelectedItemChanged: (v) => _onSelectedItemChanged(
v,
SelectorType.meridiem,
),
);
}
Widget _hourSelector() {
return _selector(
values: _timeH,
selectedValueIndex: _selectedHourIndex,
scrollController: _hourScrollController,
isDisabled: (index) => _isDisabled(index, SelectorType.hour),
onSelectedItemChanged: (v) => _onSelectedItemChanged(
v,
SelectorType.hour,
),
);
}
Widget _minuteSelector() {
return _selector(
values: _timeM,
selectedValueIndex: _selectedMinuteIndex,
scrollController: _minuteScrollController,
isDisabled: (index) => _isDisabled(index, SelectorType.minute),
onSelectedItemChanged: (v) => _onSelectedItemChanged(
v,
SelectorType.minute,
),
);
}
}

View File

@ -23,6 +23,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import '../../../routes/routes.dart';
class GeneralSettings extends StatefulWidget {
const GeneralSettings({Key? key}) : super(key: key);
@ -41,6 +43,15 @@ class _GeneralSettingsState extends State<GeneralSettings> {
int _intervalStart = 0;
int _intervalEnd = 24;
@override
void initState() {
final state = context.read<GeneralSettingsState>();
Future.delayed(
Duration.zero,
() => state.getTime(),
);
}
@override
Widget build(BuildContext context) {
return Consumer<GeneralSettingsState>(
@ -54,13 +65,39 @@ class _GeneralSettingsState extends State<GeneralSettings> {
DidvanCard(
child: MenuOption(
title: 'زمان دریافت اعلان',
onTap: () => _pickTimeRange(context),
onTap: () => Navigator.of(context).pushNamed(
Routes.notificationTime,
arguments: {
'onTimeChanged': () => Future.delayed(
Duration.zero,
() => state.getTime(),
)
},
),
icon: DidvanIcons.notification_regular,
suffix: 'از${DateTimeUtils.normalizeTimeDuration(
Duration(minutes: state.notificationTimeRange[0]),
)} تا ${DateTimeUtils.normalizeTimeDuration(
Duration(minutes: state.notificationTimeRange[1]),
)}',
suffix: state.time,
// suffix: 'از${DateTimeUtils.normalizeTimeDuration(
// Duration(minutes: state.notificationTimeRange[0]),
// )} تا ${DateTimeUtils.normalizeTimeDuration(
// Duration(minutes: state.notificationTimeRange[1]),
// )}',
),
),
const SizedBox(
height: 16,
),
DidvanCard(
child: MenuOption(
title: 'شخصی سازی محتوا',
onTap: () => Navigator.of(context).pushNamed(
Routes.favouritesStep,
),
icon: DidvanIcons.notification_regular,
// suffix: 'از${DateTimeUtils.normalizeTimeDuration(
// Duration(minutes: state.notificationTimeRange[0]),
// )} تا ${DateTimeUtils.normalizeTimeDuration(
// Duration(minutes: state.notificationTimeRange[1]),
// )}',
),
),
const ItemTitle(

View File

@ -13,6 +13,26 @@ class GeneralSettingsState extends CoreProvier {
String _fontFamily = 'Dana-FA';
double _fontSizeScale = 1;
String _brightness = 'light';
String time = "";
Future<void> getTime() async {
appState = AppState.busy;
final service = RequestService(
RequestHelper.notificationTime(),
);
await service.httpGet();
if (service.isSuccess) {
time = service.data('time');
appState = AppState.idle;
} else {
appState = AppState.failed;
}
update();
}
set notificationTimeRange(List<int> value) {
_notificationTimeRange = value;

View File

@ -69,3 +69,6 @@ class _DidvanCheckboxState extends State<DidvanCheckbox> {
);
}
}

View File

@ -53,7 +53,7 @@ dependencies:
universal_html: ^2.0.8
record: ^4.4.3
record_web: ^0.5.0
persian_datetime_picker: ^2.4.0
persian_datetime_picker: ^2.6.0
persian_number_utility: ^1.1.1
bot_toast: ^4.0.1
flutter_secure_storage: ^8.0.0