feat: enhance availability management with success message handling and loading state
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/usecases/apply_quick_set_usecase.dart';
|
||||
import '../../domain/usecases/get_weekly_availability_usecase.dart';
|
||||
import '../../domain/usecases/update_day_availability_usecase.dart';
|
||||
@@ -45,7 +44,11 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState> {
|
||||
|
||||
void _onSelectDate(SelectDate event, Emitter<AvailabilityState> emit) {
|
||||
if (state is AvailabilityLoaded) {
|
||||
emit((state as AvailabilityLoaded).copyWith(selectedDate: event.date));
|
||||
// Clear success message on navigation
|
||||
emit((state as AvailabilityLoaded).copyWith(
|
||||
selectedDate: event.date,
|
||||
clearSuccessMessage: true,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +58,10 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState> {
|
||||
) async {
|
||||
if (state is AvailabilityLoaded) {
|
||||
final currentState = state as AvailabilityLoaded;
|
||||
|
||||
// Clear message
|
||||
emit(currentState.copyWith(clearSuccessMessage: true));
|
||||
|
||||
final newWeekStart = currentState.currentWeekStart
|
||||
.add(Duration(days: event.direction * 7));
|
||||
|
||||
@@ -77,12 +84,23 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState> {
|
||||
return d.date == event.day.date ? newDay : d;
|
||||
}).toList();
|
||||
|
||||
emit(currentState.copyWith(days: updatedDays));
|
||||
// Optimistic update
|
||||
emit(currentState.copyWith(
|
||||
days: updatedDays,
|
||||
clearSuccessMessage: true,
|
||||
));
|
||||
|
||||
try {
|
||||
await updateDayAvailability(UpdateDayAvailabilityParams(newDay));
|
||||
// Success feedback
|
||||
if (state is AvailabilityLoaded) {
|
||||
emit((state as AvailabilityLoaded).copyWith(successMessage: 'Availability updated'));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(currentState.copyWith(days: currentState.days));
|
||||
// Revert
|
||||
if (state is AvailabilityLoaded) {
|
||||
emit((state as AvailabilityLoaded).copyWith(days: currentState.days));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,12 +125,23 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState> {
|
||||
return d.date == event.day.date ? newDay : d;
|
||||
}).toList();
|
||||
|
||||
emit(currentState.copyWith(days: updatedDays));
|
||||
// Optimistic update
|
||||
emit(currentState.copyWith(
|
||||
days: updatedDays,
|
||||
clearSuccessMessage: true,
|
||||
));
|
||||
|
||||
try {
|
||||
await updateDayAvailability(UpdateDayAvailabilityParams(newDay));
|
||||
// Success feedback
|
||||
if (state is AvailabilityLoaded) {
|
||||
emit((state as AvailabilityLoaded).copyWith(successMessage: 'Availability updated'));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(currentState.copyWith(days: currentState.days));
|
||||
// Revert
|
||||
if (state is AvailabilityLoaded) {
|
||||
emit((state as AvailabilityLoaded).copyWith(days: currentState.days));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,12 +153,26 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState> {
|
||||
if (state is AvailabilityLoaded) {
|
||||
final currentState = state as AvailabilityLoaded;
|
||||
|
||||
emit(currentState.copyWith(
|
||||
isActionInProgress: true,
|
||||
clearSuccessMessage: true,
|
||||
));
|
||||
|
||||
try {
|
||||
final newDays = await applyQuickSet(
|
||||
ApplyQuickSetParams(currentState.currentWeekStart, event.type));
|
||||
emit(currentState.copyWith(days: newDays));
|
||||
|
||||
emit(currentState.copyWith(
|
||||
days: newDays,
|
||||
isActionInProgress: false,
|
||||
successMessage: 'Availability updated',
|
||||
));
|
||||
} catch (e) {
|
||||
// Handle error
|
||||
emit(currentState.copyWith(
|
||||
isActionInProgress: false,
|
||||
// Could set error message here if we had a field for it, or emit AvailabilityError
|
||||
// But emitting AvailabilityError would replace the whole screen.
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,15 @@ class AvailabilityLoaded extends AvailabilityState {
|
||||
final List<DayAvailability> days;
|
||||
final DateTime currentWeekStart;
|
||||
final DateTime selectedDate;
|
||||
final bool isActionInProgress;
|
||||
final String? successMessage;
|
||||
|
||||
const AvailabilityLoaded({
|
||||
required this.days,
|
||||
required this.currentWeekStart,
|
||||
required this.selectedDate,
|
||||
this.isActionInProgress = false,
|
||||
this.successMessage,
|
||||
});
|
||||
|
||||
/// Helper to get the currently selected day's availability object
|
||||
@@ -34,11 +38,16 @@ class AvailabilityLoaded extends AvailabilityState {
|
||||
List<DayAvailability>? days,
|
||||
DateTime? currentWeekStart,
|
||||
DateTime? selectedDate,
|
||||
bool? isActionInProgress,
|
||||
String? successMessage, // Nullable override
|
||||
bool clearSuccessMessage = false,
|
||||
}) {
|
||||
return AvailabilityLoaded(
|
||||
days: days ?? this.days,
|
||||
currentWeekStart: currentWeekStart ?? this.currentWeekStart,
|
||||
selectedDate: selectedDate ?? this.selectedDate,
|
||||
isActionInProgress: isActionInProgress ?? this.isActionInProgress,
|
||||
successMessage: clearSuccessMessage ? null : (successMessage ?? this.successMessage),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -47,7 +56,7 @@ class AvailabilityLoaded extends AvailabilityState {
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [days, currentWeekStart, selectedDate];
|
||||
List<Object?> get props => [days, currentWeekStart, selectedDate, isActionInProgress, successMessage];
|
||||
}
|
||||
|
||||
class AvailabilityError extends AvailabilityState {
|
||||
|
||||
@@ -47,18 +47,33 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
||||
backgroundColor: AppColors.krowBackground,
|
||||
appBar: UiAppBar(
|
||||
title: 'My Availability',
|
||||
centerTitle: false,
|
||||
showBackButton: true,
|
||||
),
|
||||
body: BlocBuilder<AvailabilityBloc, AvailabilityState>(
|
||||
body: BlocListener<AvailabilityBloc, AvailabilityState>(
|
||||
listener: (context, state) {
|
||||
if (state is AvailabilityLoaded && state.successMessage != null) {
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.successMessage!),
|
||||
backgroundColor: Colors.green,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: BlocBuilder<AvailabilityBloc, AvailabilityState>(
|
||||
builder: (context, state) {
|
||||
if (state is AvailabilityLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is AvailabilityLoaded) {
|
||||
return SingleChildScrollView(
|
||||
return Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.only(bottom: 100),
|
||||
child: Column(
|
||||
children: [
|
||||
//_buildHeader(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
@@ -79,6 +94,15 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (state.isActionInProgress)
|
||||
Container(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else if (state is AvailabilityError) {
|
||||
return Center(child: Text('Error: ${state.message}'));
|
||||
@@ -87,52 +111,6 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(20, 60, 20, 20),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
LucideIcons.arrowLeft,
|
||||
color: AppColors.krowCharcoal,
|
||||
),
|
||||
onPressed: () => Modular.to.pop(),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'My Availability',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.krowCharcoal,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Set when you can work',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.krowMuted,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user