client hub bloc updated
This commit is contained in:
@@ -1,10 +1,6 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:design_system/src/ui_typography.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../ui_icons.dart';
|
||||
import 'ui_icon_button.dart';
|
||||
|
||||
/// A custom AppBar for the Krow UI design system.
|
||||
///
|
||||
/// This widget provides a consistent look and feel for top app bars across the application.
|
||||
|
||||
@@ -2,10 +2,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/arguments/assign_nfc_tag_arguments.dart';
|
||||
import '../../domain/arguments/delete_hub_arguments.dart';
|
||||
import '../../domain/usecases/assign_nfc_tag_usecase.dart';
|
||||
import '../../domain/usecases/delete_hub_usecase.dart';
|
||||
import '../../domain/usecases/get_hubs_usecase.dart';
|
||||
import 'client_hubs_event.dart';
|
||||
import 'client_hubs_state.dart';
|
||||
@@ -13,39 +9,18 @@ import 'client_hubs_state.dart';
|
||||
/// BLoC responsible for managing the state of the Client Hubs feature.
|
||||
///
|
||||
/// It orchestrates the flow between the UI and the domain layer by invoking
|
||||
/// specific use cases for fetching, deleting, and assigning tags to hubs.
|
||||
/// specific use cases for fetching hubs.
|
||||
class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
|
||||
with BlocErrorHandler<ClientHubsState>
|
||||
implements Disposable {
|
||||
ClientHubsBloc({
|
||||
required GetHubsUseCase getHubsUseCase,
|
||||
required DeleteHubUseCase deleteHubUseCase,
|
||||
required AssignNfcTagUseCase assignNfcTagUseCase,
|
||||
}) : _getHubsUseCase = getHubsUseCase,
|
||||
_deleteHubUseCase = deleteHubUseCase,
|
||||
_assignNfcTagUseCase = assignNfcTagUseCase,
|
||||
super(const ClientHubsState()) {
|
||||
ClientHubsBloc({required GetHubsUseCase getHubsUseCase})
|
||||
: _getHubsUseCase = getHubsUseCase,
|
||||
super(const ClientHubsState()) {
|
||||
on<ClientHubsFetched>(_onFetched);
|
||||
on<ClientHubsDeleteRequested>(_onDeleteRequested);
|
||||
on<ClientHubsNfcTagAssignRequested>(_onNfcTagAssignRequested);
|
||||
on<ClientHubsMessageCleared>(_onMessageCleared);
|
||||
|
||||
on<ClientHubsIdentifyDialogToggled>(_onIdentifyDialogToggled);
|
||||
}
|
||||
|
||||
final GetHubsUseCase _getHubsUseCase;
|
||||
final DeleteHubUseCase _deleteHubUseCase;
|
||||
final AssignNfcTagUseCase _assignNfcTagUseCase;
|
||||
|
||||
void _onIdentifyDialogToggled(
|
||||
ClientHubsIdentifyDialogToggled event,
|
||||
Emitter<ClientHubsState> emit,
|
||||
) {
|
||||
if (event.hub == null) {
|
||||
emit(state.copyWith(clearHubToIdentify: true));
|
||||
} else {
|
||||
emit(state.copyWith(hubToIdentify: event.hub));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFetched(
|
||||
ClientHubsFetched event,
|
||||
@@ -66,61 +41,6 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onDeleteRequested(
|
||||
ClientHubsDeleteRequested event,
|
||||
Emitter<ClientHubsState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: ClientHubsStatus.actionInProgress));
|
||||
|
||||
await handleError(
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
await _deleteHubUseCase.call(DeleteHubArguments(hubId: event.hubId));
|
||||
final List<Hub> hubs = await _getHubsUseCase.call();
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ClientHubsStatus.actionSuccess,
|
||||
hubs: hubs,
|
||||
successMessage: 'Hub deleted successfully',
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
status: ClientHubsStatus.actionFailure,
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onNfcTagAssignRequested(
|
||||
ClientHubsNfcTagAssignRequested event,
|
||||
Emitter<ClientHubsState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: ClientHubsStatus.actionInProgress));
|
||||
|
||||
await handleError(
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
await _assignNfcTagUseCase.call(
|
||||
AssignNfcTagArguments(hubId: event.hubId, nfcTagId: event.nfcTagId),
|
||||
);
|
||||
final List<Hub> hubs = await _getHubsUseCase.call();
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ClientHubsStatus.actionSuccess,
|
||||
hubs: hubs,
|
||||
successMessage: 'NFC tag assigned successfully',
|
||||
clearHubToIdentify: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
status: ClientHubsStatus.actionFailure,
|
||||
errorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onMessageCleared(
|
||||
ClientHubsMessageCleared event,
|
||||
Emitter<ClientHubsState> emit,
|
||||
@@ -130,8 +50,8 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
|
||||
clearErrorMessage: true,
|
||||
clearSuccessMessage: true,
|
||||
status:
|
||||
state.status == ClientHubsStatus.actionSuccess ||
|
||||
state.status == ClientHubsStatus.actionFailure
|
||||
state.status == ClientHubsStatus.success ||
|
||||
state.status == ClientHubsStatus.failure
|
||||
? ClientHubsStatus.success
|
||||
: state.status,
|
||||
),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Base class for all client hubs events.
|
||||
abstract class ClientHubsEvent extends Equatable {
|
||||
@@ -14,38 +13,7 @@ class ClientHubsFetched extends ClientHubsEvent {
|
||||
const ClientHubsFetched();
|
||||
}
|
||||
|
||||
/// Event triggered to delete a hub.
|
||||
class ClientHubsDeleteRequested extends ClientHubsEvent {
|
||||
const ClientHubsDeleteRequested(this.hubId);
|
||||
final String hubId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[hubId];
|
||||
}
|
||||
|
||||
/// Event triggered to assign an NFC tag to a hub.
|
||||
class ClientHubsNfcTagAssignRequested extends ClientHubsEvent {
|
||||
const ClientHubsNfcTagAssignRequested({
|
||||
required this.hubId,
|
||||
required this.nfcTagId,
|
||||
});
|
||||
final String hubId;
|
||||
final String nfcTagId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[hubId, nfcTagId];
|
||||
}
|
||||
|
||||
/// Event triggered to clear any error or success messages.
|
||||
class ClientHubsMessageCleared extends ClientHubsEvent {
|
||||
const ClientHubsMessageCleared();
|
||||
}
|
||||
|
||||
/// Event triggered to toggle the visibility of the "Identify NFC" dialog.
|
||||
class ClientHubsIdentifyDialogToggled extends ClientHubsEvent {
|
||||
const ClientHubsIdentifyDialogToggled({this.hub});
|
||||
final Hub? hub;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[hub];
|
||||
}
|
||||
|
||||
@@ -2,15 +2,7 @@ import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Enum representing the status of the client hubs state.
|
||||
enum ClientHubsStatus {
|
||||
initial,
|
||||
loading,
|
||||
success,
|
||||
failure,
|
||||
actionInProgress,
|
||||
actionSuccess,
|
||||
actionFailure,
|
||||
}
|
||||
enum ClientHubsStatus { initial, loading, success, failure }
|
||||
|
||||
/// State class for the ClientHubs BLoC.
|
||||
class ClientHubsState extends Equatable {
|
||||
@@ -19,7 +11,6 @@ class ClientHubsState extends Equatable {
|
||||
this.hubs = const <Hub>[],
|
||||
this.errorMessage,
|
||||
this.successMessage,
|
||||
this.hubToIdentify,
|
||||
});
|
||||
|
||||
final ClientHubsStatus status;
|
||||
@@ -27,17 +18,11 @@ class ClientHubsState extends Equatable {
|
||||
final String? errorMessage;
|
||||
final String? successMessage;
|
||||
|
||||
/// The hub currently being identified/assigned an NFC tag.
|
||||
/// If null, the identification dialog is closed.
|
||||
final Hub? hubToIdentify;
|
||||
|
||||
ClientHubsState copyWith({
|
||||
ClientHubsStatus? status,
|
||||
List<Hub>? hubs,
|
||||
String? errorMessage,
|
||||
String? successMessage,
|
||||
Hub? hubToIdentify,
|
||||
bool clearHubToIdentify = false,
|
||||
bool clearErrorMessage = false,
|
||||
bool clearSuccessMessage = false,
|
||||
}) {
|
||||
@@ -50,9 +35,6 @@ class ClientHubsState extends Equatable {
|
||||
successMessage: clearSuccessMessage
|
||||
? null
|
||||
: (successMessage ?? this.successMessage),
|
||||
hubToIdentify: clearHubToIdentify
|
||||
? null
|
||||
: (hubToIdentify ?? this.hubToIdentify),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,6 +44,5 @@ class ClientHubsState extends Equatable {
|
||||
hubs,
|
||||
errorMessage,
|
||||
successMessage,
|
||||
hubToIdentify,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class EditHubBloc extends Bloc<EditHubEvent, EditHubState>
|
||||
emit(state.copyWith(status: EditHubStatus.loading));
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
await _createHubUseCase.call(
|
||||
CreateHubArguments(
|
||||
@@ -64,7 +64,7 @@ class EditHubBloc extends Bloc<EditHubEvent, EditHubState>
|
||||
emit(state.copyWith(status: EditHubStatus.loading));
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
await _updateHubUseCase.call(
|
||||
UpdateHubArguments(
|
||||
|
||||
@@ -30,7 +30,7 @@ class HubDetailsBloc extends Bloc<HubDetailsEvent, HubDetailsState>
|
||||
emit(state.copyWith(status: HubDetailsStatus.loading));
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
await _deleteHubUseCase.call(DeleteHubArguments(hubId: event.id));
|
||||
emit(
|
||||
@@ -54,7 +54,7 @@ class HubDetailsBloc extends Bloc<HubDetailsEvent, HubDetailsState>
|
||||
emit(state.copyWith(status: HubDetailsStatus.loading));
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
await _assignNfcTagUseCase.call(
|
||||
AssignNfcTagArguments(hubId: event.hubId, nfcTagId: event.nfcTagId),
|
||||
|
||||
@@ -12,7 +12,6 @@ import '../blocs/client_hubs_state.dart';
|
||||
import '../widgets/hub_card.dart';
|
||||
import '../widgets/hub_empty_state.dart';
|
||||
import '../widgets/hub_info_card.dart';
|
||||
import '../widgets/identify_nfc_dialog.dart';
|
||||
|
||||
/// The main page for the client hubs feature.
|
||||
///
|
||||
@@ -72,84 +71,54 @@ class ClientHubsPage extends StatelessWidget {
|
||||
),
|
||||
child: const Icon(UiIcons.add),
|
||||
),
|
||||
body: Stack(
|
||||
children: <Widget>[
|
||||
CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
_buildAppBar(context),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space5,
|
||||
vertical: UiConstants.space5,
|
||||
).copyWith(bottom: 100),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildListDelegate(<Widget>[
|
||||
if (state.status == ClientHubsStatus.loading)
|
||||
const Center(child: CircularProgressIndicator())
|
||||
else if (state.hubs.isEmpty)
|
||||
HubEmptyState(
|
||||
onAddPressed: () async {
|
||||
final bool? success = await Modular.to
|
||||
.toEditHub();
|
||||
if (success == true && context.mounted) {
|
||||
BlocProvider.of<ClientHubsBloc>(
|
||||
context,
|
||||
).add(const ClientHubsFetched());
|
||||
}
|
||||
},
|
||||
)
|
||||
else ...<Widget>[
|
||||
...state.hubs.map(
|
||||
(Hub hub) => HubCard(
|
||||
hub: hub,
|
||||
onTap: () async {
|
||||
final bool? success = await Modular.to
|
||||
.toHubDetails(hub);
|
||||
if (success == true && context.mounted) {
|
||||
BlocProvider.of<ClientHubsBloc>(
|
||||
context,
|
||||
).add(const ClientHubsFetched());
|
||||
}
|
||||
},
|
||||
onNfcPressed: () =>
|
||||
BlocProvider.of<ClientHubsBloc>(
|
||||
context,
|
||||
).add(
|
||||
ClientHubsIdentifyDialogToggled(hub: hub),
|
||||
),
|
||||
onDeletePressed: () =>
|
||||
_confirmDeleteHub(context, hub),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
const HubInfoCard(),
|
||||
]),
|
||||
body: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
_buildAppBar(context),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space5,
|
||||
vertical: UiConstants.space5,
|
||||
).copyWith(bottom: 100),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildListDelegate(<Widget>[
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: UiConstants.space5),
|
||||
child: HubInfoCard(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
if (state.hubToIdentify != null)
|
||||
IdentifyNfcDialog(
|
||||
hub: state.hubToIdentify!,
|
||||
onAssign: (String tagId) {
|
||||
BlocProvider.of<ClientHubsBloc>(context).add(
|
||||
ClientHubsNfcTagAssignRequested(
|
||||
hubId: state.hubToIdentify!.id,
|
||||
nfcTagId: tagId,
|
||||
if (state.status == ClientHubsStatus.loading)
|
||||
const Center(child: CircularProgressIndicator())
|
||||
else if (state.hubs.isEmpty)
|
||||
HubEmptyState(
|
||||
onAddPressed: () async {
|
||||
final bool? success = await Modular.to.toEditHub();
|
||||
if (success == true && context.mounted) {
|
||||
BlocProvider.of<ClientHubsBloc>(
|
||||
context,
|
||||
).add(const ClientHubsFetched());
|
||||
}
|
||||
},
|
||||
)
|
||||
else ...<Widget>[
|
||||
...state.hubs.map(
|
||||
(Hub hub) => HubCard(
|
||||
hub: hub,
|
||||
onTap: () async {
|
||||
final bool? success = await Modular.to
|
||||
.toHubDetails(hub);
|
||||
if (success == true && context.mounted) {
|
||||
BlocProvider.of<ClientHubsBloc>(
|
||||
context,
|
||||
).add(const ClientHubsFetched());
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
onCancel: () => BlocProvider.of<ClientHubsBloc>(
|
||||
context,
|
||||
).add(const ClientHubsIdentifyDialogToggled()),
|
||||
),
|
||||
if (state.status == ClientHubsStatus.actionInProgress)
|
||||
Container(
|
||||
color: UiColors.black.withValues(alpha: 0.1),
|
||||
child: const Center(child: CircularProgressIndicator()),
|
||||
],
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
]),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -160,7 +129,7 @@ class ClientHubsPage extends StatelessWidget {
|
||||
|
||||
Widget _buildAppBar(BuildContext context) {
|
||||
return SliverAppBar(
|
||||
backgroundColor: UiColors.foreground, // Dark Slate equivalent
|
||||
backgroundColor: UiColors.foreground,
|
||||
automaticallyImplyLeading: false,
|
||||
expandedHeight: 140,
|
||||
pinned: true,
|
||||
@@ -219,51 +188,4 @@ class ClientHubsPage extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _confirmDeleteHub(BuildContext context, Hub hub) async {
|
||||
final String hubName = hub.name.isEmpty ? t.client_hubs.title : hub.name;
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext dialogContext) {
|
||||
return AlertDialog(
|
||||
title: Text(t.client_hubs.delete_dialog.title),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(t.client_hubs.delete_dialog.message(hubName: hubName)),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
Text(t.client_hubs.delete_dialog.undo_warning),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
Text(
|
||||
t.client_hubs.delete_dialog.dependency_warning,
|
||||
style: UiTypography.footnote1r.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () => Modular.to.pop(),
|
||||
child: Text(t.client_hubs.delete_dialog.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
BlocProvider.of<ClientHubsBloc>(
|
||||
context,
|
||||
).add(ClientHubsDeleteRequested(hub.id));
|
||||
Modular.to.pop();
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: UiColors.destructive,
|
||||
),
|
||||
child: Text(t.client_hubs.delete_dialog.delete),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,23 +6,11 @@ import 'package:core_localization/core_localization.dart';
|
||||
/// A card displaying information about a single hub.
|
||||
class HubCard extends StatelessWidget {
|
||||
/// Creates a [HubCard].
|
||||
const HubCard({
|
||||
required this.hub,
|
||||
required this.onNfcPressed,
|
||||
required this.onDeletePressed,
|
||||
required this.onTap,
|
||||
super.key,
|
||||
});
|
||||
const HubCard({required this.hub, required this.onTap, super.key});
|
||||
|
||||
/// The hub to display.
|
||||
final Hub hub;
|
||||
|
||||
/// Callback when the NFC button is pressed.
|
||||
final VoidCallback onNfcPressed;
|
||||
|
||||
/// Callback when the delete button is pressed.
|
||||
final VoidCallback onDeletePressed;
|
||||
|
||||
/// Callback when the card is tapped.
|
||||
final VoidCallback onTap;
|
||||
|
||||
@@ -37,13 +25,7 @@ class HubCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
boxShadow: const <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.popupShadow,
|
||||
blurRadius: 10,
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
],
|
||||
border: Border.all(color: UiColors.border),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
@@ -72,6 +54,7 @@ class HubCard extends StatelessWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: UiConstants.space1),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
const Icon(
|
||||
UiIcons.mapPin,
|
||||
@@ -79,7 +62,7 @@ class HubCard extends StatelessWidget {
|
||||
color: UiColors.iconThird,
|
||||
),
|
||||
const SizedBox(width: UiConstants.space1),
|
||||
Expanded(
|
||||
Flexible(
|
||||
child: Text(
|
||||
hub.address,
|
||||
style: UiTypography.footnote1r.textSecondary,
|
||||
@@ -104,20 +87,10 @@ class HubCard extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
onPressed: onDeletePressed,
|
||||
icon: const Icon(
|
||||
UiIcons.delete,
|
||||
color: UiColors.destructive,
|
||||
size: 20,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
splashRadius: 20,
|
||||
),
|
||||
],
|
||||
const Icon(
|
||||
UiIcons.chevronRight,
|
||||
size: 16,
|
||||
color: UiColors.iconSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -31,10 +31,7 @@ class HubInfoCard extends StatelessWidget {
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Text(
|
||||
t.client_hubs.about_hubs.description,
|
||||
style: UiTypography.footnote1r.copyWith(
|
||||
color: UiColors.textSecondary,
|
||||
height: 1.4,
|
||||
),
|
||||
style: UiTypography.footnote1r.textSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user