[Part 2] TF-4308 Add Create new label button in ChooseLabelModal (#4457)

This commit is contained in:
Dat Vu
2026-04-17 16:51:49 +07:00
committed by GitHub
parent a80de79537
commit a693ceb456
9 changed files with 297 additions and 254 deletions
@@ -1,31 +1,19 @@
import 'package:equatable/equatable.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:labels/model/label.dart';
import 'package:tmail_ui_user/features/labels/domain/usecases/create_new_label_interactor.dart';
import 'package:tmail_ui_user/features/labels/domain/usecases/edit_label_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart';
class OpenEditLabelModalParams with EquatableMixin {
final Label selectedLabel;
final AccountId accountId;
final VerifyNameInteractor verifyNameInteractor;
final CreateNewLabelInteractor createNewLabelInteractor;
final EditLabelInteractor editLabelInteractor;
final AccountId? accountId;
const OpenEditLabelModalParams({
required this.selectedLabel,
required this.accountId,
required this.verifyNameInteractor,
required this.createNewLabelInteractor,
required this.editLabelInteractor,
});
@override
List<Object?> get props => [
selectedLabel,
accountId,
verifyNameInteractor,
createNewLabelInteractor,
editLabelInteractor,
];
}
@@ -41,6 +41,8 @@ class AddListLabelToListEmailsDelegate extends BaseController {
);
},
imagePaths: imagePaths,
onCreateALabelAction: () => _labelController.onCreateALabelAction(
accountId: _labelController.accountId, hasResult: true),
),
dialogLabel: 'choose-label-modal',
);
@@ -9,12 +9,9 @@ import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.d
import 'package:tmail_ui_user/features/labels/domain/model/open_edit_label_modal_params.dart';
import 'package:tmail_ui_user/features/labels/domain/state/delete_a_label_state.dart';
import 'package:tmail_ui_user/features/labels/domain/state/edit_label_state.dart';
import 'package:tmail_ui_user/features/labels/domain/usecases/create_new_label_interactor.dart';
import 'package:tmail_ui_user/features/labels/domain/usecases/edit_label_interactor.dart';
import 'package:tmail_ui_user/features/labels/presentation/label_controller.dart';
import 'package:tmail_ui_user/features/labels/presentation/models/label_action_type.dart';
import 'package:tmail_ui_user/features/labels/presentation/widgets/create_new_label_modal.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart';
import 'package:tmail_ui_user/main/exceptions/logic_exception.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
@@ -48,31 +45,10 @@ extension HandleLabelActionTypeExtension on LabelController {
required AccountId? accountId,
required Label label,
}) async {
final createNewLabelInteractor = getBinding<CreateNewLabelInteractor>();
final editLabelInteractor = getBinding<EditLabelInteractor>();
final verifyNameInteractor = getBinding<VerifyNameInteractor>();
final isInteractorsInitialized = createNewLabelInteractor != null &&
editLabelInteractor != null &&
verifyNameInteractor != null;
if (!isInteractorsInitialized) {
_handleEditLabelFailure(EditLabelFailure(const InteractorNotInitialized()));
return;
}
if (accountId == null) {
_handleEditLabelFailure(EditLabelFailure(NotFoundAccountIdException()));
return;
}
final resultState = await openEditLabelModal(
params: OpenEditLabelModalParams(
selectedLabel: label,
accountId: accountId,
verifyNameInteractor: verifyNameInteractor,
createNewLabelInteractor: createNewLabelInteractor,
editLabelInteractor: editLabelInteractor,
),
);
@@ -86,10 +62,12 @@ extension HandleLabelActionTypeExtension on LabelController {
void _handleEditLabelSuccess(EditLabelSuccess success) {
toastManager.showMessageSuccess(success);
syncListLabels(success.newLabel);
popBack();
}
void _handleEditLabelFailure(EditLabelFailure failure) {
toastManager.showMessageFailure(failure);
popBack();
}
void syncListLabels(Label newLabel) {
@@ -8,15 +8,12 @@ import 'package:jmap_dart_client/jmap/core/state.dart';
import 'package:labels/labels.dart';
import 'package:model/mailbox/expand_mode.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart';
import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart';
import 'package:tmail_ui_user/features/labels/domain/model/open_edit_label_modal_params.dart';
import 'package:tmail_ui_user/features/labels/domain/state/create_new_label_state.dart';
import 'package:tmail_ui_user/features/labels/domain/state/delete_a_label_state.dart';
import 'package:tmail_ui_user/features/labels/domain/state/get_all_label_state.dart';
import 'package:tmail_ui_user/features/labels/domain/usecases/create_new_label_interactor.dart';
import 'package:tmail_ui_user/features/labels/domain/usecases/delete_a_label_interactor.dart';
import 'package:tmail_ui_user/features/labels/domain/usecases/edit_label_interactor.dart';
import 'package:tmail_ui_user/features/labels/domain/usecases/get_all_label_interactor.dart';
import 'package:tmail_ui_user/features/labels/domain/usecases/get_label_changes_interactor.dart';
import 'package:tmail_ui_user/features/labels/presentation/extensions/handle_label_action_type_extension.dart';
@@ -25,13 +22,11 @@ import 'package:tmail_ui_user/features/labels/presentation/extensions/handle_lab
import 'package:tmail_ui_user/features/labels/presentation/label_interactor_bindings.dart';
import 'package:tmail_ui_user/features/labels/presentation/mixin/label_context_menu_mixin.dart';
import 'package:tmail_ui_user/features/labels/presentation/widgets/create_new_label_modal.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart';
import 'package:tmail_ui_user/main/routes/dialog_router.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/get_label_setting_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_label_setting_state_interactor.dart';
import 'package:tmail_ui_user/features/push_notification/presentation/websocket/web_socket_queue_handler.dart';
import 'package:tmail_ui_user/main/error/capability_validator.dart';
import 'package:tmail_ui_user/main/exceptions/logic_exception.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
class LabelController extends BaseController
@@ -142,10 +137,7 @@ class LabelController extends BaseController
}
Future<dynamic> openCreateNewLabelModal({
required AccountId accountId,
required VerifyNameInteractor verifyNameInteractor,
required CreateNewLabelInteractor createNewLabelInteractor,
required EditLabelInteractor editLabelInteractor,
required AccountId? accountId,
}) async {
return DialogRouter().openDialogModal(
child: CreateNewLabelModal(
@@ -153,9 +145,6 @@ class LabelController extends BaseController
labels: labels,
accountId: accountId,
imagePaths: imagePaths,
verifyNameInteractor: verifyNameInteractor,
createNewLabelInteractor: createNewLabelInteractor,
editLabelInteractor: editLabelInteractor,
),
dialogLabel: 'create-new-label-modal',
);
@@ -172,50 +161,28 @@ class LabelController extends BaseController
imagePaths: imagePaths,
selectedLabel: params.selectedLabel,
actionType: LabelActionType.edit,
verifyNameInteractor: params.verifyNameInteractor,
createNewLabelInteractor: params.createNewLabelInteractor,
editLabelInteractor: params.editLabelInteractor,
),
dialogLabel: 'edit-label-modal',
);
}
Future<void> onCreateALabelAction({
Future<Label?> onCreateALabelAction({
required AccountId? accountId,
OnLabelActionCallback? onLabelActionCallback,
bool shouldPop = false,
bool hasResult = false,
}) async {
final createNewLabelInteractor = getBinding<CreateNewLabelInteractor>();
final editLabelInteractor = getBinding<EditLabelInteractor>();
final verifyNameInteractor = getBinding<VerifyNameInteractor>();
final isInteractorsInitialized = createNewLabelInteractor != null &&
editLabelInteractor != null &&
verifyNameInteractor != null;
if (!isInteractorsInitialized) {
_handleCreateNewLabelFailure(
failure: CreateNewLabelFailure(const InteractorNotInitialized()),
);
return;
}
if (accountId == null) {
_handleCreateNewLabelFailure(
failure: CreateNewLabelFailure(NotFoundAccountIdException()),
);
return;
}
final resultState = await openCreateNewLabelModal(
accountId: accountId,
verifyNameInteractor: verifyNameInteractor,
createNewLabelInteractor: createNewLabelInteractor,
editLabelInteractor: editLabelInteractor,
);
if (resultState is CreateNewLabelSuccess) {
_handleCreateNewLabelSuccess(
_addLabelToList(resultState.newLabel);
if (hasResult) {
toastManager.showMessageSuccess(resultState);
return resultState.newLabel;
}
_handleCreateNewLabelSuccessWithoutListUpdate(
success: resultState,
onLabelActionCallback: onLabelActionCallback,
shouldPop: shouldPop,
@@ -223,9 +190,11 @@ class LabelController extends BaseController
} else if (resultState is CreateNewLabelFailure) {
_handleCreateNewLabelFailure(failure: resultState);
}
return null;
}
void _handleCreateNewLabelSuccess({
void _handleCreateNewLabelSuccessWithoutListUpdate({
required CreateNewLabelSuccess success,
OnLabelActionCallback? onLabelActionCallback,
bool shouldPop = false,
@@ -233,7 +202,6 @@ class LabelController extends BaseController
if (onLabelActionCallback == null && !shouldPop) {
toastManager.showMessageSuccess(success);
}
_addLabelToList(success.newLabel);
if (onLabelActionCallback != null) {
onLabelActionCallback(success.newLabel);
}
@@ -11,7 +11,6 @@ import 'package:tmail_ui_user/features/labels/domain/usecases/edit_label_interac
import 'package:tmail_ui_user/features/labels/domain/usecases/delete_a_label_interactor.dart';
import 'package:tmail_ui_user/features/labels/domain/usecases/get_all_label_interactor.dart';
import 'package:tmail_ui_user/features/labels/domain/usecases/get_label_changes_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart';
import 'package:tmail_ui_user/main/exceptions/thrower/remote_exception_thrower.dart';
import 'package:uuid/uuid.dart';
@@ -35,11 +34,11 @@ class LabelInteractorBindings extends InteractorsBindings {
@override
void bindingsInteractor() {
Get.lazyPut(() => GetAllLabelInteractor(Get.find<LabelRepository>()));
Get.lazyPut(() => CreateNewLabelInteractor(Get.find<LabelRepository>()));
Get.lazyPut(() => EditLabelInteractor(Get.find<LabelRepository>()));
// Use `fenix: true` to avoid GetX dispose or SmartManagement cleanup when using it in dialogs.
Get.lazyPut(() => CreateNewLabelInteractor(Get.find<LabelRepository>()), fenix: true);
Get.lazyPut(() => EditLabelInteractor(Get.find<LabelRepository>()), fenix: true);
Get.lazyPut(() => DeleteALabelInteractor(Get.find<LabelRepository>()));
Get.lazyPut(() => GetLabelChangesInteractor(Get.find<LabelRepository>()));
Get.lazyPut(() => VerifyNameInteractor());
}
@override
@@ -12,17 +12,20 @@ import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
typedef OnLabelAsToEmailsAction = Function(List<Label> labels);
typedef OnCreateALabelAction = Future<Label?> Function();
class ChooseLabelModal extends StatefulWidget {
final List<Label> labels;
final ImagePaths imagePaths;
final OnLabelAsToEmailsAction onLabelAsToEmailsAction;
final OnCreateALabelAction onCreateALabelAction;
const ChooseLabelModal({
super.key,
required this.labels,
required this.imagePaths,
required this.onLabelAsToEmailsAction,
required this.onCreateALabelAction,
});
@override
@@ -30,18 +33,42 @@ class ChooseLabelModal extends StatefulWidget {
}
class _ChooseLabelModalState extends State<ChooseLabelModal> {
late final ValueNotifier<List<Label>> _labelListNotifier;
@override
void initState() {
super.initState();
_labelListNotifier = ValueNotifier([...widget.labels]);
}
@override
void dispose() {
_labelListNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final appLocalizations = AppLocalizations.of(context);
return LayoutBuilder(builder: (_, constraints) {
final currentScreenWidth = constraints.maxWidth;
final currentScreenHeight = constraints.maxHeight;
final height = math.max(0.0, math.min(currentScreenHeight - 100, 645.0));
final width = math.max(0.0, math.min(currentScreenWidth - 32, 536.0));
Widget bodyWidget = Container(
decoration: BoxDecoration(
return Center(
child: Container(
width: width,
height: height,
decoration: _buildModalDecoration(),
clipBehavior: Clip.antiAlias,
child: Stack(children: [_buildBodyContent(), _buildCloseButton()]),
),
);
});
}
BoxDecoration _buildModalDecoration() {
return BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(16)),
boxShadow: [
@@ -55,59 +82,31 @@ class _ChooseLabelModalState extends State<ChooseLabelModal> {
blurRadius: 2,
),
],
),
width: width,
height: height,
clipBehavior: Clip.antiAlias,
child: Stack(
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTitle(appLocalizations),
const Divider(height: 1, color: Colors.black12),
_buildSubtitle(appLocalizations),
if (widget.labels.isNotEmpty)
Expanded(
child: ListLabelWithActionModal(
labels: widget.labels,
imagePaths: widget.imagePaths,
onLabelAsToEmailsAction: widget.onLabelAsToEmailsAction,
onCloseModal: _onCloseModal,
),
)
else
Expanded(
child: Center(
child: NoLabelYetWidget(imagePaths: widget.imagePaths),
),
),
],
),
DefaultCloseButtonWidget(
iconClose: widget.imagePaths.icCloseDialog,
isAlignTopEnd: false,
onTapActionCallback: _onCloseModal,
),
],
),
);
return Center(child: bodyWidget);
});
}
Widget _buildTitle(AppLocalizations appLocalizations) {
Widget _buildBodyContent() {
final appLocalizations = AppLocalizations.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildHeader(appLocalizations),
const Divider(height: 1, color: Colors.black12),
_buildSubtitle(appLocalizations),
Expanded(child: _buildListOrEmptyState()),
],
);
}
Widget _buildHeader(AppLocalizations appLocalizations) {
return Container(
height: 52,
alignment: Alignment.center,
padding: const EdgeInsetsDirectional.symmetric(horizontal: 32),
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Text(
appLocalizations.chooseLabel,
style: ThemeUtils.textStyleHeadingH6().copyWith(
color: Colors.black,
),
style: ThemeUtils.textStyleHeadingH6().copyWith(color: Colors.black),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@@ -130,6 +129,45 @@ class _ChooseLabelModalState extends State<ChooseLabelModal> {
);
}
Widget _buildListOrEmptyState() {
return ValueListenableBuilder<List<Label>>(
valueListenable: _labelListNotifier,
builder: (context, labels, _) {
if (labels.isEmpty) {
return Center(
child: NoLabelYetWidget(
imagePaths: widget.imagePaths,
onCreateLabel: _onCreateNewLabel,
),
);
}
return ListLabelWithActionModal(
labels: labels,
imagePaths: widget.imagePaths,
onLabelAsToEmailsAction: widget.onLabelAsToEmailsAction,
onCloseModal: _onCloseModal,
onCreateALabelAction: _onCreateNewLabel,
);
},
);
}
Widget _buildCloseButton() {
return DefaultCloseButtonWidget(
iconClose: widget.imagePaths.icCloseDialog,
isAlignTopEnd: false,
onTapActionCallback: _onCloseModal,
);
}
Future<void> _onCreateNewLabel() async {
final newLabel = await widget.onCreateALabelAction();
if (newLabel != null) {
_labelListNotifier.value = [..._labelListNotifier.value, newLabel];
}
}
void _onCloseModal() {
popBack();
}
@@ -19,6 +19,7 @@ import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:labels/labels.dart';
import 'package:tmail_ui_user/features/base/widget/label_input_field_builder.dart';
import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart';
import 'package:tmail_ui_user/features/labels/domain/exceptions/label_exceptions.dart';
import 'package:tmail_ui_user/features/labels/domain/model/edit_label_request.dart';
import 'package:tmail_ui_user/features/labels/domain/state/create_new_label_state.dart';
@@ -33,6 +34,7 @@ import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification
import 'package:tmail_ui_user/features/mailbox_creator/domain/state/verify_name_view_state.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_creator/presentation/extensions/validator_failure_extension.dart';
import 'package:tmail_ui_user/main/exceptions/logic_exception.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
@@ -46,12 +48,9 @@ enum LabelPositiveButtonState {
class CreateNewLabelModal extends StatefulWidget {
final List<Label> labels;
final AccountId accountId;
final AccountId? accountId;
final ImagePaths imagePaths;
final LabelActionType actionType;
final CreateNewLabelInteractor createNewLabelInteractor;
final EditLabelInteractor editLabelInteractor;
final VerifyNameInteractor verifyNameInteractor;
final Label? selectedLabel;
const CreateNewLabelModal({
@@ -59,9 +58,6 @@ class CreateNewLabelModal extends StatefulWidget {
required this.labels,
required this.accountId,
required this.imagePaths,
required this.createNewLabelInteractor,
required this.editLabelInteractor,
required this.verifyNameInteractor,
this.actionType = LabelActionType.create,
this.selectedLabel,
});
@@ -85,10 +81,16 @@ class _CreateNewLabelModalState extends State<CreateNewLabelModal> {
List<String> _labelDisplayNameList = <String>[];
Color? _selectedColor;
StreamSubscription? _streamSubscription;
CreateNewLabelInteractor? _createNewLabelInteractor;
EditLabelInteractor? _editLabelInteractor;
VerifyNameInteractor? _verifyNameInteractor;
AccountId? _accountId;
@override
void initState() {
super.initState();
_accountId = widget.accountId;
_initInteractors();
final selectedLabel = widget.selectedLabel;
final labels = widget.labels;
if (selectedLabel != null) {
@@ -109,6 +111,18 @@ class _CreateNewLabelModalState extends State<CreateNewLabelModal> {
});
}
void _initInteractors() {
_createNewLabelInteractor = getBinding<CreateNewLabelInteractor>();
_editLabelInteractor = getBinding<EditLabelInteractor>();
_verifyNameInteractor = getBinding<VerifyNameInteractor>();
}
void _disposeInteractors() {
_createNewLabelInteractor = null;
_editLabelInteractor = null;
_verifyNameInteractor = null;
}
@override
Widget build(BuildContext context) {
final appLocalizations = AppLocalizations.of(context);
@@ -376,7 +390,10 @@ class _CreateNewLabelModalState extends State<CreateNewLabelModal> {
}
String? _verifyLabelName(AppLocalizations appLocalizations, String value) {
final result = widget.verifyNameInteractor.execute(
if (_verifyNameInteractor == null) {
return null;
}
final result = _verifyNameInteractor!.execute(
value,
_validators,
);
@@ -459,8 +476,18 @@ class _CreateNewLabelModalState extends State<CreateNewLabelModal> {
}
void _performCreateLabel(Label newLabel) {
_streamSubscription = widget.createNewLabelInteractor
.execute(widget.accountId, newLabel)
if (_createNewLabelInteractor == null) {
popBack(result: CreateNewLabelFailure(const InteractorNotInitialized()));
return;
}
if (_accountId == null) {
popBack(result: CreateNewLabelFailure(NotFoundAccountIdException()));
return;
}
_streamSubscription = _createNewLabelInteractor!
.execute(_accountId!, newLabel)
.listen(_handleDataStream, onError: _handleErrorStream);
}
@@ -511,8 +538,16 @@ class _CreateNewLabelModalState extends State<CreateNewLabelModal> {
newLabel: newLabel,
);
_streamSubscription = widget.editLabelInteractor
.execute(widget.accountId, labelRequest)
if (_editLabelInteractor == null) {
popBack(result: EditLabelFailure(const InteractorNotInitialized()));
return;
}
if (_accountId == null) {
popBack(result: EditLabelFailure(NotFoundAccountIdException()));
return;
}
_streamSubscription = _editLabelInteractor!
.execute(_accountId!, labelRequest)
.listen(_handleDataStream, onError: _handleErrorStream);
}
@@ -528,6 +563,8 @@ class _CreateNewLabelModalState extends State<CreateNewLabelModal> {
_labelDisplayNameList = [];
_streamSubscription?.cancel();
_streamSubscription = null;
_accountId = null;
_disposeInteractors();
super.dispose();
}
}
@@ -1,6 +1,11 @@
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/views/dialog/modal_list_action_button_widget.dart';
import 'package:core/utils/app_logger.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/views/dialog/confirm_dialog_button.dart';
import 'package:core/presentation/views/dialog/modal_list_action_button_widget.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:labels/model/label.dart';
import 'package:tmail_ui_user/features/labels/presentation/widgets/choose_label_modal.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/labels/label_list_item.dart';
@@ -11,6 +16,7 @@ class ListLabelWithActionModal extends StatefulWidget {
final ImagePaths imagePaths;
final OnLabelAsToEmailsAction onLabelAsToEmailsAction;
final VoidCallback onCloseModal;
final AsyncCallback onCreateALabelAction;
const ListLabelWithActionModal({
super.key,
@@ -18,6 +24,7 @@ class ListLabelWithActionModal extends StatefulWidget {
required this.imagePaths,
required this.onLabelAsToEmailsAction,
required this.onCloseModal,
required this.onCreateALabelAction,
});
@override
@@ -26,87 +33,112 @@ class ListLabelWithActionModal extends StatefulWidget {
}
class _ListLabelWithActionModalState extends State<ListLabelWithActionModal> {
final ValueNotifier<bool> _addLabelStateNotifier = ValueNotifier(false);
final ValueNotifier<List<Label>> _selectedLabelStateNotifier =
ValueNotifier([]);
final ValueNotifier<Set<Id>> _selectedIdsNotifier = ValueNotifier({});
@override
Widget build(BuildContext context) {
final labelList = widget.labels;
final appLocalizations = AppLocalizations.of(context);
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: ListView.builder(
Expanded(child: _buildLabelListView()),
_buildBottomActionButtons(),
],
);
}
Widget _buildLabelListView() {
final labels = widget.labels;
return ListView.builder(
itemCount: labels.length + 1,
padding: EdgeInsets.zero,
itemCount: labelList.length,
itemBuilder: (context, index) {
final label = labelList[index];
return ValueListenableBuilder(
valueListenable: _selectedLabelStateNotifier,
builder: (_, selectedLabels, __) {
final isSelected = selectedLabels.contains(label);
itemBuilder: (_, index) {
if (index == labels.length) {
return _buildCreateLabelButton();
}
return _buildLabelItem(labels[index]);
},
);
}
Widget _buildLabelItem(Label label) {
return ValueListenableBuilder<Set<Id>>(
valueListenable: _selectedIdsNotifier,
builder: (_, selectedIds, __) {
final isSelected = selectedIds.contains(label.id);
return LabelListItem(
label: label,
imagePaths: widget.imagePaths,
padding: const EdgeInsetsDirectional.only(
start: 4,
end: 16,
),
padding: const EdgeInsetsDirectional.only(start: 4, end: 16),
enableSelectedIcon: true,
isSelected: isSelected,
onOpenLabelCallback: _onToggleLabel,
);
},
);
},
}
Widget _buildCreateLabelButton() {
return Container(
height: 36,
alignment: AlignmentDirectional.centerStart,
margin:
const EdgeInsetsDirectional.only(start: 4, end: 8, top: 4, bottom: 8),
child: ConfirmDialogButton(
label: AppLocalizations.of(context).createALabel,
backgroundColor: Colors.transparent,
textColor: AppColor.primaryMain,
padding: const EdgeInsetsDirectional.only(start: 10, end: 16),
icon: widget.imagePaths.icAddIdentity,
onTapAction: widget.onCreateALabelAction,
),
),
ValueListenableBuilder(
valueListenable: _addLabelStateNotifier,
builder: (_, value, __) {
);
}
Widget _buildBottomActionButtons() {
final localizations = AppLocalizations.of(context);
return ValueListenableBuilder<Set<Id>>(
valueListenable: _selectedIdsNotifier,
builder: (_, selectedIds, __) {
return ModalListActionButtonWidget(
positiveLabel: appLocalizations.addLabel,
negativeLabel: appLocalizations.cancel,
padding: const EdgeInsets.symmetric(
vertical: 25,
horizontal: 25,
),
isPositiveActionEnabled: value,
onPositiveAction: _onAddLabel,
positiveLabel: localizations.addLabel,
negativeLabel: localizations.cancel,
padding: const EdgeInsets.all(25),
isPositiveActionEnabled: selectedIds.isNotEmpty,
onPositiveAction: _onConfirmAction,
onNegativeAction: widget.onCloseModal,
);
},
),
],
);
}
void _onToggleLabel(Label selectedLabel) {
final currentLabels = _selectedLabelStateNotifier.value;
if (currentLabels.contains(selectedLabel)) {
_selectedLabelStateNotifier.value =
currentLabels.where((label) => label.id != selectedLabel.id).toList();
void _onToggleLabel(Label label) {
final currentIds = Set<Id>.from(_selectedIdsNotifier.value);
final labelId = label.id;
if (labelId == null) {
logWarning('ListLabelWithActionModal::_onToggleLabel: labelId is null for label: $label');
return;
}
if (currentIds.contains(labelId)) {
currentIds.remove(labelId);
} else {
_selectedLabelStateNotifier.value = [...currentLabels, selectedLabel];
currentIds.add(labelId);
}
_selectedIdsNotifier.value = currentIds;
}
_addLabelStateNotifier.value = _selectedLabelStateNotifier.value.isNotEmpty;
}
void _onConfirmAction() {
final selectedLabels = widget.labels
.where((label) => _selectedIdsNotifier.value.contains(label.id))
.toList();
void _onAddLabel() {
widget.onLabelAsToEmailsAction(_selectedLabelStateNotifier.value);
widget.onLabelAsToEmailsAction(selectedLabels);
widget.onCloseModal();
}
@override
void dispose() {
_addLabelStateNotifier.dispose();
_selectedLabelStateNotifier.dispose();
_selectedIdsNotifier.dispose();
super.dispose();
}
}
@@ -1,14 +1,15 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:core/presentation/views/dialog/confirm_dialog_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class NoLabelYetWidget extends StatelessWidget {
final ImagePaths imagePaths;
final VoidCallback? onCreateLabel;
final AsyncCallback? onCreateLabel;
const NoLabelYetWidget({
super.key,
@@ -20,11 +21,12 @@ class NoLabelYetWidget extends StatelessWidget {
Widget build(BuildContext context) {
final appLocalizations = AppLocalizations.of(context);
return Container(
margin: const EdgeInsets.all(16),
constraints: const BoxConstraints(maxWidth: 352),
return Center(
child: SingleChildScrollView(
physics: const ClampingScrollPhysics(),
padding: const EdgeInsets.all(16),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 352),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
@@ -34,8 +36,7 @@ class NoLabelYetWidget extends StatelessWidget {
textStyle: ThemeUtils.textStyleInter600(),
padding: const EdgeInsetsDirectional.only(top: 16),
),
Flexible(
child: _LabelNoTag(
_LabelNoTag(
text: appLocalizations.noLabelsYetMessageDescriptions,
textStyle: ThemeUtils.textStyleInter400.copyWith(
fontSize: 16,
@@ -45,7 +46,6 @@ class NoLabelYetWidget extends StatelessWidget {
),
padding: const EdgeInsetsDirectional.only(top: 36),
),
),
if (onCreateLabel != null)
Container(
constraints: const BoxConstraints(minWidth: 186),
@@ -63,6 +63,7 @@ class NoLabelYetWidget extends StatelessWidget {
],
),
),
),
);
}
}