[Part 2] TF-4308 Add Create new label button in ChooseLabelModal (#4457)
This commit is contained in:
@@ -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',
|
||||
);
|
||||
|
||||
+2
-24
@@ -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,84 +33,80 @@ 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(
|
||||
color: Colors.white,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.08),
|
||||
blurRadius: 24,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.08),
|
||||
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: Container(
|
||||
width: width,
|
||||
height: height,
|
||||
decoration: _buildModalDecoration(),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Stack(children: [_buildBodyContent(), _buildCloseButton()]),
|
||||
),
|
||||
);
|
||||
|
||||
return Center(child: bodyWidget);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildTitle(AppLocalizations appLocalizations) {
|
||||
BoxDecoration _buildModalDecoration() {
|
||||
return BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.08),
|
||||
blurRadius: 24,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.08),
|
||||
blurRadius: 2,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: labelList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final label = labelList[index];
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: _selectedLabelStateNotifier,
|
||||
builder: (_, selectedLabels, __) {
|
||||
final isSelected = selectedLabels.contains(label);
|
||||
return LabelListItem(
|
||||
label: label,
|
||||
imagePaths: widget.imagePaths,
|
||||
padding: const EdgeInsetsDirectional.only(
|
||||
start: 4,
|
||||
end: 16,
|
||||
),
|
||||
enableSelectedIcon: true,
|
||||
isSelected: isSelected,
|
||||
onOpenLabelCallback: _onToggleLabel,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: _addLabelStateNotifier,
|
||||
builder: (_, value, __) {
|
||||
return ModalListActionButtonWidget(
|
||||
positiveLabel: appLocalizations.addLabel,
|
||||
negativeLabel: appLocalizations.cancel,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 25,
|
||||
horizontal: 25,
|
||||
),
|
||||
isPositiveActionEnabled: value,
|
||||
onPositiveAction: _onAddLabel,
|
||||
onNegativeAction: widget.onCloseModal,
|
||||
);
|
||||
},
|
||||
),
|
||||
Expanded(child: _buildLabelListView()),
|
||||
_buildBottomActionButtons(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _onToggleLabel(Label selectedLabel) {
|
||||
final currentLabels = _selectedLabelStateNotifier.value;
|
||||
|
||||
if (currentLabels.contains(selectedLabel)) {
|
||||
_selectedLabelStateNotifier.value =
|
||||
currentLabels.where((label) => label.id != selectedLabel.id).toList();
|
||||
} else {
|
||||
_selectedLabelStateNotifier.value = [...currentLabels, selectedLabel];
|
||||
}
|
||||
|
||||
_addLabelStateNotifier.value = _selectedLabelStateNotifier.value.isNotEmpty;
|
||||
Widget _buildLabelListView() {
|
||||
final labels = widget.labels;
|
||||
return ListView.builder(
|
||||
itemCount: labels.length + 1,
|
||||
padding: EdgeInsets.zero,
|
||||
itemBuilder: (_, index) {
|
||||
if (index == labels.length) {
|
||||
return _buildCreateLabelButton();
|
||||
}
|
||||
return _buildLabelItem(labels[index]);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _onAddLabel() {
|
||||
widget.onLabelAsToEmailsAction(_selectedLabelStateNotifier.value);
|
||||
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),
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBottomActionButtons() {
|
||||
final localizations = AppLocalizations.of(context);
|
||||
return ValueListenableBuilder<Set<Id>>(
|
||||
valueListenable: _selectedIdsNotifier,
|
||||
builder: (_, selectedIds, __) {
|
||||
return ModalListActionButtonWidget(
|
||||
positiveLabel: localizations.addLabel,
|
||||
negativeLabel: localizations.cancel,
|
||||
padding: const EdgeInsets.all(25),
|
||||
isPositiveActionEnabled: selectedIds.isNotEmpty,
|
||||
onPositiveAction: _onConfirmAction,
|
||||
onNegativeAction: widget.onCloseModal,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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 {
|
||||
currentIds.add(labelId);
|
||||
}
|
||||
_selectedIdsNotifier.value = currentIds;
|
||||
}
|
||||
|
||||
void _onConfirmAction() {
|
||||
final selectedLabels = widget.labels
|
||||
.where((label) => _selectedIdsNotifier.value.contains(label.id))
|
||||
.toList();
|
||||
|
||||
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,22 +21,22 @@ 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(),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_IconNoTag(icon: imagePaths.icNoTag),
|
||||
_LabelNoTag(
|
||||
text: appLocalizations.noLabelsYet,
|
||||
textStyle: ThemeUtils.textStyleInter600(),
|
||||
padding: const EdgeInsetsDirectional.only(top: 16),
|
||||
),
|
||||
Flexible(
|
||||
child: _LabelNoTag(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 352),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_IconNoTag(icon: imagePaths.icNoTag),
|
||||
_LabelNoTag(
|
||||
text: appLocalizations.noLabelsYet,
|
||||
textStyle: ThemeUtils.textStyleInter600(),
|
||||
padding: const EdgeInsetsDirectional.only(top: 16),
|
||||
),
|
||||
_LabelNoTag(
|
||||
text: appLocalizations.noLabelsYetMessageDescriptions,
|
||||
textStyle: ThemeUtils.textStyleInter400.copyWith(
|
||||
fontSize: 16,
|
||||
@@ -45,22 +46,22 @@ class NoLabelYetWidget extends StatelessWidget {
|
||||
),
|
||||
padding: const EdgeInsetsDirectional.only(top: 36),
|
||||
),
|
||||
),
|
||||
if (onCreateLabel != null)
|
||||
Container(
|
||||
constraints: const BoxConstraints(minWidth: 186),
|
||||
height: 48,
|
||||
margin: const EdgeInsetsDirectional.only(top: 16),
|
||||
child: ConfirmDialogButton(
|
||||
label: appLocalizations.createALabel,
|
||||
backgroundColor: Colors.white,
|
||||
textColor: AppColor.primaryMain,
|
||||
borderColor: AppColor.primaryMain,
|
||||
icon: imagePaths.icAddIdentity,
|
||||
onTapAction: onCreateLabel!,
|
||||
if (onCreateLabel != null)
|
||||
Container(
|
||||
constraints: const BoxConstraints(minWidth: 186),
|
||||
height: 48,
|
||||
margin: const EdgeInsetsDirectional.only(top: 16),
|
||||
child: ConfirmDialogButton(
|
||||
label: appLocalizations.createALabel,
|
||||
backgroundColor: Colors.white,
|
||||
textColor: AppColor.primaryMain,
|
||||
borderColor: AppColor.primaryMain,
|
||||
icon: imagePaths.icAddIdentity,
|
||||
onTapAction: onCreateLabel!,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user