feat: implement emergency contact management with BLoC, including event and state handling

This commit is contained in:
Achintha Isuru
2026-01-27 13:07:31 -05:00
parent 70f350d216
commit 2f5c736c20
5 changed files with 128 additions and 104 deletions

View File

@@ -1,80 +1,14 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../../domain/arguments/get_emergency_contacts_arguments.dart'; import '../../domain/arguments/get_emergency_contacts_arguments.dart';
import '../../domain/arguments/save_emergency_contacts_arguments.dart'; import '../../domain/arguments/save_emergency_contacts_arguments.dart';
import '../../domain/usecases/get_emergency_contacts_usecase.dart'; import '../../domain/usecases/get_emergency_contacts_usecase.dart';
import '../../domain/usecases/save_emergency_contacts_usecase.dart'; import '../../domain/usecases/save_emergency_contacts_usecase.dart';
import 'emergency_contact_event.dart';
import 'emergency_contact_state.dart';
// Events export 'emergency_contact_event.dart';
abstract class EmergencyContactEvent extends Equatable { export 'emergency_contact_state.dart';
const EmergencyContactEvent();
@override
List<Object?> get props => [];
}
class EmergencyContactsLoaded extends EmergencyContactEvent {}
class EmergencyContactAdded extends EmergencyContactEvent {}
class EmergencyContactRemoved extends EmergencyContactEvent {
final int index;
const EmergencyContactRemoved(this.index);
@override
List<Object?> get props => [index];
}
class EmergencyContactUpdated extends EmergencyContactEvent {
final int index;
final EmergencyContact contact;
const EmergencyContactUpdated(this.index, this.contact);
@override
List<Object?> get props => [index, contact];
}
class EmergencyContactsSaved extends EmergencyContactEvent {}
// State
enum EmergencyContactStatus { initial, loading, success, saving, failure }
class EmergencyContactState extends Equatable {
final EmergencyContactStatus status;
final List<EmergencyContact> contacts;
final String? errorMessage;
const EmergencyContactState({
this.status = EmergencyContactStatus.initial,
this.contacts = const [],
this.errorMessage,
});
EmergencyContactState copyWith({
EmergencyContactStatus? status,
List<EmergencyContact>? contacts,
String? errorMessage,
}) {
return EmergencyContactState(
status: status ?? this.status,
contacts: contacts ?? this.contacts,
errorMessage: errorMessage ?? this.errorMessage,
);
}
bool get isValid {
if (contacts.isEmpty) return false;
// Check if at least one contact is valid (or all?)
// Usually all added contacts should be valid.
return contacts.every((c) => c.name.isNotEmpty && c.phone.isNotEmpty);
}
@override
List<Object?> get props => [status, contacts, errorMessage];
}
// BLoC // BLoC
class EmergencyContactBloc class EmergencyContactBloc
@@ -106,7 +40,7 @@ class EmergencyContactBloc
const GetEmergencyContactsArguments(), const GetEmergencyContactsArguments(),
); );
emit(state.copyWith( emit(state.copyWith(
status: EmergencyContactStatus.success, status: EmergencyContactStatus.loaded,
contacts: contacts.isNotEmpty contacts: contacts.isNotEmpty
? contacts ? contacts
: [EmergencyContact.empty()], : [EmergencyContact.empty()],
@@ -157,7 +91,7 @@ class EmergencyContactBloc
contacts: state.contacts, contacts: state.contacts,
), ),
); );
emit(state.copyWith(status: EmergencyContactStatus.success)); emit(state.copyWith(status: EmergencyContactStatus.saved));
} catch (e) { } catch (e) {
emit(state.copyWith( emit(state.copyWith(
status: EmergencyContactStatus.failure, status: EmergencyContactStatus.failure,

View File

@@ -0,0 +1,34 @@
import 'package:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart';
abstract class EmergencyContactEvent extends Equatable {
const EmergencyContactEvent();
@override
List<Object?> get props => [];
}
class EmergencyContactsLoaded extends EmergencyContactEvent {}
class EmergencyContactAdded extends EmergencyContactEvent {}
class EmergencyContactRemoved extends EmergencyContactEvent {
final int index;
const EmergencyContactRemoved(this.index);
@override
List<Object?> get props => [index];
}
class EmergencyContactUpdated extends EmergencyContactEvent {
final int index;
final EmergencyContact contact;
const EmergencyContactUpdated(this.index, this.contact);
@override
List<Object?> get props => [index, contact];
}
class EmergencyContactsSaved extends EmergencyContactEvent {}

View File

@@ -0,0 +1,38 @@
import 'package:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart';
enum EmergencyContactStatus { initial, loading, loaded, saving, saved, failure }
class EmergencyContactState extends Equatable {
final EmergencyContactStatus status;
final List<EmergencyContact> contacts;
final String? errorMessage;
const EmergencyContactState({
this.status = EmergencyContactStatus.initial,
this.contacts = const [],
this.errorMessage,
});
EmergencyContactState copyWith({
EmergencyContactStatus? status,
List<EmergencyContact>? contacts,
String? errorMessage,
}) {
return EmergencyContactState(
status: status ?? this.status,
contacts: contacts ?? this.contacts,
errorMessage: errorMessage ?? this.errorMessage,
);
}
bool get isValid {
if (contacts.isEmpty) return false;
// Check if at least one contact is valid (or all?)
// Usually all added contacts should be valid.
return contacts.every((c) => c.name.isNotEmpty && c.phone.isNotEmpty);
}
@override
List<Object?> get props => [status, contacts, errorMessage];
}

View File

@@ -71,7 +71,7 @@ class EmergencyContactScreen extends StatelessWidget {
), ),
), ),
), ),
EmergencyContactSaveButton(state: state), const EmergencyContactSaveButton(),
], ],
); );
}, },

View File

@@ -4,41 +4,59 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import '../blocs/emergency_contact_bloc.dart'; import '../blocs/emergency_contact_bloc.dart';
class EmergencyContactSaveButton extends StatelessWidget { class EmergencyContactSaveButton extends StatelessWidget {
final EmergencyContactState state; const EmergencyContactSaveButton({super.key});
const EmergencyContactSaveButton({super.key, required this.state}); void _onSave(BuildContext context) {
context.read<EmergencyContactBloc>().add(EmergencyContactsSaved());
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return BlocConsumer<EmergencyContactBloc, EmergencyContactState>(
padding: EdgeInsets.all(UiConstants.space4), listenWhen: (previous, current) => previous.status != current.status,
decoration: BoxDecoration( listener: (context, state) {
color: UiColors.bgPopup, if (state.status == EmergencyContactStatus.saved) {
border: Border(top: BorderSide(color: UiColors.border)), ScaffoldMessenger.of(context).showSnackBar(
), SnackBar(
child: SafeArea( content: Text(
child: UiButton.primary( 'Emergency contacts saved successfully',
fullWidth: true, style: UiTypography.body2r.textPrimary,
onPressed: state.isValid ),
? () => context backgroundColor: UiColors.iconSuccess,
.read<EmergencyContactBloc>() ),
.add(EmergencyContactsSaved()) );
: null, }
child: state.status == EmergencyContactStatus.saving },
? SizedBox( builder: (context, state) {
height: 20.0, final isLoading = state.status == EmergencyContactStatus.saving;
width: 20.0, return Container(
child: CircularProgressIndicator( padding: EdgeInsets.all(UiConstants.space4),
strokeWidth: 2, decoration: BoxDecoration(
valueColor: color: UiColors.bgPopup,
AlwaysStoppedAnimation<Color>(UiColors.primaryForeground), border: Border(top: BorderSide(color: UiColors.border)),
), ),
) child: SafeArea(
: Text( child: UiButton.primary(
'Save & Continue', fullWidth: true,
), onPressed: state.isValid && !isLoading
), ? () => _onSave(context)
), : null,
child: isLoading
? SizedBox(
height: 20.0,
width: 20.0,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
UiColors.primaryForeground,
),
),
)
: const Text('Save & Continue'),
),
),
);
},
); );
} }
} }