feat: legacy mobile apps created
This commit is contained in:
@@ -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)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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()});
|
||||
}
|
||||
}
|
||||
@@ -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)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
) ??
|
||||
[],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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))),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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)],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user