feat: Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_app_bar.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_loading_overlay.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/shift_entity.dart';
|
||||
import 'package:krow/features/shifts/presentation/dialogs/geocoding_dialogs.dart';
|
||||
import 'package:krow/features/shifts/presentation/widgets/shift_buttons_widget.dart';
|
||||
import 'package:krow/features/shifts/presentation/widgets/shift_info_time_row/shift_info_clock_time_widget.dart';
|
||||
import 'package:krow/features/shifts/presentation/widgets/shift_info_time_row/shift_info_planing_duration_widget.dart';
|
||||
import 'package:krow/features/shifts/presentation/widgets/shift_info_time_row/shift_info_start_widget.dart';
|
||||
import 'package:krow/features/shifts/presentation/widgets/shift_info_time_row/shift_info_total_duration_widget.dart';
|
||||
import 'package:krow/features/shifts/presentation/widgets/shift_item_widget.dart';
|
||||
import 'package:krow/features/shifts/presentation/widgets/shift_key_responsibilities_widget.dart';
|
||||
import 'package:krow/features/shifts/presentation/widgets/shift_location_widget.dart';
|
||||
import 'package:krow/features/shifts/presentation/widgets/shift_manage_widget.dart';
|
||||
import 'package:krow/features/shifts/presentation/widgets/shift_payment_step_card_widget.dart';
|
||||
import 'package:krow/features/shifts/presentation/widgets/shift_rating_widget.dart';
|
||||
import 'package:krow/features/shifts/presentation/widgets/shift_timer_widgets/shift_timer_card_widget.dart';
|
||||
|
||||
@RoutePage()
|
||||
class ShiftDetailsScreen extends StatefulWidget implements AutoRouteWrapper {
|
||||
final ShiftEntity shift;
|
||||
|
||||
const ShiftDetailsScreen({super.key, required this.shift});
|
||||
|
||||
@override
|
||||
State<ShiftDetailsScreen> createState() => _ShiftDetailsScreenState();
|
||||
|
||||
@override
|
||||
Widget wrappedRoute(BuildContext context) {
|
||||
return BlocProvider<ShiftDetailsBloc>(
|
||||
create: (context) =>
|
||||
ShiftDetailsBloc()..add(ShiftDetailsInitialEvent(shift: shift)),
|
||||
child: this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ShiftDetailsScreenState extends State<ShiftDetailsScreen> with WidgetsBindingObserver{
|
||||
final OverlayPortalController _controller = OverlayPortalController();
|
||||
var expanded = false;
|
||||
|
||||
String _getTitle(EventShiftRoleStaffStatus status) {
|
||||
switch (status) {
|
||||
//do not use enum name. Its need for future localization;
|
||||
case EventShiftRoleStaffStatus.assigned:
|
||||
return 'assigned';
|
||||
case EventShiftRoleStaffStatus.confirmed:
|
||||
return 'confirmed';
|
||||
case EventShiftRoleStaffStatus.ongoing:
|
||||
return 'active';
|
||||
case EventShiftRoleStaffStatus.completed:
|
||||
return 'completed';
|
||||
case EventShiftRoleStaffStatus.declineByStaff:
|
||||
return 'declined';
|
||||
case EventShiftRoleStaffStatus.canceledByStaff:
|
||||
case EventShiftRoleStaffStatus.canceledByBusiness:
|
||||
case EventShiftRoleStaffStatus.canceledByAdmin:
|
||||
case EventShiftRoleStaffStatus.requestedReplace:
|
||||
return 'canceled';
|
||||
}
|
||||
}
|
||||
|
||||
bool _showButtons(EventShiftRoleStaffStatus status) =>
|
||||
status == EventShiftRoleStaffStatus.assigned || status == EventShiftRoleStaffStatus.confirmed;
|
||||
|
||||
bool showTimer(EventShiftRoleStaffStatus status) =>
|
||||
status == EventShiftRoleStaffStatus.confirmed ||
|
||||
status == EventShiftRoleStaffStatus.ongoing;
|
||||
|
||||
void _listenHandler(BuildContext context, ShiftDetailsState state) {
|
||||
|
||||
if (state.needPop) {
|
||||
context.router.maybePop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.isLoading) {
|
||||
_controller.show();
|
||||
} else {
|
||||
_controller.hide();
|
||||
}
|
||||
|
||||
if(state.errorMessage!=null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.errorMessage!),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (state.proximityState == GeofencingProximityState.none) return;
|
||||
GeocodingDialogs.showGeocodingErrorDialog(state, context);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
expanded = widget.shift.status != EventShiftRoleStaffStatus.completed;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
super.didChangeAppLifecycleState(state);
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
BlocProvider.of<ShiftDetailsBloc>(context).add(const ShiftCheckGeocodingEvent());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocConsumer<ShiftDetailsBloc, ShiftDetailsState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.shiftViewModel != current.shiftViewModel,
|
||||
listenWhen: (previous, current) =>
|
||||
previous.isLoading != current.isLoading ||
|
||||
previous.proximityState != current.proximityState || current.errorMessage!=null,
|
||||
listener: _listenHandler,
|
||||
builder: (context, state) {
|
||||
var viewModel = state.shiftViewModel;
|
||||
var status = viewModel.status;
|
||||
|
||||
return KwLoadingOverlay(
|
||||
controller: _controller,
|
||||
child: Scaffold(
|
||||
appBar: KwAppBar(
|
||||
titleText: '${_getTitle(status).tr()} ${'Shift'.tr()}',
|
||||
showNotification: true,
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
ListView(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
children: [
|
||||
ShiftItemWidget(
|
||||
bottomPadding: 0,
|
||||
viewModel,
|
||||
isDetailsMode: true,
|
||||
),
|
||||
if (showTimer(viewModel.status))
|
||||
const ShiftTimerCardWidget(),
|
||||
_buildShiftTimeInfo(viewModel),
|
||||
if (status == EventShiftRoleStaffStatus.completed)
|
||||
ShiftPaymentStepCardWidget(viewModel: viewModel),
|
||||
if (status == EventShiftRoleStaffStatus.completed &&
|
||||
viewModel.rating != null)
|
||||
ShiftRatingWidget(viewModel: viewModel),
|
||||
if (status != EventShiftRoleStaffStatus.ongoing)
|
||||
ShiftLocationWidget(viewModel: viewModel),
|
||||
ShiftManageWidget(managers: viewModel.managers),
|
||||
if (viewModel.additionalInfo != null)
|
||||
ShiftKeyResponsibilitiesWidget(
|
||||
text: viewModel.additionalInfo!,
|
||||
expandable:
|
||||
status == EventShiftRoleStaffStatus.completed,
|
||||
isExpanded: expanded,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
expanded = !expanded;
|
||||
});
|
||||
},
|
||||
),
|
||||
SizedBox(height: MediaQuery.sizeOf(context).height / 3),
|
||||
],
|
||||
),
|
||||
if (_showButtons(viewModel.status))
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: ShiftButtonsWidget(
|
||||
viewModel.status,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildShiftTimeInfo(ShiftEntity shiftViewModel) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(left: 16, right: 16, top: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
shiftViewModel.status == EventShiftRoleStaffStatus.completed
|
||||
? ShiftInfoClockTimeWidget(viewModel: shiftViewModel)
|
||||
: ShiftInfoStartWidget(viewModel: shiftViewModel),
|
||||
const Gap(8),
|
||||
shiftViewModel.status == EventShiftRoleStaffStatus.completed
|
||||
? ShiftInfoTotalDurationWidget(viewModel: shiftViewModel)
|
||||
: ShiftInfoPlaningDurationWidget(viewModel: shiftViewModel)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/application/routing/routes.gr.dart';
|
||||
import 'package:krow/core/presentation/gen/assets.gen.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
import 'package:krow/core/presentation/widgets/scroll_layout_helper.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_app_bar.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_tabs.dart';
|
||||
import 'package:krow/features/shifts/domain/blocs/shifts_list_bloc/shifts_bloc.dart';
|
||||
import 'package:krow/features/shifts/domain/blocs/shifts_list_bloc/shifts_event.dart';
|
||||
import 'package:krow/features/shifts/domain/blocs/shifts_list_bloc/shifts_state.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_item_widget.dart';
|
||||
|
||||
@RoutePage()
|
||||
class ShiftsListMainScreen extends StatefulWidget {
|
||||
const ShiftsListMainScreen({super.key});
|
||||
|
||||
@override
|
||||
State<ShiftsListMainScreen> createState() => _ShiftsListMainScreenState();
|
||||
}
|
||||
|
||||
class _ShiftsListMainScreenState extends State<ShiftsListMainScreen> {
|
||||
late AppLifecycleListener _appLifecycleListener;
|
||||
var dialogOpened = false;
|
||||
final List<String> tabs = [
|
||||
'assigned',
|
||||
'confirmed',
|
||||
'active',
|
||||
'completed',
|
||||
'canceled'
|
||||
];
|
||||
|
||||
late ScrollController _scrollController;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
BlocProvider.of<ShiftsBloc>(context).add(
|
||||
LoadTabShiftEvent(
|
||||
status: BlocProvider.of<ShiftsBloc>(context).state.tabIndex),
|
||||
);
|
||||
BlocProvider.of<ShiftsBloc>(context).add(
|
||||
const ReloadMissingBreakShift(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_appLifecycleListener = AppLifecycleListener(onStateChange: (state) {
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
BlocProvider.of<ShiftsBloc>(context).add(
|
||||
const ReloadMissingBreakShift(),
|
||||
);
|
||||
}
|
||||
});
|
||||
_scrollController = ScrollController();
|
||||
_scrollController.addListener(_onScroll);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_appLifecycleListener.dispose();
|
||||
_scrollController.removeListener(_onScroll);
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
if (_scrollController.position.atEdge) {
|
||||
if (_scrollController.position.pixels != 0) {
|
||||
BlocProvider.of<ShiftsBloc>(context).add(
|
||||
LoadMoreShiftEvent(
|
||||
status: BlocProvider.of<ShiftsBloc>(context).state.tabIndex),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocConsumer<ShiftsBloc, ShiftsState>(
|
||||
listenWhen: (oldState, state) {
|
||||
return state.missedShifts.isNotEmpty &&
|
||||
(oldState.missedShifts.isEmpty ||
|
||||
oldState.missedShifts.first.id != state.missedShifts.first.id);
|
||||
},
|
||||
listener: (context, state) async {
|
||||
if (state.missedShifts.isNotEmpty) {
|
||||
if(dialogOpened) return;
|
||||
dialogOpened = true;
|
||||
await ShiftCompleterService().startCompleteProcess(
|
||||
context,
|
||||
canSkip: false,
|
||||
state.missedShifts.first,
|
||||
onComplete: () {},
|
||||
);
|
||||
dialogOpened = false;
|
||||
|
||||
BlocProvider.of<ShiftsBloc>(context).add(
|
||||
const ReloadMissingBreakShift(),
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
List<ShiftEntity> items = state.tabs[state.tabIndex]!.items;
|
||||
return Scaffold(
|
||||
appBar: KwAppBar(
|
||||
titleText: 'your_shifts'.tr(),
|
||||
showNotification: true,
|
||||
centerTitle: false,
|
||||
),
|
||||
body: ScrollLayoutHelper(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
onRefresh: () async {
|
||||
BlocProvider.of<ShiftsBloc>(context)
|
||||
.add(const ReloadMissingBreakShift());
|
||||
BlocProvider.of<ShiftsBloc>(context)
|
||||
.add(LoadTabShiftEvent(status: state.tabIndex));
|
||||
},
|
||||
controller: _scrollController,
|
||||
upperWidget: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
KwTabBar(
|
||||
key: const Key('shifts_tab_bar'),
|
||||
tabs: tabs.map((e) => e.tr()).toList(),
|
||||
onTap: (index) {
|
||||
BlocProvider.of<ShiftsBloc>(context)
|
||||
.add(ShiftsTabChangedEvent(tabIndex: index));
|
||||
}),
|
||||
const Gap(16),
|
||||
if (state.tabs[state.tabIndex]!.isLoading &&
|
||||
state.tabs[state.tabIndex]!.items.isEmpty)
|
||||
..._buildListLoading(),
|
||||
if (!state.tabs[state.tabIndex]!.isLoading && items.isEmpty)
|
||||
..._emptyListWidget(),
|
||||
RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
BlocProvider.of<ShiftsBloc>(context)
|
||||
.add(LoadTabShiftEvent(status: state.tabIndex));
|
||||
},
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: items.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ShiftItemWidget(
|
||||
items[index],
|
||||
bottomPadding: 12,
|
||||
onPressed: () {
|
||||
context.pushRoute(
|
||||
ShiftDetailsRoute(shift: items[index]),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
lowerWidget: const SizedBox.shrink(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildListLoading() {
|
||||
return [
|
||||
const Gap(116),
|
||||
const Center(child: CircularProgressIndicator()),
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _emptyListWidget() {
|
||||
return [
|
||||
const Gap(100),
|
||||
Container(
|
||||
height: 64,
|
||||
width: 64,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grayWhite,
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
),
|
||||
child: Center(child: Assets.images.icons.xCircle.svg()),
|
||||
),
|
||||
const Gap(24),
|
||||
Text(
|
||||
'you_currently_have_no_shifts'.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
style: AppTextStyles.headingH2,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user