feat: Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/data/enums/state_status.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_box_decorations.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_app_bar.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_loading_overlay.dart';
|
||||
import 'package:krow/features/profile/schedule/domain/bloc/schedule_bloc.dart';
|
||||
import 'package:krow/features/profile/schedule/presentation/widgets/schedule_calendar.dart';
|
||||
import 'package:krow/features/profile/schedule/presentation/widgets/calendar_button_section_widget.dart';
|
||||
import 'package:krow/features/profile/schedule/presentation/widgets/calendar_edit_slot.dart';
|
||||
import 'package:krow/features/profile/schedule/presentation/widgets/calendar_footer_widget.dart';
|
||||
import 'package:krow/features/profile/schedule/presentation/widgets/calendar_slot_list_widget.dart';
|
||||
|
||||
@RoutePage()
|
||||
class ScheduleScreen extends StatelessWidget implements AutoRouteWrapper {
|
||||
ScheduleScreen({super.key});
|
||||
|
||||
final OverlayPortalController _controller = OverlayPortalController();
|
||||
|
||||
bool _calendarBuildWhen(ScheduleState previous, ScheduleState current) {
|
||||
return previous.isInEditMode != current.isInEditMode ||
|
||||
previous.isScheduleAddMode != current.isScheduleAddMode ||
|
||||
previous.selectedDates != current.selectedDates ||
|
||||
previous.schedules != current.schedules;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrappedRoute(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => ScheduleBloc()..add(const ScheduleInitEvent()),
|
||||
child: this,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: KwAppBar(titleText: 'schedule'.tr()),
|
||||
body: KwLoadingOverlay(
|
||||
controller: _controller,
|
||||
child: BlocListener<ScheduleBloc, ScheduleState>(
|
||||
listenWhen: (previous, current) => previous.status != current.status,
|
||||
listener: (context, state) {
|
||||
if (state.status == StateStatus.loading) {
|
||||
_controller.show();
|
||||
} else {
|
||||
_controller.hide();
|
||||
}
|
||||
},
|
||||
child: ListView(
|
||||
primary: false,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 16,
|
||||
),
|
||||
children: [
|
||||
_buildPageTitle(),
|
||||
const Gap(12),
|
||||
Container(
|
||||
decoration: KwBoxDecorations.primaryLight8.copyWith(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(12),
|
||||
topRight: Radius.circular(12),
|
||||
),
|
||||
),
|
||||
child: BlocBuilder<ScheduleBloc, ScheduleState>(
|
||||
buildWhen: _calendarBuildWhen,
|
||||
builder: (context, state) {
|
||||
return ScheduleCalendar(
|
||||
disabled: state.isInEditMode || state.isScheduleAddMode,
|
||||
selectedDates: state.selectedDates,
|
||||
containsScheduleForDate: state.containsScheduleForDate,
|
||||
onDateSelected: (dates) {
|
||||
BlocProvider.of<ScheduleBloc>(context).add(
|
||||
ScheduleEventSelectDates(dates: dates),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
BlocBuilder<ScheduleBloc, ScheduleState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.isInEditMode != current.isInEditMode ||
|
||||
previous.selectedSchedules != current.selectedSchedules,
|
||||
builder: (context, state) {
|
||||
if (state.isInEditMode) {
|
||||
return const Column(
|
||||
children: [
|
||||
CalendarEditSlot(),
|
||||
CalendarButtonSectionWidget()
|
||||
],
|
||||
);
|
||||
}
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
CalendarFooterWidget(
|
||||
selectedDates: state.selectedDates,
|
||||
existedSlots: state.selectedSchedules,
|
||||
),
|
||||
CalendarSlotListWidget(
|
||||
selectedSchedules: state.selectedSchedules,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
RichText _buildPageTitle() {
|
||||
return RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '${'set_your_availability'.tr()} ',
|
||||
style: AppTextStyles.bodySmallMed.copyWith(
|
||||
color: AppColors.blackBlack,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: 'mark_days_and_times'.tr(),
|
||||
style: AppTextStyles.bodySmallReg.copyWith(
|
||||
color: AppColors.blackGray,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_button.dart';
|
||||
import 'package:krow/features/profile/schedule/domain/bloc/schedule_bloc.dart';
|
||||
|
||||
class CalendarButtonSectionWidget extends StatelessWidget {
|
||||
const CalendarButtonSectionWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const Gap(16),
|
||||
BlocSelector<ScheduleBloc, ScheduleState, bool>(
|
||||
selector: (state) =>
|
||||
state.tempSchedules.firstOrNull?.isViableForSaving ?? false,
|
||||
builder: (context, isEnabled) {
|
||||
return KwButton.primary(
|
||||
label: 'save_slots'.tr(),
|
||||
disabled: !isEnabled,
|
||||
onPressed: () {
|
||||
BlocProvider.of<ScheduleBloc>(context)
|
||||
.add(const ScheduleEventSave());
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
const Gap(12),
|
||||
KwButton.outlinedPrimary(
|
||||
label: 'cancel'.tr(),
|
||||
onPressed: () {
|
||||
BlocProvider.of<ScheduleBloc>(context)
|
||||
.add(const ScheduleEventCancel());
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:krow/core/presentation/gen/assets.gen.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_button.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_popup_menu.dart';
|
||||
import 'package:krow/features/profile/schedule/domain/bloc/schedule_bloc.dart';
|
||||
|
||||
class CalendarDataDetailSettingWidget extends StatefulWidget {
|
||||
const CalendarDataDetailSettingWidget({
|
||||
super.key,
|
||||
required this.selectedDate,
|
||||
});
|
||||
|
||||
final DateTime? selectedDate;
|
||||
|
||||
@override
|
||||
State<CalendarDataDetailSettingWidget> createState() =>
|
||||
_CalendarDataDetailSettingWidgetState();
|
||||
}
|
||||
|
||||
class _CalendarDataDetailSettingWidgetState
|
||||
extends State<CalendarDataDetailSettingWidget> {
|
||||
var mode = SelectionMode.empty;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedDate = widget.selectedDate;
|
||||
if (selectedDate == null) return const SizedBox.shrink();
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.graySecondaryFrame,
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(12),
|
||||
bottomRight: Radius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
'selected_date_details'.tr(),
|
||||
style: AppTextStyles.bodyMediumMed,
|
||||
),
|
||||
const Divider(
|
||||
color: AppColors.grayTintStroke,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
DateFormat('EEEE, MMMM d', context.locale.languageCode).format(selectedDate),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: AppTextStyles.headingH3,
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
_buildKwPopupMenu(context, selectedDate),
|
||||
],
|
||||
),
|
||||
const Gap(16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: KwButton.outlinedPrimary(
|
||||
label: 'cancel'.tr(),
|
||||
onPressed: () {
|
||||
context.read<ScheduleBloc>().add(
|
||||
const ScheduleEventChangeMode(
|
||||
isInScheduleAddMode: false,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
Expanded(
|
||||
child: KwButton.primary(
|
||||
disabled: mode == SelectionMode.empty,
|
||||
label: 'choose_time'.tr(),
|
||||
onPressed: () {
|
||||
BlocProvider.of<ScheduleBloc>(context).add(
|
||||
ScheduleEventChangeMode(
|
||||
isInEditScheduleMode: true,
|
||||
isWeekly: mode == SelectionMode.weekly,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
KwPopupMenu _buildKwPopupMenu(BuildContext context, DateTime selectedDate) {
|
||||
return KwPopupMenu(
|
||||
customButtonBuilder: (_, isOpened) {
|
||||
return _buildPopupButton(isOpened, selectedDate);
|
||||
},
|
||||
menuItems: [
|
||||
KwPopupMenuItem(
|
||||
icon: Assets.images.icons.calendarV2.svg(
|
||||
height: 16,
|
||||
width: 16,
|
||||
),
|
||||
title: 'date'.tr(),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
mode = SelectionMode.single;
|
||||
});
|
||||
},
|
||||
),
|
||||
KwPopupMenuItem(
|
||||
icon: Assets.images.icons.calendar.svg(
|
||||
height: 16,
|
||||
width: 16,
|
||||
),
|
||||
title: '${'all'.tr()} ${DateFormat('EEEE', context.locale.languageCode).format(selectedDate)}',
|
||||
onTap: () {
|
||||
setState(() {
|
||||
mode = SelectionMode.weekly;
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPopupButton(bool isOpened, DateTime selectedDate) {
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
height: 34,
|
||||
padding: const EdgeInsets.only(left: 12, right: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grayWhite,
|
||||
borderRadius: BorderRadius.circular(17),
|
||||
border: Border.all(
|
||||
color: isOpened ? AppColors.bgColorDark : AppColors.grayTintStroke,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (mode == SelectionMode.empty)
|
||||
Text(
|
||||
'modify'.tr(),
|
||||
style: AppTextStyles.bodySmallMed,
|
||||
)
|
||||
else
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '${'modify'.tr()}: ',
|
||||
style: AppTextStyles.bodyMediumMed.copyWith(
|
||||
color: AppColors.blackGray,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: '${mode == SelectionMode.single ? 'one'.tr() : 'all'.tr()} '
|
||||
'${DateFormat('EEEE', context.locale.languageCode).format(selectedDate)}',
|
||||
style: AppTextStyles.bodyMediumMed,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Gap(4),
|
||||
AnimatedRotation(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
turns: isOpened ? -0.5 : 0,
|
||||
child: Assets.images.icons.caretDown.svg(
|
||||
height: 16,
|
||||
width: 16,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum SelectionMode {empty, single, weekly}
|
||||
@@ -0,0 +1,152 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/presentation/gen/assets.gen.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_button.dart';
|
||||
import 'package:krow/core/presentation/widgets/kw_time_slot.dart';
|
||||
import 'package:krow/features/profile/schedule/domain/bloc/schedule_bloc.dart';
|
||||
import 'package:krow/features/profile/schedule/domain/entities/schedule_slot.dart';
|
||||
|
||||
class CalendarEditSlot extends StatelessWidget {
|
||||
const CalendarEditSlot({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<ScheduleBloc, ScheduleState>(
|
||||
buildWhen: (previous, current) {
|
||||
return previous.scheduleErrorText != current.scheduleErrorText ||
|
||||
previous.tempSchedules != current.tempSchedules;
|
||||
},
|
||||
builder: (context, state) {
|
||||
final daySchedule = state.tempSchedules.first;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.graySecondaryFrame,
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(12),
|
||||
bottomRight: Radius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
'what_hours_available'.tr(),
|
||||
style: AppTextStyles.bodyMediumMed,
|
||||
),
|
||||
const Divider(
|
||||
color: AppColors.grayTintStroke,
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
alignment: Alignment.topCenter,
|
||||
child: Column(
|
||||
children: [
|
||||
for (int i = 0; i < daySchedule.slots.length; i++)
|
||||
_CalendarEditSlotItemWidget(
|
||||
slot: daySchedule.slots[i],
|
||||
slotIndex: i,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (state.scheduleErrorText.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Text(
|
||||
state.scheduleErrorText,
|
||||
style: AppTextStyles.bodyTinyReg.copyWith(
|
||||
color: AppColors.statusError,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Gap(16),
|
||||
KwButton.secondary(
|
||||
label: 'add_slot'.tr(),
|
||||
leftIcon: Assets.images.icons.add,
|
||||
onPressed: () {
|
||||
BlocProvider.of<ScheduleBloc>(context).add(
|
||||
const ScheduleEventAddSlot(),
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CalendarEditSlotItemWidget extends StatelessWidget {
|
||||
const _CalendarEditSlotItemWidget({
|
||||
required this.slot,
|
||||
required this.slotIndex,
|
||||
});
|
||||
|
||||
final ScheduleSlot slot;
|
||||
final int slotIndex;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const Gap(8),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: KwTimeSlotInput(
|
||||
label: 'start_time'.tr(),
|
||||
initialValue: slot.startTime,
|
||||
onChange: (DateTime start) {
|
||||
BlocProvider.of<ScheduleBloc>(context).add(
|
||||
ScheduleEventEditSlot(
|
||||
start: start,
|
||||
slot: slot,
|
||||
slotIndex: slotIndex,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
Expanded(
|
||||
child: KwTimeSlotInput(
|
||||
label: 'end_time'.tr(),
|
||||
initialValue: slot.endTime,
|
||||
onChange: (DateTime end) {
|
||||
BlocProvider.of<ScheduleBloc>(context).add(
|
||||
ScheduleEventEditSlot(
|
||||
end: end,
|
||||
slot: slot,
|
||||
slotIndex: slotIndex,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
KwButton.secondary(
|
||||
onPressed: () {
|
||||
BlocProvider.of<ScheduleBloc>(context).add(
|
||||
ScheduleEventRemoveSlot(
|
||||
slot: slot,
|
||||
slotIndex: slotIndex,
|
||||
),
|
||||
);
|
||||
},
|
||||
leftIcon: Assets.images.icons.x,
|
||||
fit: KwButtonFit.circular,
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow/features/profile/schedule/domain/entities/day_shedule.dart';
|
||||
import 'package:krow/features/profile/schedule/domain/bloc/schedule_bloc.dart';
|
||||
import 'package:krow/features/profile/schedule/presentation/widgets/calendar_data_detail_setting_widget.dart';
|
||||
import 'package:krow/features/profile/schedule/presentation/widgets/calendar_legend_widget.dart';
|
||||
|
||||
class CalendarFooterWidget extends StatelessWidget {
|
||||
const CalendarFooterWidget({
|
||||
super.key,
|
||||
required this.selectedDates,
|
||||
required this.existedSlots,
|
||||
});
|
||||
|
||||
final List<DateTime> selectedDates;
|
||||
final List<DaySchedule> existedSlots;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocSelector<ScheduleBloc, ScheduleState, bool>(
|
||||
selector: (state) => state.isScheduleAddMode,
|
||||
builder: (context, isInScheduleAddMode) {
|
||||
return AnimatedCrossFade(
|
||||
duration: Durations.short4,
|
||||
crossFadeState: isInScheduleAddMode
|
||||
? CrossFadeState.showSecond
|
||||
: CrossFadeState.showFirst,
|
||||
firstChild: CalendarLegendWidget(
|
||||
selectedDates: selectedDates,
|
||||
existedSlots: existedSlots,
|
||||
),
|
||||
secondChild: CalendarDataDetailSettingWidget(
|
||||
selectedDate: selectedDates.firstOrNull,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/presentation/gen/assets.gen.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_button.dart';
|
||||
import 'package:krow/features/profile/schedule/domain/entities/day_shedule.dart';
|
||||
import 'package:krow/features/profile/schedule/presentation/widgets/shared_functions.dart';
|
||||
|
||||
class CalendarLegendWidget extends StatelessWidget {
|
||||
const CalendarLegendWidget({
|
||||
super.key,
|
||||
required this.selectedDates,
|
||||
required this.existedSlots,
|
||||
});
|
||||
|
||||
final List<DateTime> selectedDates;
|
||||
final List<DaySchedule> existedSlots;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedDatesOption = _getSelectedDatesOption();
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.grayPrimaryFrame,
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(12),
|
||||
bottomRight: Radius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Divider(
|
||||
color: AppColors.grayTintStroke,
|
||||
),
|
||||
const Gap(8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
height: 16,
|
||||
width: 16,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColors.bgColorDark,
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
Text(
|
||||
'available'.tr(),
|
||||
style: AppTextStyles.bodyMediumReg,
|
||||
),
|
||||
const Gap(32),
|
||||
Container(
|
||||
height: 16,
|
||||
width: 16,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColors.blackCaptionText,
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
Text(
|
||||
'not_available'.tr(),
|
||||
style: AppTextStyles.bodyMediumReg,
|
||||
),
|
||||
const Spacer(),
|
||||
if (selectedDates.isEmpty)
|
||||
const SizedBox(
|
||||
height: 34,
|
||||
)
|
||||
else
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: AppColors.grayTintStroke,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: KwButton.secondary(
|
||||
height: 32,
|
||||
label: switch (selectedDatesOption) {
|
||||
SelectedDatesOption.add => 'add'.tr(),
|
||||
SelectedDatesOption.edit => 'edit'.tr(),
|
||||
SelectedDatesOption.editAll => 'edit_all'.tr(),
|
||||
},
|
||||
onPressed: () => onEditButtonPress(
|
||||
context,
|
||||
selectedDatesOption: selectedDatesOption,
|
||||
isWeeklySchedule: existedSlots.length == 1 &&
|
||||
existedSlots.first.isWeekly,
|
||||
),
|
||||
rightIcon: existedSlots.isNotEmpty
|
||||
? Assets.images.icons.edit
|
||||
: Assets.images.icons.add,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Gap(24),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
SelectedDatesOption _getSelectedDatesOption() {
|
||||
if (existedSlots.isEmpty) return SelectedDatesOption.add;
|
||||
|
||||
return existedSlots.length > 1 || selectedDates.length > 1
|
||||
? SelectedDatesOption.editAll
|
||||
: SelectedDatesOption.edit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:krow/core/presentation/gen/assets.gen.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_box_decorations.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/dialogs/kw_dialog.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_popup_menu.dart';
|
||||
import 'package:krow/features/profile/schedule/domain/entities/day_shedule.dart';
|
||||
import 'package:krow/features/profile/schedule/domain/bloc/schedule_bloc.dart';
|
||||
import 'package:krow/features/profile/schedule/domain/entities/schedule_slot.dart';
|
||||
import 'package:krow/features/profile/schedule/presentation/widgets/shared_functions.dart';
|
||||
|
||||
import '../../../../../app.dart';
|
||||
|
||||
class CalendarSlotListWidget extends StatelessWidget {
|
||||
const CalendarSlotListWidget({
|
||||
super.key,
|
||||
required this.selectedSchedules,
|
||||
});
|
||||
|
||||
final List<DaySchedule> selectedSchedules;
|
||||
|
||||
static final timeFormat = DateFormat('h:mma', 'en');
|
||||
|
||||
Future<void> _onDeleteButtonPress(
|
||||
BuildContext context,
|
||||
DaySchedule schedule,
|
||||
) async {
|
||||
final scheduleBloc = context.read<ScheduleBloc>();
|
||||
if (!schedule.isWeekly) {
|
||||
scheduleBloc.add(
|
||||
ScheduleEventDeleteSchedule(schedule: schedule),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await KwDialog.show(
|
||||
context: context,
|
||||
state: KwDialogState.warning,
|
||||
icon: Assets.images.icons.alertTriangle,
|
||||
title: 'delete_schedule'.tr(),
|
||||
message: 'delete_schedule_message'.tr(args: [(DateFormat('EEEE', context.locale.languageCode).format(schedule.date))]),
|
||||
primaryButtonLabel: 'confirm'.tr(),
|
||||
secondaryButtonLabel: 'cancel'.tr(),
|
||||
onPrimaryButtonPressed: (dialogContext) {
|
||||
scheduleBloc.add(
|
||||
ScheduleEventDeleteSchedule(schedule: schedule),
|
||||
);
|
||||
|
||||
Navigator.of(dialogContext).pop();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<ScheduleBloc, ScheduleState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.selectedDates != current.selectedDates,
|
||||
builder: (context, state) {
|
||||
if (state.selectedDates.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Gap(12),
|
||||
Text(
|
||||
'slot_details'.tr(),
|
||||
style: AppTextStyles.bodySmallMed,
|
||||
),
|
||||
const Gap(12),
|
||||
for (var schedule in selectedSchedules)
|
||||
_buildInfoWidget(context, schedule),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoWidget(context, DaySchedule schedule) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: 12),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: KwBoxDecorations.primaryLight8,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
height: 36,
|
||||
width: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grayWhite,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(
|
||||
color: AppColors.grayStroke,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Assets.images.icons.calendar.svg(),
|
||||
),
|
||||
),
|
||||
const Gap(12),
|
||||
Text(
|
||||
DateFormat('MMMM d', appContext!.locale.languageCode).format(schedule.date),
|
||||
style: AppTextStyles.headingH3,
|
||||
),
|
||||
const Spacer(),
|
||||
KwPopupMenu(
|
||||
horizontalMargin: 24,
|
||||
menuItems: [
|
||||
KwPopupMenuItem(
|
||||
title: 'edit'.tr(),
|
||||
icon: Assets.images.icons.edit.svg(),
|
||||
onTap: () => onEditButtonPress(
|
||||
context,
|
||||
editedDate: schedule.date,
|
||||
isWeeklySchedule: schedule.isWeekly,
|
||||
),
|
||||
),
|
||||
KwPopupMenuItem(
|
||||
title: 'delete'.tr(),
|
||||
textStyle: AppTextStyles.bodyMediumReg.copyWith(
|
||||
color: AppColors.statusError,
|
||||
),
|
||||
icon: Assets.images.icons.trash.svg(),
|
||||
onTap: () => _onDeleteButtonPress(context, schedule),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const Gap(12),
|
||||
Text(
|
||||
'time_slots'.tr(),
|
||||
style: AppTextStyles.bodySmallMed.copyWith(
|
||||
color: AppColors.blackGray,
|
||||
),
|
||||
),
|
||||
for (var i = 0; i < schedule.slots.length / 2; i++)
|
||||
Row(
|
||||
children: [
|
||||
_buildSlotWidget(context, schedule.slots[i * 2]),
|
||||
const Gap(12),
|
||||
schedule.slots.length > (i * 2 + 1)
|
||||
? _buildSlotWidget(context, schedule.slots[i * 2 + 1])
|
||||
: const Spacer(),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_buildSlotWidget(BuildContext context, ScheduleSlot slot) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 12),
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grayWhite,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(
|
||||
color: AppColors.grayTintStroke,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${timeFormat.format(slot.startTime)} - '
|
||||
'${timeFormat.format(slot.endTime)}',
|
||||
style: AppTextStyles.bodyMediumReg,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
import 'package:calendar_date_picker2/calendar_date_picker2.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/presentation/gen/assets.gen.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
|
||||
class ScheduleCalendar extends StatefulWidget {
|
||||
const ScheduleCalendar({
|
||||
super.key,
|
||||
required this.selectedDates,
|
||||
required this.onDateSelected,
|
||||
required this.containsScheduleForDate,
|
||||
required this.disabled,
|
||||
});
|
||||
|
||||
// static const _monthNames = [
|
||||
// 'Jan',
|
||||
// 'Feb',
|
||||
// 'Mar',
|
||||
// 'Apr',
|
||||
// 'May',
|
||||
// 'Jun',
|
||||
// 'Jul',
|
||||
// 'Aug',
|
||||
// 'Sep',
|
||||
// 'Oct',
|
||||
// 'Nov',
|
||||
// 'Dec',
|
||||
// ];
|
||||
|
||||
|
||||
final List<DateTime> selectedDates;
|
||||
final void Function(List<DateTime> value) onDateSelected;
|
||||
final bool Function(DateTime date) containsScheduleForDate;
|
||||
final bool disabled;
|
||||
|
||||
@override
|
||||
State<ScheduleCalendar> createState() => _ScheduleCalendarState();
|
||||
}
|
||||
|
||||
class _ScheduleCalendarState extends State<ScheduleCalendar> {
|
||||
final currentDate = DateTime.now();
|
||||
final selectedTextStyle = AppTextStyles.bodyMediumSmb.copyWith(
|
||||
color: AppColors.grayWhite,
|
||||
);
|
||||
final dayTextStyle = AppTextStyles.bodyMediumReg.copyWith(
|
||||
color: AppColors.bgColorDark,
|
||||
);
|
||||
|
||||
late List<DateTime> _selectedDates =
|
||||
_getStateSelectedDates(widget.selectedDates);
|
||||
|
||||
List<DateTime> _getStateSelectedDates(List<DateTime> selectedDates) {
|
||||
if (selectedDates.isEmpty) return [];
|
||||
|
||||
return [
|
||||
selectedDates.first,
|
||||
if (selectedDates.length > 1) selectedDates.last,
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant ScheduleCalendar oldWidget) {
|
||||
_selectedDates = _getStateSelectedDates(widget.selectedDates);
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IgnorePointer(
|
||||
ignoring: widget.disabled,
|
||||
child: CalendarDatePicker2(
|
||||
value: _selectedDates,
|
||||
config: CalendarDatePicker2Config(
|
||||
calendarType: CalendarDatePicker2Type.range,
|
||||
selectedRangeHighlightColor: AppColors.tintGray,
|
||||
selectedDayTextStyle: selectedTextStyle,
|
||||
dayTextStyle: dayTextStyle,
|
||||
|
||||
selectedMonthTextStyle: selectedTextStyle,
|
||||
selectedYearTextStyle: selectedTextStyle,
|
||||
selectedDayHighlightColor: AppColors.bgColorDark,
|
||||
centerAlignModePicker: true,
|
||||
monthTextStyle: dayTextStyle,
|
||||
weekdayLabelBuilder: _dayWeekBuilder,
|
||||
dayBuilder: _dayBuilder,
|
||||
monthBuilder: _monthBuilder,
|
||||
yearBuilder: _yearBuilder,
|
||||
controlsTextStyle: AppTextStyles.headingH3,
|
||||
nextMonthIcon: Assets.images.icons.caretRight.svg(),
|
||||
lastMonthIcon: Assets.images.icons.caretLeft.svg(),
|
||||
customModePickerIcon: Padding(
|
||||
padding: const EdgeInsets.only(left: 4),
|
||||
child: Assets.images.icons.caretDown.svg(),
|
||||
),
|
||||
// modePickerBuilder: _controlBuilder,
|
||||
),
|
||||
onValueChanged: widget.onDateSelected,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _monthBuilder({
|
||||
required int month,
|
||||
TextStyle? textStyle,
|
||||
BoxDecoration? decoration,
|
||||
bool? isSelected,
|
||||
bool? isDisabled,
|
||||
bool? isCurrentMonth,
|
||||
}) {
|
||||
return Center(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 12),
|
||||
height: 46,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected == true ? AppColors.blackDarkBgBody : null,
|
||||
borderRadius: BorderRadius.circular(23),
|
||||
border: Border.all(
|
||||
color: AppColors.grayStroke,
|
||||
width: isSelected == true ? 0 : 1,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
DateFormat.MMM(context.locale.toLanguageTag()).format(
|
||||
DateFormat('M').parse(month.toString()),
|
||||
),
|
||||
style: isSelected == true
|
||||
? AppTextStyles.bodyMediumMed
|
||||
.copyWith(color: AppColors.grayWhite)
|
||||
: AppTextStyles.bodyMediumReg
|
||||
.copyWith(color: AppColors.blackGray),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _yearBuilder({
|
||||
required int year,
|
||||
TextStyle? textStyle,
|
||||
BoxDecoration? decoration,
|
||||
bool? isSelected,
|
||||
bool? isDisabled,
|
||||
bool? isCurrentYear,
|
||||
}) {
|
||||
return Center(
|
||||
child: Text(
|
||||
year.toString(),
|
||||
style: dayTextStyle,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _dayBuilder({
|
||||
required DateTime date,
|
||||
TextStyle? textStyle,
|
||||
BoxDecoration? decoration,
|
||||
bool? isSelected,
|
||||
bool? isDisabled,
|
||||
bool? isToday,
|
||||
}) {
|
||||
final eventExist = widget.containsScheduleForDate(date);
|
||||
|
||||
return Center(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(left: 2, right: 2),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected == true ? AppColors.bgColorDark : null,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (eventExist) const Gap(5),
|
||||
Text(
|
||||
date.day.toString(),
|
||||
style: isSelected == true
|
||||
? selectedTextStyle
|
||||
: AppTextStyles.bodyMediumReg.copyWith(
|
||||
color: _isPast(date)
|
||||
? AppColors.blackCaptionText
|
||||
: AppColors.bgColorDark,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (eventExist) ...[
|
||||
const Gap(1),
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected == true
|
||||
? AppColors.grayWhite
|
||||
: AppColors.bgColorDark,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
child: const SizedBox(
|
||||
height: 4,
|
||||
width: 4,
|
||||
),
|
||||
),
|
||||
],
|
||||
const Gap(2),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool _isPast(DateTime date) {
|
||||
return date.isBefore(
|
||||
DateTime(currentDate.year, currentDate.month, currentDate.day),
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _dayWeekBuilder({
|
||||
required int weekday,
|
||||
bool? isScrollViewTopHeader,
|
||||
}) {
|
||||
print(DateFormat('d', 'en').parse((weekday).toString()));
|
||||
return Text(
|
||||
DateFormat.E(context.locale.toLanguageTag()).format(
|
||||
DateTime(2020, 1, (5 + weekday)) // 5 is Monday, so we start from there),
|
||||
),
|
||||
style: AppTextStyles.bodyMediumSmb.copyWith(
|
||||
color: AppColors.bgColorDark,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow/core/presentation/gen/assets.gen.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/dialogs/kw_dialog.dart';
|
||||
import 'package:krow/features/profile/schedule/domain/bloc/schedule_bloc.dart';
|
||||
import 'package:krow/features/profile/schedule/presentation/widgets/weekly_option_selector.dart';
|
||||
|
||||
enum SelectedDatesOption { add, edit, editAll }
|
||||
|
||||
Future<void> onEditButtonPress(
|
||||
BuildContext context, {
|
||||
required bool isWeeklySchedule,
|
||||
SelectedDatesOption selectedDatesOption = SelectedDatesOption.edit,
|
||||
DateTime? editedDate,
|
||||
}) async {
|
||||
if (selectedDatesOption != SelectedDatesOption.edit) {
|
||||
context.read<ScheduleBloc>().add(
|
||||
const ScheduleEventChangeMode(
|
||||
isInScheduleAddMode: true,
|
||||
isInEditScheduleMode: true,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isWeeklySchedule) {
|
||||
context.read<ScheduleBloc>().add(
|
||||
ScheduleEventChangeMode(
|
||||
isWeekly: false,
|
||||
isInEditScheduleMode: true,
|
||||
editedDate: editedDate,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var isWeeklySelected = false;
|
||||
await KwDialog.show(
|
||||
context: context,
|
||||
icon: Assets.images.icons.menuBoard,
|
||||
title: 'update_schedule'.tr(),
|
||||
message: 'update_schedule_message'.tr(),
|
||||
primaryButtonLabel: 'submit_update'.tr(),
|
||||
secondaryButtonLabel: 'cancel'.tr(),
|
||||
onPrimaryButtonPressed: (dialogContext) {
|
||||
context.read<ScheduleBloc>().add(
|
||||
ScheduleEventChangeMode(
|
||||
isWeekly: isWeeklySelected,
|
||||
isInEditScheduleMode: true,
|
||||
editedDate: editedDate,
|
||||
),
|
||||
);
|
||||
|
||||
Navigator.of(dialogContext).pop();
|
||||
},
|
||||
child: WeeklyOptionSelector(
|
||||
onWeeklyToggle: (isWeekly) {
|
||||
isWeeklySelected = isWeekly;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_option_selector.dart';
|
||||
|
||||
class WeeklyOptionSelector extends StatefulWidget {
|
||||
const WeeklyOptionSelector({
|
||||
super.key,
|
||||
required this.onWeeklyToggle,
|
||||
});
|
||||
|
||||
final void Function(bool) onWeeklyToggle;
|
||||
|
||||
@override
|
||||
State<WeeklyOptionSelector> createState() => _WeeklyOptionSelectorState();
|
||||
}
|
||||
|
||||
class _WeeklyOptionSelectorState extends State<WeeklyOptionSelector> {
|
||||
int selectedOption = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return KwOptionSelector(
|
||||
selectedIndex: selectedOption,
|
||||
selectedColor: AppColors.blackDarkBgBody,
|
||||
itemBorder: const Border.fromBorderSide(
|
||||
BorderSide(color: AppColors.grayStroke),
|
||||
),
|
||||
onChanged: (index) => setState(() {
|
||||
selectedOption = index;
|
||||
widget.onWeeklyToggle(selectedOption != 0);
|
||||
}),
|
||||
items: ['this_day_only'.tr(), 'entire_schedule'.tr()],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user