refactor: Extract hub details UI components into dedicated widgets and introduce new edit hub form elements.
This commit is contained in:
@@ -9,7 +9,7 @@ import 'package:krow_domain/krow_domain.dart';
|
|||||||
import '../blocs/edit_hub/edit_hub_bloc.dart';
|
import '../blocs/edit_hub/edit_hub_bloc.dart';
|
||||||
import '../blocs/edit_hub/edit_hub_event.dart';
|
import '../blocs/edit_hub/edit_hub_event.dart';
|
||||||
import '../blocs/edit_hub/edit_hub_state.dart';
|
import '../blocs/edit_hub/edit_hub_state.dart';
|
||||||
import '../widgets/hub_address_autocomplete.dart';
|
import '../widgets/edit_hub/edit_hub_form_section.dart';
|
||||||
|
|
||||||
/// A dedicated full-screen page for adding or editing a hub.
|
/// A dedicated full-screen page for adding or editing a hub.
|
||||||
class EditHubPage extends StatefulWidget {
|
class EditHubPage extends StatefulWidget {
|
||||||
@@ -36,7 +36,7 @@ class _EditHubPageState extends State<EditHubPage> {
|
|||||||
_addressController = TextEditingController(text: widget.hub?.address);
|
_addressController = TextEditingController(text: widget.hub?.address);
|
||||||
_addressFocusNode = FocusNode();
|
_addressFocusNode = FocusNode();
|
||||||
|
|
||||||
// Update header on change
|
// Update header on change (if header is added back)
|
||||||
_nameController.addListener(() => setState(() {}));
|
_nameController.addListener(() => setState(() {}));
|
||||||
_addressController.addListener(() => setState(() {}));
|
_addressController.addListener(() => setState(() {}));
|
||||||
}
|
}
|
||||||
@@ -136,59 +136,17 @@ class _EditHubPageState extends State<EditHubPage> {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
padding: const EdgeInsets.all(UiConstants.space5),
|
||||||
child: Form(
|
child: EditHubFormSection(
|
||||||
key: _formKey,
|
formKey: _formKey,
|
||||||
child: Column(
|
nameController: _nameController,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
addressController: _addressController,
|
||||||
children: <Widget>[
|
addressFocusNode: _addressFocusNode,
|
||||||
// ── Name field ──────────────────────────────────
|
onAddressSelected: (Prediction prediction) {
|
||||||
_FieldLabel(t.client_hubs.edit_hub.name_label),
|
|
||||||
TextFormField(
|
|
||||||
controller: _nameController,
|
|
||||||
style: UiTypography.body1r.textPrimary,
|
|
||||||
textInputAction: TextInputAction.next,
|
|
||||||
validator: (String? value) {
|
|
||||||
if (value == null || value.trim().isEmpty) {
|
|
||||||
return 'Name is required';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
decoration: _inputDecoration(
|
|
||||||
t.client_hubs.edit_hub.name_hint,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
|
|
||||||
// ── Address field ────────────────────────────────
|
|
||||||
_FieldLabel(
|
|
||||||
t.client_hubs.edit_hub.address_label,
|
|
||||||
),
|
|
||||||
HubAddressAutocomplete(
|
|
||||||
controller: _addressController,
|
|
||||||
hintText: t.client_hubs.edit_hub.address_hint,
|
|
||||||
focusNode: _addressFocusNode,
|
|
||||||
onSelected: (Prediction prediction) {
|
|
||||||
_selectedPrediction = prediction;
|
_selectedPrediction = prediction;
|
||||||
},
|
},
|
||||||
),
|
onSave: _onSave,
|
||||||
|
isSaving: isSaving,
|
||||||
const SizedBox(height: UiConstants.space8),
|
isEdit: widget.hub != null,
|
||||||
|
|
||||||
// ── Save button ──────────────────────────────────
|
|
||||||
UiButton.primary(
|
|
||||||
onPressed: isSaving ? null : _onSave,
|
|
||||||
text: widget.hub == null
|
|
||||||
? t
|
|
||||||
.client_hubs
|
|
||||||
.add_hub_dialog
|
|
||||||
.create_button
|
|
||||||
: t.client_hubs.edit_hub.save_button,
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 40),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -209,42 +167,4 @@ class _EditHubPageState extends State<EditHubPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
InputDecoration _inputDecoration(String hint) {
|
|
||||||
return InputDecoration(
|
|
||||||
hintText: hint,
|
|
||||||
hintStyle: UiTypography.body2r.textPlaceholder,
|
|
||||||
filled: true,
|
|
||||||
fillColor: UiColors.input,
|
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: UiConstants.space4,
|
|
||||||
vertical: 14,
|
|
||||||
),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
|
||||||
borderSide: const BorderSide(color: UiColors.border),
|
|
||||||
),
|
|
||||||
enabledBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
|
||||||
borderSide: const BorderSide(color: UiColors.border),
|
|
||||||
),
|
|
||||||
focusedBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
|
||||||
borderSide: const BorderSide(color: UiColors.ring, width: 2),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _FieldLabel extends StatelessWidget {
|
|
||||||
const _FieldLabel(this.text);
|
|
||||||
final String text;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: UiConstants.space2),
|
|
||||||
child: Text(text, style: UiTypography.body2m.textPrimary),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import 'package:krow_domain/krow_domain.dart';
|
|||||||
import '../blocs/hub_details/hub_details_bloc.dart';
|
import '../blocs/hub_details/hub_details_bloc.dart';
|
||||||
import '../blocs/hub_details/hub_details_event.dart';
|
import '../blocs/hub_details/hub_details_event.dart';
|
||||||
import '../blocs/hub_details/hub_details_state.dart';
|
import '../blocs/hub_details/hub_details_state.dart';
|
||||||
|
import '../widgets/hub_details/hub_details_bottom_actions.dart';
|
||||||
|
import '../widgets/hub_details/hub_details_header.dart';
|
||||||
|
import '../widgets/hub_details/hub_details_item.dart';
|
||||||
|
|
||||||
/// A read-only details page for a single [Hub].
|
/// A read-only details page for a single [Hub].
|
||||||
///
|
///
|
||||||
@@ -47,49 +50,11 @@ class HubDetailsPage extends StatelessWidget {
|
|||||||
final bool isLoading = state.status == HubDetailsStatus.loading;
|
final bool isLoading = state.status == HubDetailsStatus.loading;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: UiAppBar(
|
appBar: const UiAppBar(showBackButton: true),
|
||||||
title: t.client_hubs.hub_details.title,
|
bottomNavigationBar: HubDetailsBottomActions(
|
||||||
onLeadingPressed: () => Modular.to.pop(),
|
isLoading: isLoading,
|
||||||
),
|
onDelete: () => _confirmDeleteHub(context),
|
||||||
bottomNavigationBar: SafeArea(
|
onEdit: () => _navigateToEditPage(context),
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
|
||||||
const Divider(height: 1, thickness: 0.5),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
|
||||||
child: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Expanded(
|
|
||||||
child: UiButton.secondary(
|
|
||||||
onPressed: isLoading
|
|
||||||
? null
|
|
||||||
: () => _confirmDeleteHub(context),
|
|
||||||
text: t.common.delete,
|
|
||||||
leadingIcon: UiIcons.delete,
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
foregroundColor: UiColors.destructive,
|
|
||||||
side: const BorderSide(
|
|
||||||
color: UiColors.destructive,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: UiConstants.space4),
|
|
||||||
Expanded(
|
|
||||||
child: UiButton.secondary(
|
|
||||||
onPressed: isLoading
|
|
||||||
? null
|
|
||||||
: () => _navigateToEditPage(context),
|
|
||||||
text: t.client_hubs.hub_details.edit_button,
|
|
||||||
leadingIcon: UiIcons.edit,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
backgroundColor: UiColors.bgMenu,
|
backgroundColor: UiColors.bgMenu,
|
||||||
body: Stack(
|
body: Stack(
|
||||||
@@ -99,75 +64,7 @@ class HubDetailsPage extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
// ── Header ──────────────────────────────────────────
|
// ── Header ──────────────────────────────────────────
|
||||||
Padding(
|
HubDetailsHeader(hub: hub),
|
||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: <Widget>[
|
|
||||||
Container(
|
|
||||||
width: 114,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.primary.withValues(
|
|
||||||
alpha: 0.08,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
UiConstants.radiusBase,
|
|
||||||
),
|
|
||||||
border: Border.all(color: UiColors.primary),
|
|
||||||
),
|
|
||||||
child: const Center(
|
|
||||||
child: Icon(
|
|
||||||
UiIcons.nfc,
|
|
||||||
color: UiColors.primary,
|
|
||||||
size: 32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: UiConstants.space4),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
hub.name,
|
|
||||||
style:
|
|
||||||
UiTypography.headline1b.textPrimary,
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: UiConstants.space1,
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: <Widget>[
|
|
||||||
const Icon(
|
|
||||||
UiIcons.mapPin,
|
|
||||||
size: 16,
|
|
||||||
color: UiColors.textSecondary,
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
width: UiConstants.space1,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
hub.address,
|
|
||||||
style: UiTypography
|
|
||||||
.body2r
|
|
||||||
.textSecondary,
|
|
||||||
maxLines: 2,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Divider(height: 1, thickness: 0.5),
|
const Divider(height: 1, thickness: 0.5),
|
||||||
|
|
||||||
Padding(
|
Padding(
|
||||||
@@ -175,7 +72,7 @@ class HubDetailsPage extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_buildDetailItem(
|
HubDetailsItem(
|
||||||
label: t.client_hubs.hub_details.nfc_label,
|
label: t.client_hubs.hub_details.nfc_label,
|
||||||
value:
|
value:
|
||||||
hub.nfcTagId ??
|
hub.nfcTagId ??
|
||||||
@@ -203,51 +100,6 @@ class HubDetailsPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDetailItem({
|
|
||||||
required String label,
|
|
||||||
required String value,
|
|
||||||
required IconData icon,
|
|
||||||
bool isHighlight = false,
|
|
||||||
}) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.white,
|
|
||||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
|
||||||
border: Border.all(color: UiColors.border),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space3),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isHighlight
|
|
||||||
? UiColors.tagInProgress
|
|
||||||
: UiColors.bgInputField,
|
|
||||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
icon,
|
|
||||||
color: isHighlight ? UiColors.iconSuccess : UiColors.iconThird,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: UiConstants.space4),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(label, style: UiTypography.footnote1r.textSecondary),
|
|
||||||
const SizedBox(height: UiConstants.space1),
|
|
||||||
Text(value, style: UiTypography.body1m.textPrimary),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _navigateToEditPage(BuildContext context) async {
|
Future<void> _navigateToEditPage(BuildContext context) async {
|
||||||
final bool? saved = await Modular.to.toEditHub(hub: hub);
|
final bool? saved = await Modular.to.toEditHub(hub: hub);
|
||||||
if (saved == true && context.mounted) {
|
if (saved == true && context.mounted) {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// A simple field label widget for the edit hub page.
|
||||||
|
class EditHubFieldLabel extends StatelessWidget {
|
||||||
|
const EditHubFieldLabel(this.text, {super.key});
|
||||||
|
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: UiConstants.space2),
|
||||||
|
child: Text(text, style: UiTypography.body2m.textPrimary),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:google_places_flutter/model/prediction.dart';
|
||||||
|
|
||||||
|
import '../hub_address_autocomplete.dart';
|
||||||
|
import 'edit_hub_field_label.dart';
|
||||||
|
|
||||||
|
/// The form section for adding or editing a hub.
|
||||||
|
class EditHubFormSection extends StatelessWidget {
|
||||||
|
const EditHubFormSection({
|
||||||
|
required this.formKey,
|
||||||
|
required this.nameController,
|
||||||
|
required this.addressController,
|
||||||
|
required this.addressFocusNode,
|
||||||
|
required this.onAddressSelected,
|
||||||
|
required this.onSave,
|
||||||
|
this.isSaving = false,
|
||||||
|
this.isEdit = false,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final GlobalKey<FormState> formKey;
|
||||||
|
final TextEditingController nameController;
|
||||||
|
final TextEditingController addressController;
|
||||||
|
final FocusNode addressFocusNode;
|
||||||
|
final ValueChanged<Prediction> onAddressSelected;
|
||||||
|
final VoidCallback onSave;
|
||||||
|
final bool isSaving;
|
||||||
|
final bool isEdit;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Form(
|
||||||
|
key: formKey,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: <Widget>[
|
||||||
|
// ── Name field ──────────────────────────────────
|
||||||
|
EditHubFieldLabel(t.client_hubs.edit_hub.name_label),
|
||||||
|
TextFormField(
|
||||||
|
controller: nameController,
|
||||||
|
style: UiTypography.body1r.textPrimary,
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
|
validator: (String? value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return 'Name is required';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
decoration: _inputDecoration(t.client_hubs.edit_hub.name_hint),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
|
||||||
|
// ── Address field ────────────────────────────────
|
||||||
|
EditHubFieldLabel(t.client_hubs.edit_hub.address_label),
|
||||||
|
HubAddressAutocomplete(
|
||||||
|
controller: addressController,
|
||||||
|
hintText: t.client_hubs.edit_hub.address_hint,
|
||||||
|
focusNode: addressFocusNode,
|
||||||
|
onSelected: onAddressSelected,
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: UiConstants.space8),
|
||||||
|
|
||||||
|
// ── Save button ──────────────────────────────────
|
||||||
|
UiButton.primary(
|
||||||
|
onPressed: isSaving ? null : onSave,
|
||||||
|
text: isEdit
|
||||||
|
? t.client_hubs.edit_hub.save_button
|
||||||
|
: t.client_hubs.add_hub_dialog.create_button,
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputDecoration _inputDecoration(String hint) {
|
||||||
|
return InputDecoration(
|
||||||
|
hintText: hint,
|
||||||
|
hintStyle: UiTypography.body2r.textPlaceholder,
|
||||||
|
filled: true,
|
||||||
|
fillColor: UiColors.input,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space4,
|
||||||
|
vertical: 14,
|
||||||
|
),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||||
|
borderSide: const BorderSide(color: UiColors.border),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||||
|
borderSide: const BorderSide(color: UiColors.border),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||||
|
borderSide: const BorderSide(color: UiColors.ring, width: 2),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Bottom action buttons for the hub details page.
|
||||||
|
class HubDetailsBottomActions extends StatelessWidget {
|
||||||
|
const HubDetailsBottomActions({
|
||||||
|
required this.onDelete,
|
||||||
|
required this.onEdit,
|
||||||
|
this.isLoading = false,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final VoidCallback onDelete;
|
||||||
|
final VoidCallback onEdit;
|
||||||
|
final bool isLoading;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SafeArea(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
const Divider(height: 1, thickness: 0.5),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space5),
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: UiButton.secondary(
|
||||||
|
onPressed: isLoading ? null : onDelete,
|
||||||
|
text: t.common.delete,
|
||||||
|
leadingIcon: UiIcons.delete,
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
foregroundColor: UiColors.destructive,
|
||||||
|
side: const BorderSide(color: UiColors.destructive),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: UiConstants.space4),
|
||||||
|
Expanded(
|
||||||
|
child: UiButton.secondary(
|
||||||
|
onPressed: isLoading ? null : onEdit,
|
||||||
|
text: t.client_hubs.hub_details.edit_button,
|
||||||
|
leadingIcon: UiIcons.edit,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
|
/// Header widget for the hub details page.
|
||||||
|
class HubDetailsHeader extends StatelessWidget {
|
||||||
|
const HubDetailsHeader({required this.hub, super.key});
|
||||||
|
|
||||||
|
final Hub hub;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space5),
|
||||||
|
child: IntrinsicHeight(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
spacing: UiConstants.space1,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(hub.name, style: UiTypography.headline1b.textPrimary),
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
const Icon(
|
||||||
|
UiIcons.mapPin,
|
||||||
|
size: 16,
|
||||||
|
color: UiColors.textSecondary,
|
||||||
|
),
|
||||||
|
const SizedBox(width: UiConstants.space1),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
hub.address,
|
||||||
|
style: UiTypography.body2r.textSecondary,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// A reusable detail item for the hub details page.
|
||||||
|
class HubDetailsItem extends StatelessWidget {
|
||||||
|
const HubDetailsItem({
|
||||||
|
required this.label,
|
||||||
|
required this.value,
|
||||||
|
required this.icon,
|
||||||
|
this.isHighlight = false,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String label;
|
||||||
|
final String value;
|
||||||
|
final IconData icon;
|
||||||
|
final bool isHighlight;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.white,
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||||
|
border: Border.all(color: UiColors.border),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space3),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isHighlight
|
||||||
|
? UiColors.tagInProgress
|
||||||
|
: UiColors.bgInputField,
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
color: isHighlight ? UiColors.iconSuccess : UiColors.iconThird,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: UiConstants.space4),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(label, style: UiTypography.footnote1r.textSecondary),
|
||||||
|
const SizedBox(height: UiConstants.space1),
|
||||||
|
Text(value, style: UiTypography.body1m.textPrimary),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,9 @@ class OnboardingSection extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final TranslationsStaffProfileEn i18n = Translations.of(context).staff.profile;
|
final TranslationsStaffProfileEn i18n = Translations.of(
|
||||||
|
context,
|
||||||
|
).staff.profile;
|
||||||
|
|
||||||
return BlocBuilder<ProfileCubit, ProfileState>(
|
return BlocBuilder<ProfileCubit, ProfileState>(
|
||||||
builder: (BuildContext context, ProfileState state) {
|
builder: (BuildContext context, ProfileState state) {
|
||||||
@@ -49,6 +51,11 @@ class OnboardingSection extends StatelessWidget {
|
|||||||
completed: state.experienceComplete,
|
completed: state.experienceComplete,
|
||||||
onTap: () => Modular.to.toExperience(),
|
onTap: () => Modular.to.toExperience(),
|
||||||
),
|
),
|
||||||
|
ProfileMenuItem(
|
||||||
|
icon: UiIcons.shirt,
|
||||||
|
label: i18n.menu_items.attire,
|
||||||
|
onTap: () => Modular.to.toAttire(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user