feat: legacy mobile apps created
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.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_input.dart';
|
||||
import 'package:krow/features/shifts/presentation/dialogs/cancel_dialog/cancel_reason_dropdown.dart';
|
||||
|
||||
class CancelDialogView extends StatefulWidget {
|
||||
const CancelDialogView({super.key});
|
||||
|
||||
@override
|
||||
State<CancelDialogView> createState() => _CancelDialogViewState();
|
||||
}
|
||||
|
||||
class _CancelDialogViewState extends State<CancelDialogView> {
|
||||
String selectedReason = '';
|
||||
final _textEditingController = TextEditingController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: AnimatedSize(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grayWhite,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 56),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'cancel_shift'.tr(),
|
||||
style: AppTextStyles.headingH1.copyWith(height: 1),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const Gap(8),
|
||||
Text(
|
||||
'please_select_reason'.tr(),
|
||||
style: AppTextStyles.bodyMediumReg
|
||||
.copyWith(color: AppColors.blackGray),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const Gap(8),
|
||||
CancelReasonDropdown(
|
||||
selectedReason: selectedReason,
|
||||
onReasonSelected: (String reason) {
|
||||
setState(() {
|
||||
selectedReason = reason;
|
||||
});
|
||||
},
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
controller: _textEditingController,
|
||||
minHeight: 144,
|
||||
maxLength: 300,
|
||||
showCounter: true,
|
||||
radius: 12,
|
||||
title: 'additional_reasons'.tr(),
|
||||
hintText: 'enter_main_text'.tr(),
|
||||
),
|
||||
const Gap(24),
|
||||
_buttonGroup(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buttonGroup(
|
||||
BuildContext context,
|
||||
) {
|
||||
return Column(
|
||||
children: [
|
||||
KwButton.primary(
|
||||
disabled: selectedReason.isEmpty,
|
||||
label: 'submit_reason'.tr(),
|
||||
onPressed: () {
|
||||
context.maybePop({
|
||||
'reason': selectedReason,
|
||||
'additionalReason': _textEditingController.text,
|
||||
});
|
||||
},
|
||||
),
|
||||
const Gap(8),
|
||||
KwButton.outlinedPrimary(
|
||||
label: 'cancel'.tr(),
|
||||
onPressed: () {
|
||||
context.maybePop();
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
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_popup_menu.dart';
|
||||
|
||||
const reasons = {
|
||||
'sick_leave': 'sick_leave',
|
||||
'vacation ': 'vacation',
|
||||
'other': 'other_specify',
|
||||
};
|
||||
|
||||
class CancelReasonDropdown extends StatelessWidget {
|
||||
final String? selectedReason;
|
||||
final Function(String reason) onReasonSelected;
|
||||
|
||||
const CancelReasonDropdown(
|
||||
{super.key,
|
||||
required this.selectedReason,
|
||||
required this.onReasonSelected});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: buildReasonInput(context, selectedReason),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> buildReasonInput(BuildContext context, String? selectedReason) {
|
||||
return [
|
||||
const Gap(24),
|
||||
Row(
|
||||
children: [
|
||||
const Gap(16),
|
||||
Text(
|
||||
'reason'.tr(),
|
||||
style:
|
||||
AppTextStyles.bodyTinyReg.copyWith(color: AppColors.blackGray),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Gap(4),
|
||||
KwPopupMenu(
|
||||
horizontalPadding: 40,
|
||||
fit: KwPopupMenuFit.expand,
|
||||
customButtonBuilder: (context, isOpened) {
|
||||
return _buildMenuButton(isOpened, selectedReason);
|
||||
},
|
||||
menuItems: [
|
||||
...reasons.entries
|
||||
.map((e) => _buildMenuItem(context, e, selectedReason ?? ''))
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
Container _buildMenuButton(bool isOpened, String? selectedReason) {
|
||||
return Container(
|
||||
height: 48,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grayWhite,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(
|
||||
color: isOpened ? AppColors.bgColorDark : AppColors.grayTintStroke,
|
||||
width: 1),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
reasons[selectedReason]?.tr() ?? 'select_reason_from_list'.tr(),
|
||||
style: AppTextStyles.bodyMediumReg.copyWith(
|
||||
color: selectedReason == null
|
||||
? AppColors.blackGray
|
||||
: AppColors.blackBlack),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
KwPopupMenuItem _buildMenuItem(BuildContext context,
|
||||
MapEntry<String, String> entry, String selectedReason) {
|
||||
return KwPopupMenuItem(
|
||||
title: entry.value.tr(),
|
||||
onTap: () {
|
||||
onReasonSelected(entry.key);
|
||||
},
|
||||
icon: Container(
|
||||
height: 16,
|
||||
width: 16,
|
||||
decoration: BoxDecoration(
|
||||
color: selectedReason != entry.key ? null : AppColors.bgColorDark,
|
||||
shape: BoxShape.circle,
|
||||
border: selectedReason == entry.key
|
||||
? null
|
||||
: Border.all(color: AppColors.grayTintStroke, width: 1),
|
||||
),
|
||||
child: selectedReason == entry.key
|
||||
? Center(
|
||||
child: Assets.images.icons.check.svg(
|
||||
height: 10,
|
||||
width: 10,
|
||||
colorFilter: const ColorFilter.mode(
|
||||
AppColors.grayWhite, BlendMode.srcIn),
|
||||
))
|
||||
: null,
|
||||
),
|
||||
textStyle: AppTextStyles.bodySmallMed,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:krow/features/shifts/presentation/dialogs/cancel_dialog/cancel_dialog_view.dart';
|
||||
|
||||
class ShiftCancelDialog extends StatelessWidget {
|
||||
const ShiftCancelDialog({super.key});
|
||||
|
||||
static Future<Map<String, dynamic>?> showCustomDialog(
|
||||
BuildContext context) async {
|
||||
return await showDialog<Map<String, dynamic>>(
|
||||
context: context,
|
||||
builder: (context) => const CancelDialogView(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const CancelDialogView();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow/features/shifts/domain/blocs/complete_dialog/shift_complete_dialog_bloc.dart';
|
||||
import 'package:krow/features/shifts/domain/blocs/complete_dialog/shift_complete_dialog_event.dart';
|
||||
import 'package:krow/features/shifts/presentation/dialogs/complete_dialog/widgets/complete_dialog_view.dart';
|
||||
|
||||
//TODO(Artem: create widgets instead helper methods. Add initial break time values.Move reasons to state. Create Time slots validator.)
|
||||
class ShiftCompleteDialog {
|
||||
static Future<Map<String, dynamic>?> showCustomDialog(BuildContext context,
|
||||
bool canSkip, String eventName, DateTime minLimit, int breakDurationInMinutes) async {
|
||||
return showDialog<Map<String, dynamic>>(
|
||||
barrierDismissible: canSkip,
|
||||
context: context,
|
||||
builder: (context) => BlocProvider(
|
||||
create: (_) => CompleteDialogBloc()
|
||||
..add(
|
||||
InitializeCompleteDialog(
|
||||
minLimit: minLimit,
|
||||
breakDurationInMinutes:breakDurationInMinutes , // Default break duration
|
||||
),
|
||||
),
|
||||
child: CompleteDialogView(
|
||||
canSkip: canSkip,
|
||||
eventName: eventName,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ClockOutDetails {
|
||||
final String? breakStartTime;
|
||||
final String? breakEndTime;
|
||||
final String? reason;
|
||||
final String? additionalReason;
|
||||
|
||||
ClockOutDetails._(
|
||||
{this.breakStartTime,
|
||||
this.breakEndTime,
|
||||
this.reason,
|
||||
this.additionalReason});
|
||||
|
||||
factory ClockOutDetails.positive(String breakStartTime, String breakEndTime) {
|
||||
return ClockOutDetails._(
|
||||
breakStartTime: breakStartTime,
|
||||
breakEndTime: breakEndTime,
|
||||
);
|
||||
}
|
||||
|
||||
factory ClockOutDetails.negative(String reason, String additionalReason) {
|
||||
return ClockOutDetails._(
|
||||
reason: reason,
|
||||
additionalReason: additionalReason,
|
||||
);
|
||||
}
|
||||
|
||||
factory ClockOutDetails.empty() {
|
||||
return ClockOutDetails._();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
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/application/common/date_time_extension.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/kw_time_slot.dart';
|
||||
import 'package:krow/features/shifts/domain/blocs/complete_dialog/shift_complete_dialog_bloc.dart';
|
||||
import 'package:krow/features/shifts/domain/blocs/complete_dialog/shift_complete_dialog_event.dart';
|
||||
import 'package:krow/features/shifts/domain/blocs/complete_dialog/shift_complete_dialog_state.dart';
|
||||
|
||||
class BreakTimePicker extends StatelessWidget {
|
||||
final CompleteDialogState state;
|
||||
|
||||
const BreakTimePicker({super.key, required this.state});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: KwTimeSlotInput(
|
||||
label: 'Break Start Time',
|
||||
initialValue: DateFormat('H:mm').parse(state.startTime),
|
||||
onChange: (v) => context.read<CompleteDialogBloc>().add(
|
||||
ChangeStartTime(v),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Gap(12),
|
||||
Expanded(
|
||||
child: KwTimeSlotInput(
|
||||
editable: false,
|
||||
label: 'Break End Time',
|
||||
initialValue: DateFormat('H:mm').parse(state.endTime),
|
||||
onChange: (v) {},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (state.breakTimeInputError != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8, left: 16),
|
||||
child: Text(
|
||||
state.breakTimeInputError!,
|
||||
style: AppTextStyles.bodyTinyReg
|
||||
.copyWith(color: AppColors.statusError),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
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/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_input.dart';
|
||||
import 'package:krow/features/shifts/domain/blocs/complete_dialog/shift_complete_dialog_bloc.dart';
|
||||
import 'package:krow/features/shifts/domain/blocs/complete_dialog/shift_complete_dialog_event.dart';
|
||||
import 'package:krow/features/shifts/domain/blocs/complete_dialog/shift_complete_dialog_state.dart';
|
||||
import 'package:krow/features/shifts/presentation/dialogs/complete_dialog/shift_complete_dialog.dart';
|
||||
import 'package:krow/features/shifts/presentation/dialogs/complete_dialog/widgets/break_time_picker.dart';
|
||||
import 'package:krow/features/shifts/presentation/dialogs/complete_dialog/widgets/complete_reason_input.dart';
|
||||
|
||||
class CompleteDialogView extends StatelessWidget {
|
||||
CompleteDialogView({super.key, this.canSkip = true, required this.eventName});
|
||||
|
||||
final _textEditingController = TextEditingController();
|
||||
final bool canSkip;
|
||||
final String eventName;
|
||||
|
||||
String _title(BreakStatus state) => state == BreakStatus.negative
|
||||
? 'help_us_understand'.tr()
|
||||
: 'did_you_take_a_break'.tr();
|
||||
|
||||
String _message(BreakStatus state) => state == BreakStatus.negative
|
||||
? 'taking_breaks_essential'.tr()
|
||||
: 'taking_regular_breaks'.tr(namedArgs: {'eventName': eventName});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<CompleteDialogBloc, CompleteDialogState>(
|
||||
builder: (context, state) {
|
||||
return Center(
|
||||
child: AnimatedSize(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grayWhite,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 24, vertical: 56),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
_title(state.status),
|
||||
style: AppTextStyles.headingH1.copyWith(height: 1),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const Gap(8),
|
||||
Text(
|
||||
_message(state.status),
|
||||
style: AppTextStyles.bodyMediumReg
|
||||
.copyWith(color: AppColors.blackGray),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const Gap(8),
|
||||
if (state.status == BreakStatus.positive)
|
||||
BreakTimePicker(state: state),
|
||||
if (state.status == BreakStatus.negative)
|
||||
CompleteReasonInput(selectedReason: state.selectedReason),
|
||||
if (state.status == BreakStatus.negative) ...[
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
controller: _textEditingController,
|
||||
minHeight: 144,
|
||||
maxLength: 300,
|
||||
showCounter: true,
|
||||
radius: 12,
|
||||
title: 'additional_reasons'.tr(),
|
||||
hintText: 'enter_main_text'.tr(),
|
||||
),
|
||||
],
|
||||
const Gap(24),
|
||||
_buttonGroup(context, state),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buttonGroup(BuildContext context, CompleteDialogState state) {
|
||||
var cancelButton = canSkip
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: KwButton.outlinedPrimary(
|
||||
label: 'cancel'.tr(),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
return Column(
|
||||
children: [
|
||||
if (state.status == BreakStatus.neutral) ...[
|
||||
KwButton.primary(
|
||||
label: 'yes_i_took_a_break'.tr(),
|
||||
onPressed: () => context
|
||||
.read<CompleteDialogBloc>()
|
||||
.add(SelectBreakStatus(BreakStatus.positive)),
|
||||
),
|
||||
const Gap(8),
|
||||
KwButton.outlinedPrimary(
|
||||
label: 'no_i_didnt_take_a_break'.tr(),
|
||||
onPressed: () => context
|
||||
.read<CompleteDialogBloc>()
|
||||
.add(SelectBreakStatus(BreakStatus.negative)),
|
||||
),
|
||||
],
|
||||
if (state.status == BreakStatus.positive) ...[
|
||||
KwButton.primary(
|
||||
disabled: state.breakTimeInputError != null,
|
||||
label: 'submit_break_time'.tr(),
|
||||
onPressed: () {
|
||||
Navigator.pop(context, <String, dynamic>{
|
||||
'result': true,
|
||||
'details': ClockOutDetails.positive(
|
||||
state.startTime,
|
||||
state.endTime,
|
||||
)
|
||||
});
|
||||
},
|
||||
),
|
||||
cancelButton,
|
||||
],
|
||||
if (state.status == BreakStatus.negative) ...[
|
||||
KwButton.primary(
|
||||
disabled: state.selectedReason == null,
|
||||
label: 'submit_reason'.tr(),
|
||||
onPressed: () {
|
||||
Navigator.pop(context, {
|
||||
'result': false,
|
||||
'details': ClockOutDetails.negative(
|
||||
state.selectedReason!, _textEditingController.text)
|
||||
});
|
||||
},
|
||||
),
|
||||
cancelButton,
|
||||
]
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
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_popup_menu.dart';
|
||||
import 'package:krow/features/shifts/domain/blocs/complete_dialog/shift_complete_dialog_bloc.dart';
|
||||
import 'package:krow/features/shifts/domain/blocs/complete_dialog/shift_complete_dialog_event.dart';
|
||||
|
||||
const reasons = {
|
||||
'unpredictable_workflows': 'unpredictable_workflows',
|
||||
'poor_time_management': 'poor_time_management',
|
||||
'lack_of_coverage_or_short_staff': 'lack_of_coverage_or_short_staff',
|
||||
'no_break_area': 'no_break_area',
|
||||
'other': 'other',
|
||||
};
|
||||
|
||||
class CompleteReasonInput extends StatelessWidget {
|
||||
final String? selectedReason;
|
||||
|
||||
const CompleteReasonInput({super.key, required this.selectedReason});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: buildReasonInput(context, selectedReason),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> buildReasonInput(BuildContext context, String? selectedReason) {
|
||||
return [
|
||||
const Gap(24),
|
||||
Row(
|
||||
children: [
|
||||
const Gap(16),
|
||||
Text(
|
||||
'reason'.tr(),
|
||||
style:
|
||||
AppTextStyles.bodyTinyReg.copyWith(color: AppColors.blackGray),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Gap(4),
|
||||
KwPopupMenu(
|
||||
horizontalPadding: 40,
|
||||
fit: KwPopupMenuFit.expand,
|
||||
customButtonBuilder: (context, isOpened) {
|
||||
return _buildMenuButton(isOpened, selectedReason);
|
||||
},
|
||||
menuItems: [
|
||||
...reasons.entries
|
||||
.map((e) => _buildMenuItem(context, e, selectedReason ?? ''))
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
Container _buildMenuButton(bool isOpened, String? selectedReason) {
|
||||
return Container(
|
||||
height: 48,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grayWhite,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(
|
||||
color: isOpened ? AppColors.bgColorDark : AppColors.grayTintStroke,
|
||||
width: 1),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
reasons[selectedReason]?.tr() ?? 'select_reason_from_list'.tr(),
|
||||
style: AppTextStyles.bodyMediumReg.copyWith(
|
||||
color: selectedReason == null
|
||||
? AppColors.blackGray
|
||||
: AppColors.blackBlack),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
KwPopupMenuItem _buildMenuItem(BuildContext context,
|
||||
MapEntry<String, String> entry, String selectedReason) {
|
||||
return KwPopupMenuItem(
|
||||
title: entry.value.tr(),
|
||||
onTap: () {
|
||||
context.read<CompleteDialogBloc>().add(SelectReason(entry.key));
|
||||
},
|
||||
icon: Container(
|
||||
height: 16,
|
||||
width: 16,
|
||||
decoration: BoxDecoration(
|
||||
color: selectedReason != entry.key ? null : AppColors.bgColorDark,
|
||||
shape: BoxShape.circle,
|
||||
border: selectedReason == entry.key
|
||||
? null
|
||||
: Border.all(color: AppColors.grayTintStroke, width: 1),
|
||||
),
|
||||
child: selectedReason == entry.key
|
||||
? Center(
|
||||
child: Assets.images.icons.check.svg(
|
||||
height: 10,
|
||||
width: 10,
|
||||
colorFilter: const ColorFilter.mode(
|
||||
AppColors.grayWhite, BlendMode.srcIn),
|
||||
))
|
||||
: null,
|
||||
),
|
||||
textStyle: AppTextStyles.bodySmallMed,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.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_input.dart';
|
||||
import 'package:krow/features/shifts/presentation/dialogs/decline_dialog/decline_reason_dropdown.dart';
|
||||
|
||||
class DeclineDialogView extends StatefulWidget {
|
||||
const DeclineDialogView({super.key});
|
||||
|
||||
@override
|
||||
State<DeclineDialogView> createState() => _DeclineDialogViewState();
|
||||
}
|
||||
|
||||
class _DeclineDialogViewState extends State<DeclineDialogView> {
|
||||
String selectedReason = '';
|
||||
final _textEditingController = TextEditingController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: AnimatedSize(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grayWhite,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 56),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'decline_alert'.tr(),
|
||||
style: AppTextStyles.headingH1.copyWith(height: 1),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const Gap(8),
|
||||
Text(
|
||||
'mention_reason_declining'.tr(),
|
||||
style: AppTextStyles.bodyMediumReg
|
||||
.copyWith(color: AppColors.blackGray),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const Gap(8),
|
||||
DeclineReasonDropdown(
|
||||
selectedReason: selectedReason,
|
||||
onReasonSelected: (String reason) {
|
||||
setState(() {
|
||||
selectedReason = reason;
|
||||
});
|
||||
},
|
||||
),
|
||||
const Gap(8),
|
||||
KwTextInput(
|
||||
controller: _textEditingController,
|
||||
minHeight: 144,
|
||||
maxLength: 300,
|
||||
showCounter: true,
|
||||
radius: 12,
|
||||
title: 'additional_reasons'.tr(),
|
||||
hintText: 'enter_main_text'.tr(),
|
||||
),
|
||||
const Gap(24),
|
||||
_buttonGroup(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buttonGroup(
|
||||
BuildContext context,
|
||||
) {
|
||||
return Column(
|
||||
children: [
|
||||
KwButton.primary(
|
||||
disabled: selectedReason.isEmpty,
|
||||
label: 'agree_and_close',
|
||||
onPressed: () {
|
||||
context.maybePop({
|
||||
'reason': selectedReason,
|
||||
'additionalReason': _textEditingController.text,
|
||||
});
|
||||
},
|
||||
),
|
||||
const Gap(8),
|
||||
KwButton.outlinedPrimary(
|
||||
label: 'contact_admin',
|
||||
onPressed: () {
|
||||
//todo contact admin
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
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_popup_menu.dart';
|
||||
|
||||
const reasons = {
|
||||
'health': 'health',
|
||||
'transportation': 'transportation',
|
||||
'personal': 'personal',
|
||||
'schedule_conflict': 'schedule_conflict',
|
||||
'other': 'other_specify',
|
||||
};
|
||||
|
||||
class DeclineReasonDropdown extends StatelessWidget {
|
||||
final String? selectedReason;
|
||||
final Function(String reason) onReasonSelected;
|
||||
|
||||
const DeclineReasonDropdown(
|
||||
{super.key,
|
||||
required this.selectedReason,
|
||||
required this.onReasonSelected});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: buildReasonInput(context, selectedReason),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> buildReasonInput(BuildContext context, String? selectedReason) {
|
||||
return [
|
||||
const Gap(24),
|
||||
Row(
|
||||
children: [
|
||||
const Gap(16),
|
||||
Text(
|
||||
'valid_reasons'.tr(),
|
||||
style:
|
||||
AppTextStyles.bodyTinyReg.copyWith(color: AppColors.blackGray),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Gap(4),
|
||||
KwPopupMenu(
|
||||
horizontalPadding: 40,
|
||||
fit: KwPopupMenuFit.expand,
|
||||
customButtonBuilder: (context, isOpened) {
|
||||
return _buildMenuButton(isOpened, selectedReason);
|
||||
},
|
||||
menuItems: [
|
||||
...reasons.entries
|
||||
.map((e) => _buildMenuItem(context, e, selectedReason ?? ''))
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
Container _buildMenuButton(bool isOpened, String? selectedReason) {
|
||||
return Container(
|
||||
height: 48,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grayWhite,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(
|
||||
color: isOpened ? AppColors.bgColorDark : AppColors.grayTintStroke,
|
||||
width: 1),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
reasons[selectedReason]?.tr() ?? 'select_reason_from_list'.tr(),
|
||||
style: AppTextStyles.bodyMediumReg.copyWith(
|
||||
color: selectedReason == null
|
||||
? AppColors.blackGray
|
||||
: AppColors.blackBlack),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
KwPopupMenuItem _buildMenuItem(BuildContext context,
|
||||
MapEntry<String, String> entry, String selectedReason) {
|
||||
return KwPopupMenuItem(
|
||||
title: entry.value.tr(),
|
||||
onTap: () {
|
||||
onReasonSelected(entry.key);
|
||||
},
|
||||
icon: Container(
|
||||
height: 16,
|
||||
width: 16,
|
||||
decoration: BoxDecoration(
|
||||
color: selectedReason != entry.key ? null : AppColors.bgColorDark,
|
||||
shape: BoxShape.circle,
|
||||
border: selectedReason == entry.key
|
||||
? null
|
||||
: Border.all(color: AppColors.grayTintStroke, width: 1),
|
||||
),
|
||||
child: selectedReason == entry.key
|
||||
? Center(
|
||||
child: Assets.images.icons.check.svg(
|
||||
height: 10,
|
||||
width: 10,
|
||||
colorFilter: const ColorFilter.mode(
|
||||
AppColors.grayWhite, BlendMode.srcIn),
|
||||
))
|
||||
: null,
|
||||
),
|
||||
textStyle: AppTextStyles.bodySmallMed,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:krow/features/shifts/presentation/dialogs/decline_dialog/decline_dialog_view.dart';
|
||||
|
||||
class ShiftDeclineDialog extends StatelessWidget {
|
||||
const ShiftDeclineDialog({super.key});
|
||||
|
||||
static Future<Map<String, dynamic>?> showCustomDialog(
|
||||
BuildContext context) async {
|
||||
return await showDialog<Map<String, dynamic>>(
|
||||
context: context,
|
||||
builder: (context) => const ShiftDeclineDialog(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const DeclineDialogView();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geolocator/geolocator.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/shifts/domain/blocs/shift_deteils_bloc/shift_details_bloc.dart';
|
||||
|
||||
class GeocodingDialogs {
|
||||
static bool dialogAlreadyOpen = false;
|
||||
|
||||
static void showGeocodingErrorDialog(
|
||||
ShiftDetailsState state, BuildContext context) async{
|
||||
if (dialogAlreadyOpen) {
|
||||
return;
|
||||
}
|
||||
dialogAlreadyOpen = true;
|
||||
var future;
|
||||
switch (state.proximityState) {
|
||||
case GeofencingProximityState.tooFar:
|
||||
future = KwDialog.show(
|
||||
context: context,
|
||||
icon: Assets.images.icons.alertTriangle,
|
||||
state: KwDialogState.warning,
|
||||
title: "You're too far",
|
||||
message: 'Please move closer to the designated location.',
|
||||
primaryButtonLabel: 'OK',
|
||||
onPrimaryButtonPressed: (dialogContext) {
|
||||
dialogContext.router.maybePop();
|
||||
dialogAlreadyOpen = false;
|
||||
|
||||
},
|
||||
);
|
||||
break;
|
||||
case GeofencingProximityState.locationDisabled:
|
||||
future = KwDialog.show(
|
||||
context: context,
|
||||
icon: Assets.images.icons.alertTriangle,
|
||||
state: KwDialogState.negative,
|
||||
title: 'Location Disabled',
|
||||
message: 'Please enable location services to continue.',
|
||||
primaryButtonLabel: 'Go to Settings',
|
||||
onPrimaryButtonPressed: (dialogContext) async {
|
||||
dialogContext.router.maybePop();
|
||||
dialogAlreadyOpen = false;
|
||||
await Geolocator.openLocationSettings();
|
||||
|
||||
},
|
||||
);
|
||||
break;
|
||||
case GeofencingProximityState.goToSettings:
|
||||
future = KwDialog.show(
|
||||
context: context,
|
||||
icon: Assets.images.icons.alertTriangle,
|
||||
state: KwDialogState.info,
|
||||
title: 'Permission Required',
|
||||
message: 'You need to allow location access in settings.',
|
||||
primaryButtonLabel: 'Open Settings',
|
||||
onPrimaryButtonPressed: (dialogContext) async {
|
||||
dialogContext.maybePop();
|
||||
dialogAlreadyOpen = false;
|
||||
|
||||
await Geolocator.openLocationSettings();
|
||||
|
||||
},
|
||||
);
|
||||
break;
|
||||
case GeofencingProximityState.onlyInUse:
|
||||
future = KwDialog.show(
|
||||
context: context,
|
||||
icon: Assets.images.icons.alertTriangle,
|
||||
state: KwDialogState.info,
|
||||
title: 'Track "All the time" required',
|
||||
message:
|
||||
'To ensure accurate time tracking, we need access to your location. Time tracking will automatically stop if you move more than 500 meters away from your assigned work location. '
|
||||
'Please grant “Allow all the time” access to your location. '
|
||||
'Go to Settings → Permissions → Location and select “Allow all the time”.',
|
||||
primaryButtonLabel: 'Open Settings',
|
||||
onPrimaryButtonPressed: (dialogContext) async {
|
||||
dialogContext.maybePop();
|
||||
dialogAlreadyOpen = false;
|
||||
await Geolocator.openLocationSettings();
|
||||
},
|
||||
);
|
||||
break;
|
||||
case GeofencingProximityState.permissionDenied:
|
||||
future = KwDialog.show(
|
||||
context: context,
|
||||
icon: Assets.images.icons.alertTriangle,
|
||||
state: KwDialogState.negative,
|
||||
title: 'Permission Denied',
|
||||
message:
|
||||
'You have denied location access. Please allow it manually.',
|
||||
primaryButtonLabel: 'OK',
|
||||
onPrimaryButtonPressed: (dialogContext) async {
|
||||
dialogContext.maybePop();
|
||||
dialogAlreadyOpen = false;
|
||||
await Geolocator.openAppSettings();
|
||||
});
|
||||
break;
|
||||
default:
|
||||
dialogAlreadyOpen = false;
|
||||
break;
|
||||
}
|
||||
await future;
|
||||
dialogAlreadyOpen = false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user