TF-4178 Implement create new label

This commit is contained in:
dab246
2025-12-09 14:34:06 +07:00
committed by Dat H. Pham
parent 38c47ca373
commit 6c959fcf81
12 changed files with 163 additions and 39 deletions
@@ -79,9 +79,13 @@ mixin HandleSetErrorMixin {
}
parseErrorForSetResponse(SetResponse? response, Id requestId) {
final mapError =
response?.notCreated ?? response?.notUpdated ?? response?.notDestroyed;
if (mapError != null && mapError.containsKey(requestId)) {
final mapError = <Id, SetError>{
...?response?.notCreated,
...?response?.notUpdated,
...?response?.notDestroyed,
};
if (mapError.containsKey(requestId)) {
final setError = mapError[requestId];
log('HandleSetErrorMixin::parseErrorForSetResponse():setError: $setError');
if (setError?.type == ErrorMethodResponse.invalidArguments) {
@@ -1,6 +1,8 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart';
import 'package:dartz/dartz.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';
@@ -9,11 +11,15 @@ import 'package:labels/model/label.dart';
import 'package:labels/utils/labels_constants.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/labels/domain/state/create_new_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/get_all_label_interactor.dart';
import 'package:tmail_ui_user/features/labels/presentation/label_interactor_bindings.dart';
import 'package:tmail_ui_user/features/labels/presentation/widgets/create_new_label_modal.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 {
@@ -22,6 +28,7 @@ class LabelController extends BaseController {
final isCreateNewLabelModalVisible = RxBool(false);
GetAllLabelInteractor? _getAllLabelInteractor;
CreateNewLabelInteractor? _createNewLabelInteractor;
bool isLabelCapabilitySupported(Session session, AccountId accountId) {
return LabelsConstants.labelsCapability.isSupported(session, accountId);
@@ -30,6 +37,7 @@ class LabelController extends BaseController {
void injectLabelsBindings() {
LabelInteractorBindings().dependencies();
_getAllLabelInteractor = getBinding<GetAllLabelInteractor>();
_createNewLabelInteractor = getBinding<CreateNewLabelInteractor>();
}
void getAllLabels(AccountId accountId) {
@@ -44,7 +52,7 @@ class LabelController extends BaseController {
: ExpandMode.COLLAPSE;
}
Future<void> openCreateNewLabelModal() async {
Future<void> openCreateNewLabelModal(AccountId? accountId) async {
if (PlatformInfo.isWeb) {
isCreateNewLabelModalVisible.value = true;
}
@@ -52,7 +60,10 @@ class LabelController extends BaseController {
await Get.generalDialog(
barrierDismissible: true,
barrierLabel: 'create-new-label-modal',
pageBuilder: (_, __, ___) => CreateNewLabelModal(labels: labels),
pageBuilder: (_, __, ___) => CreateNewLabelModal(
labels: labels,
onCreateNewLabelCallback: (label) => _createNewLabel(accountId, label),
),
).whenComplete(() {
if (PlatformInfo.isWeb) {
isCreateNewLabelModalVisible.value = false;
@@ -60,10 +71,41 @@ class LabelController extends BaseController {
});
}
void _createNewLabel(AccountId? accountId, Label label) {
log('LabelController::_createNewLabel:Label: $label');
if (accountId == null) {
consumeState(
Stream.value(Left(CreateNewLabelFailure(NotFoundAccountIdException()))),
);
} else if (_createNewLabelInteractor == null) {
consumeState(
Stream.value(Left(CreateNewLabelFailure(InteractorNotInitialized()))),
);
} else {
consumeState(_createNewLabelInteractor!.execute(accountId, label));
}
}
void _handleCreateNewLabelSuccess(CreateNewLabelSuccess success) {
toastManager.showMessageSuccess(success);
_addLabelToList(success.newLabel);
}
void _handleCreateNewLabelFailure(CreateNewLabelFailure failure) {
toastManager.showMessageFailure(failure);
}
void _addLabelToList(Label newLabel) {
labels.add(newLabel);
labels.sortByAlphabetically();
}
@override
void handleSuccessViewState(Success success) {
if (success is GetAllLabelSuccess) {
labels.value = success.labels..sortByAlphabetically();
} else if (success is CreateNewLabelSuccess) {
_handleCreateNewLabelSuccess(success);
} else {
super.handleSuccessViewState(success);
}
@@ -73,6 +115,8 @@ class LabelController extends BaseController {
void handleFailureViewState(Failure failure) {
if (failure is GetAllLabelFailure) {
labels.value = [];
} else if (failure is CreateNewLabelFailure) {
_handleCreateNewLabelFailure(failure);
} else {
super.handleFailureViewState(failure);
}
@@ -81,6 +125,7 @@ class LabelController extends BaseController {
@override
void onClose() {
_getAllLabelInteractor = null;
_createNewLabelInteractor = null;
super.onClose();
}
}
@@ -6,6 +6,7 @@ import 'package:tmail_ui_user/features/labels/data/datasource_impl/label_datasou
import 'package:tmail_ui_user/features/labels/data/network/label_api.dart';
import 'package:tmail_ui_user/features/labels/data/repository/label_repository_impl.dart';
import 'package:tmail_ui_user/features/labels/domain/repository/label_repository.dart';
import 'package:tmail_ui_user/features/labels/domain/usecases/create_new_label_interactor.dart';
import 'package:tmail_ui_user/features/labels/domain/usecases/get_all_label_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart';
import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart';
@@ -31,6 +32,7 @@ class LabelInteractorBindings extends InteractorsBindings {
@override
void bindingsInteractor() {
Get.lazyPut(() => GetAllLabelInteractor(Get.find<LabelRepository>()));
Get.lazyPut(() => CreateNewLabelInteractor(Get.find<LabelRepository>()));
Get.lazyPut(() => VerifyNameInteractor());
}
@@ -11,13 +11,11 @@ import 'package:core/presentation/views/dialog/modal_list_action_button_widget.d
import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:labels/extensions/list_label_extension.dart';
import 'package:labels/model/label.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/mailbox_creator/domain/model/verification/duplicate_name_validator.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/empty_name_validator.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/name_with_space_only_validator.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/special_character_validator.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/validator.dart';
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';
@@ -25,10 +23,17 @@ import 'package:tmail_ui_user/features/mailbox_creator/presentation/extensions/v
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
typedef OnCreateNewLabelCallback = Function(Label label);
class CreateNewLabelModal extends StatefulWidget {
final List<Label> labels;
final OnCreateNewLabelCallback onCreateNewLabelCallback;
const CreateNewLabelModal({super.key, required this.labels});
const CreateNewLabelModal({
super.key,
required this.labels,
required this.onCreateNewLabelCallback,
});
@override
State<CreateNewLabelModal> createState() => _CreateNewLabelModalState();
@@ -41,6 +46,7 @@ class _CreateNewLabelModalState extends State<CreateNewLabelModal> {
final ValueNotifier<String?> _labelNameErrorTextNotifier =
ValueNotifier(null);
final ValueNotifier<Color?> _labelSelectedColorNotifier = ValueNotifier(null);
final ValueNotifier<bool> _createLabelStateNotifier = ValueNotifier(false);
final TextEditingController _nameInputController = TextEditingController();
final FocusNode _nameInputFocusNode = FocusNode();
@@ -203,12 +209,20 @@ class _CreateNewLabelModalState extends State<CreateNewLabelModal> {
),
),
),
ModalListActionButtonWidget(
positiveLabel: appLocalizations.createLabel,
negativeLabel: appLocalizations.cancel,
padding: const EdgeInsets.symmetric(vertical: 25),
onPositiveAction: _onCreateNewLabel,
onNegativeAction: _onCloseModal,
ValueListenableBuilder(
valueListenable: _createLabelStateNotifier,
builder: (_, value, __) {
return ModalListActionButtonWidget(
positiveLabel: appLocalizations.createLabel,
negativeLabel: appLocalizations.cancel,
padding: const EdgeInsets.symmetric(
vertical: 25,
),
isPositiveActionEnabled: value,
onPositiveAction: _onCreateNewLabel,
onNegativeAction: _onCloseModal,
);
},
),
],
),
@@ -248,10 +262,9 @@ class _CreateNewLabelModalState extends State<CreateNewLabelModal> {
AppLocalizations appLocalizations,
String value,
) {
_labelNameErrorTextNotifier.value = _verifyLabelName(
appLocalizations,
value,
);
final errorText = _verifyLabelName(appLocalizations, value);
_labelNameErrorTextNotifier.value = errorText;
_createLabelStateNotifier.value = errorText == null;
}
String? _verifyLabelName(AppLocalizations appLocalizations, String value) {
@@ -272,7 +285,6 @@ class _CreateNewLabelModalState extends State<CreateNewLabelModal> {
EmptyNameValidator(),
NameWithSpaceOnlyValidator(),
DuplicateNameValidator(_labelDisplayNameList),
SpecialCharacterValidator(),
];
void _clearInputFocus() {
@@ -281,6 +293,15 @@ class _CreateNewLabelModalState extends State<CreateNewLabelModal> {
void _onCreateNewLabel() {
_clearInputFocus();
final newLabel = Label(
displayName: _nameInputController.text,
color: _selectedColor != null
? HexColor(_selectedColor!.toHexTriplet())
: null,
);
widget.onCreateNewLabelCallback(newLabel);
popBack();
}
@@ -318,6 +339,7 @@ class _CreateNewLabelModalState extends State<CreateNewLabelModal> {
_nameInputController.dispose();
_labelNameErrorTextNotifier.dispose();
_labelSelectedColorNotifier.dispose();
_createLabelStateNotifier.dispose();
_labelDisplayNameList = [];
super.dispose();
}
@@ -350,6 +350,7 @@ abstract class BaseMailboxView extends GetWidget<MailboxController>
final labelController =
controller.mailboxDashBoardController.labelController;
final accountId = controller.accountId;
final labelListExpandMode = labelController.labelListExpandMode.value;
final countLabels = labelController.labels.length;
@@ -364,7 +365,8 @@ abstract class BaseMailboxView extends GetWidget<MailboxController>
expandMode: labelListExpandMode,
countLabels: countLabels,
onToggleLabelListState: labelController.toggleLabelListState,
onAddNewLabel: labelController.openCreateNewLabelModal,
onAddNewLabel: () =>
labelController.openCreateNewLabelModal(accountId),
);
} else {
return const SizedBox.shrink();
@@ -54,8 +54,6 @@ extension ValicatorFailureExtension on VerifyNameFailure {
return appLocalizations.this_field_cannot_be_blank;
} else if (exception is DuplicatedNameException) {
return appLocalizations.tagNameAlreadyExists;
} else if (exception is SpecialCharacterException) {
return appLocalizations.tagNameCannotContainSpecialCharacters;
} else if (exception is NameWithSpaceOnlyException) {
return appLocalizations.thisFieldCannotContainOnlySpaces;
} else {