refactor: move dialog state management to BLoC and make client hubs page stateless.
This commit is contained in:
@@ -31,6 +31,26 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
|
||||
on<ClientHubsDeleteRequested>(_onDeleteRequested);
|
||||
on<ClientHubsNfcTagAssignRequested>(_onNfcTagAssignRequested);
|
||||
on<ClientHubsMessageCleared>(_onMessageCleared);
|
||||
on<ClientHubsAddDialogToggled>(_onAddDialogToggled);
|
||||
on<ClientHubsIdentifyDialogToggled>(_onIdentifyDialogToggled);
|
||||
}
|
||||
|
||||
void _onAddDialogToggled(
|
||||
ClientHubsAddDialogToggled event,
|
||||
Emitter<ClientHubsState> emit,
|
||||
) {
|
||||
emit(state.copyWith(showAddHubDialog: event.visible));
|
||||
}
|
||||
|
||||
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(
|
||||
@@ -66,6 +86,7 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
|
||||
status: ClientHubsStatus.actionSuccess,
|
||||
hubs: hubs,
|
||||
successMessage: 'Hub created successfully',
|
||||
showAddHubDialog: false,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
@@ -118,6 +139,7 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
|
||||
status: ClientHubsStatus.actionSuccess,
|
||||
hubs: hubs,
|
||||
successMessage: 'NFC tag assigned successfully',
|
||||
clearHubToIdentify: true,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Base class for all client hubs events.
|
||||
abstract class ClientHubsEvent extends Equatable {
|
||||
@@ -52,3 +53,23 @@ class ClientHubsNfcTagAssignRequested extends ClientHubsEvent {
|
||||
class ClientHubsMessageCleared extends ClientHubsEvent {
|
||||
const ClientHubsMessageCleared();
|
||||
}
|
||||
|
||||
/// Event triggered to toggle the visibility of the "Add Hub" dialog.
|
||||
class ClientHubsAddDialogToggled extends ClientHubsEvent {
|
||||
final bool visible;
|
||||
|
||||
const ClientHubsAddDialogToggled({required this.visible});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [visible];
|
||||
}
|
||||
|
||||
/// Event triggered to toggle the visibility of the "Identify NFC" dialog.
|
||||
class ClientHubsIdentifyDialogToggled extends ClientHubsEvent {
|
||||
final Hub? hub;
|
||||
|
||||
const ClientHubsIdentifyDialogToggled({this.hub});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [hub];
|
||||
}
|
||||
|
||||
@@ -19,11 +19,20 @@ class ClientHubsState extends Equatable {
|
||||
final String? errorMessage;
|
||||
final String? successMessage;
|
||||
|
||||
/// Whether the "Add Hub" dialog should be visible.
|
||||
final bool showAddHubDialog;
|
||||
|
||||
/// The hub currently being identified/assigned an NFC tag.
|
||||
/// If null, the identification dialog is closed.
|
||||
final Hub? hubToIdentify;
|
||||
|
||||
const ClientHubsState({
|
||||
this.status = ClientHubsStatus.initial,
|
||||
this.hubs = const [],
|
||||
this.errorMessage,
|
||||
this.successMessage,
|
||||
this.showAddHubDialog = false,
|
||||
this.hubToIdentify,
|
||||
});
|
||||
|
||||
ClientHubsState copyWith({
|
||||
@@ -31,15 +40,29 @@ class ClientHubsState extends Equatable {
|
||||
List<Hub>? hubs,
|
||||
String? errorMessage,
|
||||
String? successMessage,
|
||||
bool? showAddHubDialog,
|
||||
Hub? hubToIdentify,
|
||||
bool clearHubToIdentify = false,
|
||||
}) {
|
||||
return ClientHubsState(
|
||||
status: status ?? this.status,
|
||||
hubs: hubs ?? this.hubs,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
successMessage: successMessage ?? this.successMessage,
|
||||
showAddHubDialog: showAddHubDialog ?? this.showAddHubDialog,
|
||||
hubToIdentify: clearHubToIdentify
|
||||
? null
|
||||
: (hubToIdentify ?? this.hubToIdentify),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status, hubs, errorMessage, successMessage];
|
||||
List<Object?> get props => [
|
||||
status,
|
||||
hubs,
|
||||
errorMessage,
|
||||
successMessage,
|
||||
showAddHubDialog,
|
||||
hubToIdentify,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import '../blocs/client_hubs_bloc.dart';
|
||||
import '../blocs/client_hubs_event.dart';
|
||||
@@ -15,18 +14,13 @@ import '../widgets/hub_info_card.dart';
|
||||
import '../widgets/identify_nfc_dialog.dart';
|
||||
|
||||
/// The main page for the client hubs feature.
|
||||
class ClientHubsPage extends StatefulWidget {
|
||||
///
|
||||
/// This page follows the KROW Clean Architecture by being a [StatelessWidget]
|
||||
/// and delegating all state management to the [ClientHubsBloc].
|
||||
class ClientHubsPage extends StatelessWidget {
|
||||
/// Creates a [ClientHubsPage].
|
||||
const ClientHubsPage({super.key});
|
||||
|
||||
@override
|
||||
State<ClientHubsPage> createState() => _ClientHubsPageState();
|
||||
}
|
||||
|
||||
class _ClientHubsPageState extends State<ClientHubsPage> {
|
||||
bool _showAddHub = false;
|
||||
Hub? _hubToIdentify;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<ClientHubsBloc>(
|
||||
@@ -68,14 +62,22 @@ class _ClientHubsPageState extends State<ClientHubsPage> {
|
||||
else if (state.hubs.isEmpty)
|
||||
HubEmptyState(
|
||||
onAddPressed: () =>
|
||||
setState(() => _showAddHub = true),
|
||||
BlocProvider.of<ClientHubsBloc>(context).add(
|
||||
const ClientHubsAddDialogToggled(
|
||||
visible: true,
|
||||
),
|
||||
),
|
||||
)
|
||||
else ...[
|
||||
...state.hubs.map(
|
||||
(hub) => HubCard(
|
||||
hub: hub,
|
||||
onNfcPressed: () =>
|
||||
setState(() => _hubToIdentify = hub),
|
||||
BlocProvider.of<ClientHubsBloc>(
|
||||
context,
|
||||
).add(
|
||||
ClientHubsIdentifyDialogToggled(hub: hub),
|
||||
),
|
||||
onDeletePressed: () =>
|
||||
BlocProvider.of<ClientHubsBloc>(
|
||||
context,
|
||||
@@ -90,33 +92,35 @@ class _ClientHubsPageState extends State<ClientHubsPage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_showAddHub)
|
||||
if (state.showAddHubDialog)
|
||||
AddHubDialog(
|
||||
onCreate: (name, address) {
|
||||
BlocProvider.of<ClientHubsBloc>(context).add(
|
||||
ClientHubsAddRequested(name: name, address: address),
|
||||
);
|
||||
setState(() => _showAddHub = false);
|
||||
},
|
||||
onCancel: () => setState(() => _showAddHub = false),
|
||||
onCancel: () => BlocProvider.of<ClientHubsBloc>(
|
||||
context,
|
||||
).add(const ClientHubsAddDialogToggled(visible: false)),
|
||||
),
|
||||
if (_hubToIdentify != null)
|
||||
if (state.hubToIdentify != null)
|
||||
IdentifyNfcDialog(
|
||||
hub: _hubToIdentify!,
|
||||
hub: state.hubToIdentify!,
|
||||
onAssign: (tagId) {
|
||||
BlocProvider.of<ClientHubsBloc>(context).add(
|
||||
ClientHubsNfcTagAssignRequested(
|
||||
hubId: _hubToIdentify!.id,
|
||||
hubId: state.hubToIdentify!.id,
|
||||
nfcTagId: tagId,
|
||||
),
|
||||
);
|
||||
setState(() => _hubToIdentify = null);
|
||||
},
|
||||
onCancel: () => setState(() => _hubToIdentify = null),
|
||||
onCancel: () => BlocProvider.of<ClientHubsBloc>(
|
||||
context,
|
||||
).add(const ClientHubsIdentifyDialogToggled()),
|
||||
),
|
||||
if (state.status == ClientHubsStatus.actionInProgress)
|
||||
Container(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
color: Colors.black.withValues(alpha: 0.1),
|
||||
child: const Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
],
|
||||
@@ -152,7 +156,7 @@ class _ClientHubsPageState extends State<ClientHubsPage> {
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
color: Colors.white.withValues(alpha: 0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
@@ -187,7 +191,9 @@ class _ClientHubsPageState extends State<ClientHubsPage> {
|
||||
],
|
||||
),
|
||||
UiButton.primary(
|
||||
onPressed: () => setState(() => _showAddHub = true),
|
||||
onPressed: () => BlocProvider.of<ClientHubsBloc>(
|
||||
context,
|
||||
).add(const ClientHubsAddDialogToggled(visible: true)),
|
||||
text: t.client_hubs.add_hub,
|
||||
leadingIcon: LucideIcons.plus,
|
||||
),
|
||||
|
||||
@@ -42,7 +42,7 @@ class _AddHubDialogState extends State<AddHubDialog> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: Colors.black.withOpacity(0.5),
|
||||
color: Colors.black.withValues(alpha: 0.5),
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Container(
|
||||
@@ -52,7 +52,10 @@ class _AddHubDialogState extends State<AddHubDialog> {
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 20),
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.1),
|
||||
blurRadius: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
|
||||
@@ -33,7 +33,7 @@ class HubCard extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.04),
|
||||
color: Colors.black.withValues(alpha: 0.04),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
|
||||
@@ -20,7 +20,7 @@ class HubEmptyState extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.04),
|
||||
color: Colors.black.withValues(alpha: 0.04),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
|
||||
@@ -40,7 +40,7 @@ class _IdentifyNfcDialogState extends State<IdentifyNfcDialog> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: Colors.black.withOpacity(0.5),
|
||||
color: Colors.black.withValues(alpha: 0.5),
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Container(
|
||||
@@ -50,7 +50,10 @@ class _IdentifyNfcDialogState extends State<IdentifyNfcDialog> {
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 20),
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.1),
|
||||
blurRadius: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
|
||||
Reference in New Issue
Block a user