feat: legacy mobile apps created

This commit is contained in:
Achintha Isuru
2025-12-02 23:51:04 -05:00
parent 850441ca64
commit 8e7753b324
1519 changed files with 0 additions and 16 deletions

View File

@@ -0,0 +1,76 @@
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/theme.dart';
import 'package:krow/core/presentation/widgets/ui_kit/kw_button.dart';
import 'package:krow/features/shifts/data/models/staff_shift.dart';
import 'package:krow/features/shifts/domain/blocs/shift_deteils_bloc/shift_details_bloc.dart';
import 'package:krow/features/shifts/presentation/dialogs/cancel_dialog/shift_cancel_dialog.dart';
import 'package:krow/features/shifts/presentation/dialogs/decline_dialog/shift_decline_dialog.dart';
class ShiftButtonsWidget extends StatelessWidget {
final EventShiftRoleStaffStatus status;
const ShiftButtonsWidget(this.status, {super.key});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: const Alignment(0, -0.5),
colors: [
AppColors.grayWhite,
AppColors.grayWhite.withAlpha(0),
],
),
),
child: SafeArea(
child: Column(
children: status == EventShiftRoleStaffStatus.assigned
? [
KwButton.primary(
label: 'accept_shift'.tr(),
onPressed: () {
BlocProvider.of<ShiftDetailsBloc>(context).add(
const ShiftConfirmEvent(),
);
},
fit: KwButtonFit.expanded,
),
const Gap(12),
KwButton.outlinedPrimary(
label: 'decline_shift'.tr(),
onPressed: () async {
var result =
await ShiftDeclineDialog.showCustomDialog(context);
if (result != null && context.mounted) {
BlocProvider.of<ShiftDetailsBloc>(context).add(
ShiftDeclineEvent(
result['reason'], result['additionalReason']),
);
}
}).copyWith(color: AppColors.statusError)
]
: [
KwButton.outlinedPrimary(
label: 'cancel_shift'.tr(),
onPressed: () async {
var result =
await ShiftCancelDialog.showCustomDialog(context);
if (result != null && context.mounted) {
BlocProvider.of<ShiftDetailsBloc>(context).add(
ShiftCancelEvent(
result['reason'], result['additionalReason']),
);
}
}).copyWith(color: AppColors.statusError)
],
),
),
);
}
}

View File

@@ -0,0 +1,51 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:intl/intl.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/features/shifts/domain/shift_entity.dart';
class ShiftInfoClockTimeWidget extends StatelessWidget {
final ShiftEntity viewModel;
const ShiftInfoClockTimeWidget({super.key, required this.viewModel});
@override
Widget build(BuildContext context) {
var clockIn = DateFormat('h:mma','en')
.format(viewModel.clockIn ?? DateTime.now())
.toLowerCase();
var clockOut = DateFormat('h:mma','en')
.format(viewModel.clockOut ?? DateTime.now())
.toLowerCase();
return Expanded(
child: Container(
padding: const EdgeInsets.all(12),
decoration: KwBoxDecorations.primaryLight12,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${'clock_in_1'.tr()} - ${'clock_out_1'.tr()}'.toUpperCase(),
style: AppTextStyles.captionReg
.copyWith(color: AppColors.blackCaptionText)),
const Gap(16),
Text(clockIn, style: AppTextStyles.headingH3),
const Gap(6),
Text('clock_in_1'.tr(),
style: AppTextStyles.bodySmallReg
.copyWith(color: AppColors.blackGray)),
const Gap(12),
Text(clockOut, style: AppTextStyles.headingH3),
const Gap(6),
Text('clock_out_1'.tr(),
style: AppTextStyles.bodySmallReg
.copyWith(color: AppColors.blackGray)),
],
),
),
);
}
}

View File

@@ -0,0 +1,54 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.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/features/shifts/domain/shift_entity.dart';
class ShiftInfoPlaningDurationWidget extends StatelessWidget {
final ShiftEntity viewModel;
const ShiftInfoPlaningDurationWidget({super.key, required this.viewModel});
@override
Widget build(BuildContext context) {
var formattedDuration =
_format(viewModel.endDate.difference(viewModel.startDate).inMinutes);
var formattedBreak = _format(viewModel.planingBreakTime!);
return Expanded(
child: Container(
padding: const EdgeInsets.all(12),
decoration: KwBoxDecorations.primaryLight12,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('duration'.tr().toUpperCase(),
style: AppTextStyles.captionReg
.copyWith(color: AppColors.blackCaptionText)),
const Gap(16),
Text(formattedDuration, style: AppTextStyles.headingH3),
const Gap(6),
Text('shift_duration'.tr(),
style: AppTextStyles.bodySmallReg
.copyWith(color: AppColors.blackGray)),
const Gap(12),
Text(formattedBreak, style: AppTextStyles.headingH3),
const Gap(6),
Text('break_duration'.tr(),
style: AppTextStyles.bodySmallReg
.copyWith(color: AppColors.blackGray)),
],
),
),
);
}
String _format(int? duration) {
if (duration == null) return '0 hours';
var hours = duration ~/ 60;
var minutes = duration % 60;
var hoursStr = 'hours_1'.tr(namedArgs: {'hours':hours.toString(),'plural':hours == 1 ? '' : 's'});
return minutes == 0 ? hoursStr : 'hours_minutes'.tr(namedArgs: {'hours':hours.toString(),'minutes':minutes.toString()});
}
}

View File

@@ -0,0 +1,48 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:intl/intl.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/features/shifts/domain/shift_entity.dart';
class ShiftInfoStartWidget extends StatelessWidget {
final ShiftEntity viewModel;
const ShiftInfoStartWidget({super.key, required this.viewModel});
@override
Widget build(BuildContext context) {
var dateTime = viewModel.startDate;
var date = DateFormat('MMMM d', context.locale.languageCode).format(dateTime);
var time = DateFormat('h:mma', 'en').format(dateTime).toLowerCase();
return Expanded(
child: Container(
padding: const EdgeInsets.all(12),
decoration: KwBoxDecorations.primaryLight12,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('start'.tr().toUpperCase(),
style: AppTextStyles.captionReg
.copyWith(color: AppColors.blackCaptionText)),
const Gap(16),
Text(date, style: AppTextStyles.headingH3),
const Gap(6),
Text('date'.tr(),
style: AppTextStyles.bodySmallReg
.copyWith(color: AppColors.blackGray)),
const Gap(12),
Text(time, style: AppTextStyles.headingH3),
const Gap(6),
Text('time'.tr(),
style: AppTextStyles.bodySmallReg
.copyWith(color: AppColors.blackGray)),
],
),
),
);
}
}

View File

@@ -0,0 +1,60 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.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/features/shifts/domain/shift_entity.dart';
class ShiftInfoTotalDurationWidget extends StatelessWidget {
final ShiftEntity viewModel;
const ShiftInfoTotalDurationWidget({super.key, required this.viewModel});
@override
Widget build(BuildContext context) {
var formattedDuration;
if (viewModel.clockOut == null || viewModel.clockIn == null) {
formattedDuration = '00:00:00';
}
formattedDuration = _format(viewModel.clockOut
?.difference(viewModel.clockIn ?? DateTime.now())
.inSeconds ??
0);
var formattedBreak = _format(viewModel.totalBreakTime!);
return Expanded(
child: Container(
padding: const EdgeInsets.all(12),
decoration: KwBoxDecorations.primaryLight12,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('total_time_breaks'.tr().toUpperCase(),
style: AppTextStyles.captionReg
.copyWith(color: AppColors.blackCaptionText)),
const Gap(16),
Text(formattedBreak, style: AppTextStyles.headingH3),
const Gap(6),
Text('break_hours'.tr(),
style: AppTextStyles.bodySmallReg
.copyWith(color: AppColors.blackGray)),
const Gap(12),
Text(formattedDuration, style: AppTextStyles.headingH3),
const Gap(6),
Text('total_hours'.tr(),
style: AppTextStyles.bodySmallReg
.copyWith(color: AppColors.blackGray)),
],
),
),
);
}
String _format(int duration) {
var hours = (duration ~/ 3600).toString().padLeft(2, '0');
var minutes = ((duration % 3600) ~/ 60).toString().padLeft(2, '0');
var seconds = (duration % 60).toString().padLeft(2, '0');
return '$hours:$minutes:$seconds';
}
}

View File

@@ -0,0 +1,99 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.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/features/shifts/data/models/cancellation_reason.dart';
class ShiftItemCanceledWidget extends StatelessWidget {
final CancellationReason? reason;
final bool canceledByUser;
const ShiftItemCanceledWidget(
{super.key, required this.reason, required this.canceledByUser});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12.0),
margin: const EdgeInsets.only(left: 16, right: 16, top: 8),
decoration:
KwBoxDecorations.primaryLight8.copyWith(color: AppColors.tintBlue),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'your_shift_canceled'.tr(),
style: AppTextStyles.bodyMediumMed
.copyWith(color: AppColors.primaryBlue),
),
],
),
const Gap(8),
Text(
'please_review_reason'.tr(),
style: AppTextStyles.bodyTinyReg
.copyWith(color: AppColors.primaryBlue)),
const Gap(8),
Row(
children: [
Text(
'canceled_by'.tr(),
style: AppTextStyles.bodyTinyReg
.copyWith(color: AppColors.tintDarkBlue),
),
const Gap(4),
Text(
canceledByUser ? 'user'.tr() : 'admin'.tr(),
style: AppTextStyles.bodyTinyReg
.copyWith(color: AppColors.primaryBlue),
),
],
),
const Gap(8),
Container(
padding: const EdgeInsets.all(12.0),
decoration: KwBoxDecorations.primaryLight6,
child: Row(
children: [
Text(
'${'reason'.tr()}:',
style: AppTextStyles.bodyMediumReg
.copyWith(color: AppColors.blackGray),
),
const Gap(4),
Text(reasonToString(reason).tr(),
style: AppTextStyles.bodyMediumMed),
],
),
),
],
),
);
}
String reasonToString(CancellationReason? reason) {
switch (reason) {
case CancellationReason.sickLeave:
return 'sick_leave';
case CancellationReason.vacation:
return 'vacation';
case CancellationReason.other:
return 'Other';
case CancellationReason.health:
return 'health';
case CancellationReason.transportation:
return 'transportation';
case CancellationReason.personal:
return 'personal';
case CancellationReason.scheduleConflict:
return 'schedule_conflict';
case null:
return 'other';
}
}
}

View File

@@ -0,0 +1,106 @@
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/features/shifts/domain/shift_entity.dart';
class ShiftAdditionalDetails extends StatefulWidget {
final ShiftEntity viewModel;
const ShiftAdditionalDetails({super.key, required this.viewModel});
@override
State<ShiftAdditionalDetails> createState() => _ShiftAdditionalDetailsState();
}
class _ShiftAdditionalDetailsState extends State<ShiftAdditionalDetails> {
bool expanded = false;
@override
Widget build(BuildContext context) {
if (widget.viewModel.additionalData?.isEmpty ?? true) {
return const Gap(12);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Gap(12),
Container(
margin: const EdgeInsets.symmetric(horizontal: 20),
height: 1,
color: AppColors.grayTintStroke,
),
const Gap(12),
GestureDetector(
onTap: () {
setState(() {
expanded = !expanded;
});
},
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 20),
height: 16,
color: Colors.transparent,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('additional_details'.tr(),
style: AppTextStyles.captionReg),
const Spacer(),
AnimatedRotation(
duration: const Duration(milliseconds: 150),
turns: expanded ? -0.5 : 0,
child: Assets.images.icons.caretDown.svg(
width: 16,
height: 16,
colorFilter: const ColorFilter.mode(
AppColors.blackBlack, BlendMode.srcIn))),
],
),
),
),
if (widget.viewModel.additionalData?.isNotEmpty ?? false) ...[
const Gap(12),
_buildExpandedDetails(),
]
],
);
}
_buildExpandedDetails() {
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
clipBehavior: Clip.antiAlias,
height: expanded ? widget.viewModel.additionalData!.length * 28 + 12 : 0,
padding: const EdgeInsets.only(left: 20, right: 20, bottom: 12),
decoration: const BoxDecoration(
color: AppColors.graySecondaryFrame,
borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
...widget.viewModel.additionalData?.map(
(e) {
return Padding(
padding: const EdgeInsets.only(top: 12),
child: Row(
children: [
Text(e.name ?? '',
style: AppTextStyles.bodySmallReg
.copyWith(color: AppColors.blackGray)),
const Spacer(),
Text('yes'.tr(), style: AppTextStyles.bodySmallReg),
],
),
);
},
) ??
[],
],
),
);
}
}

View File

@@ -0,0 +1,62 @@
import 'package:cached_network_image/cached_network_image.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/features/shifts/domain/shift_entity.dart';
import 'package:krow/features/shifts/presentation/widgets/shift_item_components/shift_status_label_widget.dart';
class ShiftItemHeaderWidget extends StatelessWidget {
final ShiftEntity viewModel;
const ShiftItemHeaderWidget(this.viewModel, {super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
viewModel.imageUrl.isNotEmpty
? ClipOval(
child: CachedNetworkImage(
imageUrl: viewModel.imageUrl,
fit: BoxFit.cover,
height: 48,
width: 48,
))
: const SizedBox.shrink(),
ShiftStatusLabelWidget(viewModel),
],
),
const Gap(12),
Row(
children: [
Expanded(
child: Text(
viewModel.skillName,
style: AppTextStyles.bodyMediumMed,
)),
const Gap(24),
Text(
'\$${viewModel.rate}/h',
style: AppTextStyles.bodyMediumMed,
)
],
),
const Gap(4),
Text(
viewModel.eventName,
style:
AppTextStyles.bodySmallReg.copyWith(color: AppColors.blackGray),
),
],
),
);
}
}

View File

@@ -0,0 +1,98 @@
import 'dart:async';
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/features/shifts/domain/shift_entity.dart';
Timer? shiftListTimer;
class ShiftOngoingCounterWidget extends StatefulWidget {
final ShiftEntity viewModel;
const ShiftOngoingCounterWidget({super.key, required this.viewModel});
@override
State<ShiftOngoingCounterWidget> createState() =>
_ShiftOngoingCounterWidgetState();
}
class _ShiftOngoingCounterWidgetState extends State<ShiftOngoingCounterWidget> {
@override
void initState() {
super.initState();
shiftListTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (mounted) {
setState(() {});
}
});
}
@override
Widget build(BuildContext context) {
var duration =
DateTime.now().difference(widget.viewModel.clockIn ?? DateTime.now());
var hours = duration.inHours.remainder(24).abs().toString().padLeft(2, '0');
var minutes =
duration.inMinutes.remainder(60).abs().toString().padLeft(2, '0');
var seconds =
duration.inSeconds.remainder(60).abs().toString().padLeft(2, '0');
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
height: 80,
margin: const EdgeInsets.only(top: 24, left: 24, right: 24),
decoration: BoxDecoration(
color: AppColors.tintGreen,
border: Border.all(color: AppColors.tintDarkGreen),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildTimeText('hours'.tr(), hours),
_divider(),
_buildTimeText('minutes'.tr(), minutes),
_divider(),
_buildTimeText('seconds'.tr(), seconds),
],
),
),
],
);
}
Widget _divider() {
return SizedBox(
width: 24,
child: Center(
child: Container(
height: 24,
width: 1,
color: AppColors.tintDarkGreen,
),
),
);
}
Widget _buildTimeText(String label, String time) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(time,
style: AppTextStyles.headingH3
.copyWith(color: AppColors.statusSuccess)),
const Gap(6),
Text(
label,
style: AppTextStyles.bodySmallReg
.copyWith(color: AppColors.statusSuccess),
),
],
);
}
}

View File

@@ -0,0 +1,77 @@
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/features/shifts/domain/shift_entity.dart';
class ShiftPlaceAndTimeWidget extends StatelessWidget {
final ShiftEntity viewModel;
const ShiftPlaceAndTimeWidget(this.viewModel, {super.key});
@override
Widget build(BuildContext context) {
final timeFormat = DateFormat('MMMM d, h:mma', context.locale.languageCode);
return Stack(
children: [
SizedBox(
height: 28,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(top: 12.0),
child: Container(
constraints: BoxConstraints(
minWidth: MediaQuery.of(context).size.width - 32),
child: IntrinsicWidth(
child: Row(
children: [
const Gap(16),
Assets.images.icons.location.svg(width: 16, height: 16),
const Gap(4),
Text(
viewModel.locationName,
style: AppTextStyles.bodySmallReg,
),
const Spacer(),
const Gap(12),
Assets.images.icons.calendar.svg(width: 16, height: 16),
const Gap(4),
Text(
timeFormat.format(viewModel.startDate),
style: AppTextStyles.bodySmallReg,
),
const Gap(16),
],
),
),
),
);
},
),
),
Positioned(
right: 0,
child: Container(
height: 28,
width: 20,
decoration: BoxDecoration(
color: Colors.red,
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
AppColors.grayPrimaryFrame.withAlpha(50),
AppColors.grayPrimaryFrame.withAlpha(255),
],
),
),
),
),
],
);
}
}

View File

@@ -0,0 +1,104 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
import 'package:krow/core/presentation/styles/theme.dart';
import 'package:krow/features/shifts/data/models/staff_shift.dart';
import 'package:krow/features/shifts/domain/shift_entity.dart';
class ShiftStatusLabelWidget extends StatefulWidget {
final ShiftEntity viewModel;
const ShiftStatusLabelWidget(this.viewModel, {super.key});
@override
State<ShiftStatusLabelWidget> createState() => _ShiftStatusLabelWidgetState();
}
class _ShiftStatusLabelWidgetState extends State<ShiftStatusLabelWidget> {
Color getColor() {
switch (widget.viewModel.status) {
case EventShiftRoleStaffStatus.assigned:
return AppColors.primaryBlue;
case EventShiftRoleStaffStatus.confirmed:
return AppColors.statusWarning;
case EventShiftRoleStaffStatus.ongoing:
return AppColors.statusSuccess;
case EventShiftRoleStaffStatus.completed:
return AppColors.bgColorDark;
case EventShiftRoleStaffStatus.canceledByAdmin:
case EventShiftRoleStaffStatus.canceledByBusiness:
case EventShiftRoleStaffStatus.canceledByStaff:
case EventShiftRoleStaffStatus.requestedReplace:
case EventShiftRoleStaffStatus.declineByStaff:
return AppColors.statusError;
}
}
String getText() {
switch (widget.viewModel.status) {
case EventShiftRoleStaffStatus.assigned:
return _getAssignedAgo();
case EventShiftRoleStaffStatus.confirmed:
return _getStartIn();
case EventShiftRoleStaffStatus.ongoing:
return 'ongoing'.tr();
case EventShiftRoleStaffStatus.completed:
return 'completed'.tr();
case EventShiftRoleStaffStatus.declineByStaff:
return 'declined'.tr();
case EventShiftRoleStaffStatus.canceledByAdmin:
case EventShiftRoleStaffStatus.canceledByBusiness:
case EventShiftRoleStaffStatus.canceledByStaff:
case EventShiftRoleStaffStatus.requestedReplace:
return 'canceled'.tr();
}
}
String _getStartIn() {
var duration = widget.viewModel.startDate.difference(DateTime.now());
var startIn = '';
if (duration.inMinutes < 0) {
return 'started'.tr();
}
if (duration.inDays > 0) {
startIn = '${duration.inDays}d ${duration.inHours.remainder(24)}h';
} else if (duration.inHours.abs() > 0) {
startIn = '${duration.inHours}h ${duration.inMinutes.remainder(60)}min';
} else {
startIn = '${duration.inMinutes}min';
}
return 'starts_in'.tr(namedArgs: {'time': startIn});
}
String _getAssignedAgo() {
var duration = DateTime.now().difference(widget.viewModel.assignedDate);
var timeAgo = '';
if (duration.inDays > 0) {
timeAgo = '${duration.inDays}d ago';
} else if (duration.inHours > 0) {
timeAgo = '${duration.inHours}h ago';
} else {
timeAgo = '${duration.inMinutes}m ago';
}
return 'assigned_ago'.tr(namedArgs: {'time': timeAgo});
}
@override
Widget build(BuildContext context) {
return Container(
height: 24,
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: getColor(),
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text(
getText(),
style: AppTextStyles.bodySmallMed
.copyWith(color: AppColors.grayWhite, height: 0.7),
),
),
);
}
}

View File

@@ -0,0 +1,104 @@
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/features/shifts/data/models/event_tag.dart';
import 'package:krow/features/shifts/domain/shift_entity.dart';
class ShiftTagsWidget extends StatelessWidget {
final ShiftEntity viewModel;
final bool scrollable;
const ShiftTagsWidget(this.viewModel, {this.scrollable = false, super.key});
final textColors = const {
'1': AppColors.statusWarningBody,
'2': AppColors.statusError,
'3': AppColors.statusSuccess,
};
final backGroundsColors = const {
'1': AppColors.tintYellow,
'2': AppColors.tintRed,
'3': AppColors.tintGreen,
};
@override
Widget build(BuildContext context) {
if (viewModel.tags == null || viewModel.tags!.isEmpty) {
return const SizedBox.shrink();
}
return Column(
children: [
const Gap(12),
if (scrollable) _buildScrollableList(),
if (!scrollable) _buildChips(),
],
);
}
Widget _buildScrollableList() {
return SizedBox(
height: 44,
child: ListView.builder(
padding: const EdgeInsets.only(left: 16, right: 12),
itemCount: viewModel.tags?.length ?? 0,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return _buildTag(viewModel.tags![index]);
},
),
);
}
Widget _buildChips() {
return Padding(
padding: const EdgeInsets.only(left: 16, right: 12),
child: Wrap(
runSpacing: 4,
children: viewModel.tags!.map((e) => _buildTag(e)).toList(),
),
);
}
Widget _buildTag(EventTag shiftTage) {
return Container(
margin: const EdgeInsets.only(right: 4),
padding: const EdgeInsets.all(8),
height: 44,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: backGroundsColors[shiftTage.id]??AppColors.tintGreen,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 28,
width: 28,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: AppColors.grayWhite,
),
child: Center(
child: Assets.images.icons.eye.svg(
width: 12,
height: 12,
colorFilter: ColorFilter.mode(
textColors[shiftTage.id] ?? AppColors.statusWarningBody,
BlendMode.srcIn)),
),
),
const Gap(8),
Text(
shiftTage.name,
style: AppTextStyles.bodySmallReg
.copyWith(color: textColors[shiftTage.id]??AppColors.statusSuccess),
),
const Gap(8),
],
),
);
}
}

View File

@@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import 'package:krow/core/presentation/styles/kw_box_decorations.dart';
import 'package:krow/core/presentation/widgets/shift_payment_step_widget.dart';
import 'package:krow/core/presentation/widgets/shift_total_time_spend_widget.dart';
import 'package:krow/features/shifts/data/models/staff_shift.dart';
import 'package:krow/features/shifts/domain/shift_entity.dart';
import 'package:krow/features/shifts/presentation/widgets/shift_item_canceled_widget.dart';
import 'package:krow/features/shifts/presentation/widgets/shift_item_components/shift_additional_details_widget.dart';
import 'package:krow/features/shifts/presentation/widgets/shift_item_components/shift_item_header_widget.dart';
import 'package:krow/features/shifts/presentation/widgets/shift_item_components/shift_ongoing_counter_widget.dart';
import 'package:krow/features/shifts/presentation/widgets/shift_item_components/shift_place_and_time_widget.dart';
import 'package:krow/features/shifts/presentation/widgets/shift_item_components/shift_tags_widget.dart';
class ShiftItemWidget extends StatelessWidget {
final ShiftEntity viewModel;
final bool isDetailsMode;
final VoidCallback? onPressed;
final double bottomPadding;
const ShiftItemWidget(this.viewModel,
{this.bottomPadding = 0,
this.onPressed,
this.isDetailsMode = false,
super.key});
@override
Widget build(BuildContext context) {
var showPlaceAndTime = !isDetailsMode &&
(viewModel.status == EventShiftRoleStaffStatus.assigned ||
viewModel.status == EventShiftRoleStaffStatus.confirmed);
var showTags =
isDetailsMode || viewModel.status == EventShiftRoleStaffStatus.assigned;
var showOngoingCounter =
!isDetailsMode && viewModel.status == EventShiftRoleStaffStatus.ongoing;
var showTotalTimeSpend = !isDetailsMode &&
viewModel.status == EventShiftRoleStaffStatus.completed;
var showAdditionalDetails =
isDetailsMode || viewModel.status == EventShiftRoleStaffStatus.ongoing;
var canceled =
viewModel.status == EventShiftRoleStaffStatus.canceledByStaff ||
viewModel.status == EventShiftRoleStaffStatus.canceledByBusiness ||
viewModel.status == EventShiftRoleStaffStatus.canceledByAdmin;
return GestureDetector(
onTap: canceled?null:onPressed,
child: Container(
decoration: KwBoxDecorations.primaryLight12,
margin: EdgeInsets.only(bottom: bottomPadding, left: 16, right: 16),
padding:
EdgeInsets.only(top: 24, bottom: showAdditionalDetails ? 0 : 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ShiftItemHeaderWidget(viewModel),
if (showPlaceAndTime) ShiftPlaceAndTimeWidget(viewModel),
if (showTags)
ShiftTagsWidget(viewModel, scrollable: !isDetailsMode),
if (showOngoingCounter)
ShiftOngoingCounterWidget(viewModel: viewModel),
if (showTotalTimeSpend)
ShiftTotalTimeSpendWidget(
startTime: viewModel.clockIn?? DateTime.now(),
endTime: viewModel.clockOut?? viewModel.clockIn??DateTime.now(),
totalBreakTime: viewModel.totalBreakTime ?? 0,
),
if (showTotalTimeSpend)
const ShiftPaymentStepWidget(
currentIndex: 1,
),
if (showAdditionalDetails)
ShiftAdditionalDetails(viewModel: viewModel),
if (canceled)
ShiftItemCanceledWidget(
reason: viewModel.cancellationReason,
canceledByUser: viewModel.status ==
EventShiftRoleStaffStatus.canceledByStaff),
],
),
),
);
}
}

View File

@@ -0,0 +1,90 @@
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_box_decorations.dart';
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
import 'package:krow/core/presentation/styles/theme.dart';
class ShiftKeyResponsibilitiesWidget extends StatelessWidget {
final String text;
final bool isExpanded;
final bool expandable;
final VoidCallback onTap;
const ShiftKeyResponsibilitiesWidget(
{super.key,
required this.text,
required this.expandable,
required this.isExpanded,
required this.onTap});
@override
Widget build(BuildContext context) {
return AnimatedSize(
duration: const Duration(milliseconds: 300),
clipBehavior: Clip.antiAlias,
alignment: Alignment.topCenter,
child: Container(
clipBehavior: Clip.antiAlias,
height: isExpanded ? null : 40,
margin: const EdgeInsets.only(top: 8, left: 16, right: 16),
padding: const EdgeInsets.all(12),
decoration: KwBoxDecorations.primaryLight12,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
expandable ? expandableTitle() : fixedTitle(),
const Gap(16),
Text(
text,
style: AppTextStyles.bodySmallReg
.copyWith(color: AppColors.blackGray),
),
],
),
),
);
}
Widget fixedTitle() {
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text('${'key_responsibilities'.tr()}:'.toUpperCase(),
style: AppTextStyles.captionReg
.copyWith(color: AppColors.blackCaptionText)),
]);
}
Widget expandableTitle() {
return GestureDetector(
onTap: onTap,
child: Container(
color: Colors.transparent,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('additional_information'.tr().toUpperCase(),
style: AppTextStyles.bodySmallReg
.copyWith(color: AppColors.blackGray)),
],
),
AnimatedRotation(
duration: const Duration(milliseconds: 150),
turns: isExpanded ? -0.5 : 0,
child: Assets.images.icons.caretDown.svg(
width: 16,
height: 16,
colorFilter: const ColorFilter.mode(
AppColors.blackBlack, BlendMode.srcIn))),
],
),
),
);
}
}

View File

@@ -0,0 +1,106 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:krow/core/application/common/map_utils.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/kw_button.dart';
import 'package:krow/features/shifts/data/models/staff_shift.dart';
import 'package:krow/features/shifts/domain/shift_entity.dart';
class ShiftLocationWidget extends StatelessWidget {
final ShiftEntity viewModel;
const ShiftLocationWidget({super.key, required this.viewModel});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(top: 8, left: 16, right: 16),
padding: const EdgeInsets.all(12),
decoration: KwBoxDecorations.primaryLight12,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'location'.tr().toUpperCase(),
style: AppTextStyles.captionReg
.copyWith(color: AppColors.blackCaptionText),
),
const Gap(16),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Text(
viewModel.locationName,
style: AppTextStyles.headingH3.copyWith(height: 1),
),
),
),
const Gap(16),
KwButton.outlinedPrimary(
label: 'get_direction'.tr(),
leftIcon: Assets.images.icons.routing,
onPressed: () {
MapUtils.openMapByLatLon(
viewModel.locationLat,
viewModel.locationLon,
);
},
),
],
),
if (viewModel.status != EventShiftRoleStaffStatus.completed)
_buildMap(),
],
),
);
}
Widget _buildMap() {
if (viewModel.locationLat == 0 && viewModel.locationLon == 0) {
return Container(
height: 166,
margin: const EdgeInsets.only(top: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
),
);
}
return Container(
margin: const EdgeInsets.only(top: 16),
height: 166,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: GoogleMap(
rotateGesturesEnabled: false,
compassEnabled: false,
zoomControlsEnabled: false,
scrollGesturesEnabled: false,
zoomGesturesEnabled: false,
tiltGesturesEnabled: false,
mapToolbarEnabled: false,
myLocationButtonEnabled: false,
markers: {
Marker(
markerId: const MarkerId('1'),
position: LatLng(viewModel.locationLat, viewModel.locationLon),
),
},
initialCameraPosition: CameraPosition(
target: LatLng(viewModel.locationLat, viewModel.locationLon),
zoom: 12,
),
),
),
);
}
}

View File

@@ -0,0 +1,113 @@
import 'package:cached_network_image/cached_network_image.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_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_button.dart';
import 'package:krow/features/shifts/domain/shift_entity.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:whatsapp_unilink/whatsapp_unilink.dart';
class ShiftManageWidget extends StatelessWidget {
final List<ShiftManager> managers;
const ShiftManageWidget({super.key, required this.managers});
@override
Widget build(BuildContext context) {
if (managers.isEmpty) return const SizedBox.shrink();
return Container(
padding: const EdgeInsets.all(12.0),
margin: const EdgeInsets.only(left: 16, right: 16, top: 8),
decoration: KwBoxDecorations.primaryLight12,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'manage_contact_details'.tr().toUpperCase(),
style: AppTextStyles.captionReg
.copyWith(color: AppColors.blackCaptionText),
),
const Gap(16),
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: managers.length,
itemBuilder: (context, index) {
return _buildManager(managers[index]);
},
separatorBuilder: (context, index) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 8),
child: Divider(
color: AppColors.grayTintStroke,
),
);
},
),
],
),
);
}
Widget _buildManager(ShiftManager manager) {
return Row(
children: [
if(manager.imageUrl.isNotEmpty)
CircleAvatar(
radius: 24.0,
backgroundColor: AppColors.grayWhite,
backgroundImage: CachedNetworkImageProvider(
manager.imageUrl), // Replace with your image asset
),
const SizedBox(width: 16.0),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
manager.name,
style: AppTextStyles.bodyMediumSmb,
),
const SizedBox(height: 4.0),
Text(
manager.phoneNumber,
style: AppTextStyles.bodySmallReg
.copyWith(color: AppColors.blackGray),
),
],
),
),
const SizedBox(width: 8.0),
Row(
children: [
KwButton.outlinedPrimary(
rightIcon: Assets.images.icons.whatsapp,
fit: KwButtonFit.circular,
onPressed: () {
var link = WhatsAppUnilink(
phoneNumber: manager.phoneNumber,
text: '',
);
launchUrlString(link.toString());
},
),
const SizedBox(width: 8.0),
KwButton.outlinedPrimary(
label: 'call'.tr(),
rightIcon: Assets.images.icons.call,
onPressed: () {
launchUrlString('tel:${manager.phoneNumber}');
},
).copyWith(
color: AppColors.tintDarkGreen,
textColors: AppColors.statusSuccess),
],
)
],
);
}
}

View File

@@ -0,0 +1,33 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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/shift_payment_step_widget.dart';
import 'package:krow/features/shifts/domain/shift_entity.dart';
class ShiftPaymentStepCardWidget extends StatelessWidget {
final ShiftEntity viewModel;
const ShiftPaymentStepCardWidget({super.key, required this.viewModel});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12.0),
margin: const EdgeInsets.only(left: 16, right: 16, top: 8),
decoration: KwBoxDecorations.primaryLight12,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'payment_status'.tr().toUpperCase(),
style: AppTextStyles.captionReg
.copyWith(color: AppColors.blackCaptionText),
),
const ShiftPaymentStepWidget(currentIndex: 1),
],
),
);
}
}

View File

@@ -0,0 +1,60 @@
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_box_decorations.dart';
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
import 'package:krow/core/presentation/styles/theme.dart';
import 'package:krow/features/shifts/domain/shift_entity.dart';
class ShiftRatingWidget extends StatelessWidget {
final ShiftEntity viewModel;
const ShiftRatingWidget({super.key, required this.viewModel});
final double maxRating = 5.0; // Maximum rating, e.g., 5
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12.0),
margin: const EdgeInsets.only(left: 16, right: 16, top: 8),
decoration: KwBoxDecorations.primaryLight12,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'clients_rate'.tr().toUpperCase(),
style: AppTextStyles.captionReg
.copyWith(color: AppColors.blackCaptionText),
),
const Gap(4),
Center(
child: Text(viewModel.rating?.rating.toStringAsFixed(1) ?? '0.0',
style: AppTextStyles.headingH0),
),
const Gap(4),
_buildRating(viewModel.rating?.rating ?? 0),
],
),
);
}
_buildRating(double rating) {
List<Widget> stars = [];
for (int i = 1; i <= maxRating; i++) {
if (i <= rating) {
stars.add(Assets.images.icons.ratingStar.star.svg());
} else if (i - rating < 1) {
stars.add(Assets.images.icons.ratingStar.starHalf.svg());
} else {
stars.add(Assets.images.icons.ratingStar.starEmpty.svg());
}
}
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [const Gap(28), ...stars, const Gap(28)],
);
}
}

View File

@@ -0,0 +1,218 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gap/gap.dart';
import 'package:krow/core/application/common/map_utils.dart';
import 'package:krow/core/application/routing/routes.gr.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_button.dart';
import 'package:krow/features/shifts/data/models/staff_shift.dart';
import 'package:krow/features/shifts/domain/blocs/shift_deteils_bloc/shift_details_bloc.dart';
import 'package:krow/features/shifts/domain/services/shift_completer_service.dart';
import 'package:krow/features/shifts/domain/shift_entity.dart';
import 'package:krow/features/shifts/presentation/widgets/shift_timer_widgets/shift_timer_widget.dart';
class ShiftTimerCardWidget extends StatelessWidget {
const ShiftTimerCardWidget({super.key});
bool _hasBorder(EventShiftRoleStaffStatus status) =>
status == EventShiftRoleStaffStatus.ongoing;
@override
Widget build(BuildContext context) {
return BlocBuilder<ShiftDetailsBloc, ShiftDetailsState>(
buildWhen: (previous, current) =>
previous.isToFar != current.isToFar ||
previous.shiftViewModel != current.shiftViewModel,
builder: (context, state) {
return Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(top: 8, left: 16, right: 16),
decoration: KwBoxDecorations.primaryLight12.copyWith(
border: _hasBorder(state.shiftViewModel.status)
? Border.all(color: AppColors.tintDarkGreen, width: 2)
: null,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'timer'.tr().toUpperCase(),
style: AppTextStyles.captionReg
.copyWith(color: AppColors.blackCaptionText),
),
const Gap(16),
ShiftTimerWidget(
shiftStatus: state.shiftViewModel.status,
clockIn: state.shiftViewModel.clockIn,
clockOut: state.shiftViewModel.clockOut,
),
_buildButton(
context,
state.isToFar,
state.shiftViewModel,
),
],
),
);
},
);
}
_buildButton(
context,
bool isToFar,
ShiftEntity viewModel,
) {
final status = viewModel.status;
return Column(
children: [
if (isToFar && status == EventShiftRoleStaffStatus.confirmed ||
status == EventShiftRoleStaffStatus.ongoing)
_toFarMessage(status),
const Gap(16),
KwButton.primary(
disabled: (status == EventShiftRoleStaffStatus.confirmed ||
status == EventShiftRoleStaffStatus.ongoing) &&
isToFar != false,
label: status == EventShiftRoleStaffStatus.confirmed
? 'clock_in'.tr()
: 'clock_out'.tr(),
onPressed: () async {
if (status == EventShiftRoleStaffStatus.confirmed) {
_onClockIn(context, viewModel.eventId);
} else {
await _onClocOut(context, viewModel.eventId, viewModel);
}
},
),
if (isToFar && status == EventShiftRoleStaffStatus.confirmed) ...[
const Gap(16),
KwButton.outlinedPrimary(
leftIcon: Assets.images.icons.routing,
label: 'get_direction'.tr(),
onPressed: () {
MapUtils.openMapByLatLon(
viewModel.locationLat,
viewModel.locationLon,
);
},
)
]
],
);
}
Widget _toFarMessage(status) {
return Container(
margin: const EdgeInsets.only(top: 16),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppColors.graySecondaryFrame,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Container(
height: 28,
width: 28,
decoration: const BoxDecoration(
color: AppColors.grayWhite,
shape: BoxShape.circle,
),
child: Assets.images.icons.alertCircle.svg(
height: 12,
width: 12,
colorFilter:
const ColorFilter.mode(AppColors.blackBlack, BlendMode.srcIn),
),
),
const Gap(8),
Expanded(
child: Text(
status == EventShiftRoleStaffStatus.ongoing
? 'reach_location_to_clock_out'.tr()
: 'reach_location_to_clock_in'.tr(),
style: AppTextStyles.bodySmallMed,
),
),
],
),
);
}
Future<void> _onClocOut(
BuildContext context, String eventId, ShiftEntity shift) async {
if (!kDebugMode) {
var qrResult = await context.router.push(const QrScannerRoute());
if (!context.mounted) return;
if (qrResult == null || qrResult != eventId) {
KwDialog.show(
context: context,
icon: Assets.images.icons.alertTriangle,
state: KwDialogState.negative,
title: 'oops_something_wrong'.tr(),
message: 'qr_code_error'.tr(),
primaryButtonLabel: 'retry_scanning'.tr(),
secondaryButtonLabel: 'cancel'.tr(),
onPrimaryButtonPressed: (dialogContext) {
Navigator.pop(dialogContext);
_onClocOut(context, eventId, shift);
});
return;
}
}
ShiftCompleterService().startCompleteProcess(context, shift,
onComplete: () {
BlocProvider.of<ShiftDetailsBloc>(context)
.add(const ShiftCompleteEvent());
});
}
void _onClockIn(BuildContext context, String eventId) async {
if (kDebugMode) {
BlocProvider.of<ShiftDetailsBloc>(context).add(const ShiftClockInEvent());
return;
}
var result = await context.router.push(const QrScannerRoute());
if (!context.mounted) return;
if (result != null && result == eventId) {
KwDialog.show(
context: context,
icon: Assets.images.icons.like,
state: KwDialogState.positive,
title: 'youre_good_to_go'.tr(),
message: 'shift_timer_started'.tr(),
child: Text(
'lunch_break_reminder'.tr(),
textAlign: TextAlign.center,
style: AppTextStyles.bodyMediumMed,
),
primaryButtonLabel: 'Continue to Dashboard');
BlocProvider.of<ShiftDetailsBloc>(context).add(const ShiftClockInEvent());
} else {
KwDialog.show(
context: context,
icon: Assets.images.icons.alertTriangle,
state: KwDialogState.negative,
title: 'oops_something_wrong'.tr(),
message: 'qr_code_error'.tr(),
primaryButtonLabel: 'retry_scanning'.tr(),
secondaryButtonLabel: 'cancel'.tr(),
onPrimaryButtonPressed: (dialogContext) {
Navigator.pop(dialogContext);
_onClockIn(context, eventId);
});
}
}
}

View File

@@ -0,0 +1,121 @@
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/features/shifts/data/models/staff_shift.dart';
class ShiftTimerWidget extends StatefulWidget {
final EventShiftRoleStaffStatus shiftStatus;
final DateTime? clockIn;
final DateTime? clockOut;
const ShiftTimerWidget(
{super.key,
required this.shiftStatus,
required this.clockIn,
required this.clockOut});
@override
State<ShiftTimerWidget> createState() => _ShiftTimerWidgetState();
}
class _ShiftTimerWidgetState extends State<ShiftTimerWidget> {
Color _borderColor() =>
widget.shiftStatus == EventShiftRoleStaffStatus.ongoing
? AppColors.tintDarkGreen
: AppColors.grayStroke;
Color _labelColor() => widget.shiftStatus == EventShiftRoleStaffStatus.ongoing
? AppColors.statusSuccess
: AppColors.blackGray;
Color _counterColor() =>
widget.shiftStatus == EventShiftRoleStaffStatus.ongoing
? AppColors.statusSuccess
: AppColors.blackBlack;
Color _bgColor() => widget.shiftStatus == EventShiftRoleStaffStatus.ongoing
? AppColors.tintGreen
: AppColors.graySecondaryFrame;
Color _dividerColor() =>
widget.shiftStatus == EventShiftRoleStaffStatus.ongoing
? AppColors.tintDarkGreen
: AppColors.grayTintStroke;
Duration _getDuration() {
return widget.shiftStatus == EventShiftRoleStaffStatus.ongoing
? DateTime.now()
.toUtc()
.difference(widget.clockIn ?? DateTime.now().toUtc())
: Duration.zero;
}
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
var duration = _getDuration();
var hours = duration.inHours.remainder(24).abs().toString().padLeft(2, '0');
var minutes =
duration.inMinutes.remainder(60).abs().toString().padLeft(2, '0');
var seconds =
duration.inSeconds.remainder(60).abs().toString().padLeft(2, '0');
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
height: 80,
decoration: BoxDecoration(
color: _bgColor(),
border: Border.all(color: _borderColor()),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildTimeText('hours'.tr(), hours),
_divider(),
_buildTimeText('minutes'.tr(), minutes),
_divider(),
_buildTimeText('seconds'.tr(), seconds),
],
),
),
],
);
}
Widget _divider() {
return SizedBox(
width: 24,
child: Center(
child: Container(
height: 24,
width: 1,
color: _dividerColor(),
),
),
);
}
Widget _buildTimeText(String label, String time) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(time,
style: AppTextStyles.headingH3.copyWith(color: _counterColor())),
const Gap(6),
Text(
label,
style: AppTextStyles.bodySmallReg.copyWith(color: _labelColor()),
),
],
);
}
}