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:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
|
||||||
import '../../domain/usecases/apply_quick_set_usecase.dart';
|
import '../../domain/usecases/apply_quick_set_usecase.dart';
|
||||||
import '../../domain/usecases/get_weekly_availability_usecase.dart';
|
import '../../domain/usecases/get_weekly_availability_usecase.dart';
|
||||||
import '../../domain/usecases/update_day_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) {
|
void _onSelectDate(SelectDate event, Emitter<AvailabilityState> emit) {
|
||||||
if (state is AvailabilityLoaded) {
|
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 {
|
) async {
|
||||||
if (state is AvailabilityLoaded) {
|
if (state is AvailabilityLoaded) {
|
||||||
final currentState = state as AvailabilityLoaded;
|
final currentState = state as AvailabilityLoaded;
|
||||||
|
|
||||||
|
// Clear message
|
||||||
|
emit(currentState.copyWith(clearSuccessMessage: true));
|
||||||
|
|
||||||
final newWeekStart = currentState.currentWeekStart
|
final newWeekStart = currentState.currentWeekStart
|
||||||
.add(Duration(days: event.direction * 7));
|
.add(Duration(days: event.direction * 7));
|
||||||
|
|
||||||
@@ -77,12 +84,23 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState> {
|
|||||||
return d.date == event.day.date ? newDay : d;
|
return d.date == event.day.date ? newDay : d;
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
emit(currentState.copyWith(days: updatedDays));
|
// Optimistic update
|
||||||
|
emit(currentState.copyWith(
|
||||||
|
days: updatedDays,
|
||||||
|
clearSuccessMessage: true,
|
||||||
|
));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateDayAvailability(UpdateDayAvailabilityParams(newDay));
|
await updateDayAvailability(UpdateDayAvailabilityParams(newDay));
|
||||||
|
// Success feedback
|
||||||
|
if (state is AvailabilityLoaded) {
|
||||||
|
emit((state as AvailabilityLoaded).copyWith(successMessage: 'Availability updated'));
|
||||||
|
}
|
||||||
} catch (e) {
|
} 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;
|
return d.date == event.day.date ? newDay : d;
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
emit(currentState.copyWith(days: updatedDays));
|
// Optimistic update
|
||||||
|
emit(currentState.copyWith(
|
||||||
|
days: updatedDays,
|
||||||
|
clearSuccessMessage: true,
|
||||||
|
));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateDayAvailability(UpdateDayAvailabilityParams(newDay));
|
await updateDayAvailability(UpdateDayAvailabilityParams(newDay));
|
||||||
|
// Success feedback
|
||||||
|
if (state is AvailabilityLoaded) {
|
||||||
|
emit((state as AvailabilityLoaded).copyWith(successMessage: 'Availability updated'));
|
||||||
|
}
|
||||||
} catch (e) {
|
} 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) {
|
if (state is AvailabilityLoaded) {
|
||||||
final currentState = state as AvailabilityLoaded;
|
final currentState = state as AvailabilityLoaded;
|
||||||
|
|
||||||
|
emit(currentState.copyWith(
|
||||||
|
isActionInProgress: true,
|
||||||
|
clearSuccessMessage: true,
|
||||||
|
));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final newDays = await applyQuickSet(
|
final newDays = await applyQuickSet(
|
||||||
ApplyQuickSetParams(currentState.currentWeekStart, event.type));
|
ApplyQuickSetParams(currentState.currentWeekStart, event.type));
|
||||||
emit(currentState.copyWith(days: newDays));
|
|
||||||
|
emit(currentState.copyWith(
|
||||||
|
days: newDays,
|
||||||
|
isActionInProgress: false,
|
||||||
|
successMessage: 'Availability updated',
|
||||||
|
));
|
||||||
} catch (e) {
|
} 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 List<DayAvailability> days;
|
||||||
final DateTime currentWeekStart;
|
final DateTime currentWeekStart;
|
||||||
final DateTime selectedDate;
|
final DateTime selectedDate;
|
||||||
|
final bool isActionInProgress;
|
||||||
|
final String? successMessage;
|
||||||
|
|
||||||
const AvailabilityLoaded({
|
const AvailabilityLoaded({
|
||||||
required this.days,
|
required this.days,
|
||||||
required this.currentWeekStart,
|
required this.currentWeekStart,
|
||||||
required this.selectedDate,
|
required this.selectedDate,
|
||||||
|
this.isActionInProgress = false,
|
||||||
|
this.successMessage,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Helper to get the currently selected day's availability object
|
/// Helper to get the currently selected day's availability object
|
||||||
@@ -34,11 +38,16 @@ class AvailabilityLoaded extends AvailabilityState {
|
|||||||
List<DayAvailability>? days,
|
List<DayAvailability>? days,
|
||||||
DateTime? currentWeekStart,
|
DateTime? currentWeekStart,
|
||||||
DateTime? selectedDate,
|
DateTime? selectedDate,
|
||||||
|
bool? isActionInProgress,
|
||||||
|
String? successMessage, // Nullable override
|
||||||
|
bool clearSuccessMessage = false,
|
||||||
}) {
|
}) {
|
||||||
return AvailabilityLoaded(
|
return AvailabilityLoaded(
|
||||||
days: days ?? this.days,
|
days: days ?? this.days,
|
||||||
currentWeekStart: currentWeekStart ?? this.currentWeekStart,
|
currentWeekStart: currentWeekStart ?? this.currentWeekStart,
|
||||||
selectedDate: selectedDate ?? this.selectedDate,
|
selectedDate: selectedDate ?? this.selectedDate,
|
||||||
|
isActionInProgress: isActionInProgress ?? this.isActionInProgress,
|
||||||
|
successMessage: clearSuccessMessage ? null : (successMessage ?? this.successMessage),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +56,7 @@ class AvailabilityLoaded extends AvailabilityState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [days, currentWeekStart, selectedDate];
|
List<Object?> get props => [days, currentWeekStart, selectedDate, isActionInProgress, successMessage];
|
||||||
}
|
}
|
||||||
|
|
||||||
class AvailabilityError extends AvailabilityState {
|
class AvailabilityError extends AvailabilityState {
|
||||||
|
|||||||
@@ -47,92 +47,70 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
|||||||
backgroundColor: AppColors.krowBackground,
|
backgroundColor: AppColors.krowBackground,
|
||||||
appBar: UiAppBar(
|
appBar: UiAppBar(
|
||||||
title: 'My Availability',
|
title: 'My Availability',
|
||||||
|
centerTitle: false,
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
),
|
),
|
||||||
body: BlocBuilder<AvailabilityBloc, AvailabilityState>(
|
body: BlocListener<AvailabilityBloc, AvailabilityState>(
|
||||||
builder: (context, state) {
|
listener: (context, state) {
|
||||||
if (state is AvailabilityLoading) {
|
if (state is AvailabilityLoaded && state.successMessage != null) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||||
} else if (state is AvailabilityLoaded) {
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
return SingleChildScrollView(
|
SnackBar(
|
||||||
padding: const EdgeInsets.only(bottom: 100),
|
content: Text(state.successMessage!),
|
||||||
child: Column(
|
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 Stack(
|
||||||
children: [
|
children: [
|
||||||
//_buildHeader(),
|
SingleChildScrollView(
|
||||||
Padding(
|
padding: const EdgeInsets.only(bottom: 100),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
children: [
|
||||||
_buildQuickSet(context),
|
Padding(
|
||||||
const SizedBox(height: 24),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
_buildWeekNavigation(context, state),
|
child: Column(
|
||||||
const SizedBox(height: 24),
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
_buildSelectedDayAvailability(
|
children: [
|
||||||
context,
|
_buildQuickSet(context),
|
||||||
state.selectedDayAvailability,
|
const SizedBox(height: 24),
|
||||||
|
_buildWeekNavigation(context, state),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildSelectedDayAvailability(
|
||||||
|
context,
|
||||||
|
state.selectedDayAvailability,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildInfoCard(),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
|
||||||
_buildInfoCard(),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (state.isActionInProgress)
|
||||||
|
Container(
|
||||||
|
color: Colors.black.withOpacity(0.3),
|
||||||
|
child: const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
);
|
} else if (state is AvailabilityError) {
|
||||||
} else if (state is AvailabilityError) {
|
return Center(child: Text('Error: ${state.message}'));
|
||||||
return Center(child: Text('Error: ${state.message}'));
|
}
|
||||||
}
|
return const SizedBox.shrink();
|
||||||
return const SizedBox.shrink();
|
},
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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