TF-527 Add presentation layer in add new identity

This commit is contained in:
dab246
2022-05-05 09:10:32 +07:00
committed by Dat H. Pham
parent 32f99fcca2
commit 36e33b0866
19 changed files with 1030 additions and 57 deletions
-1
View File
@@ -42,7 +42,6 @@ export 'presentation/views/button/button_builder.dart';
export 'presentation/views/button/icon_button_web.dart';
export 'presentation/views/image/avatar_builder.dart';
export 'presentation/views/list/sliver_grid_delegate_fixed_height.dart';
export 'presentation/views/text/text_form_field_builder.dart';
export 'presentation/views/image/icon_builder.dart';
export 'presentation/views/context_menu/context_menu_action_builder.dart';
export 'presentation/views/context_menu/context_menu_builder.dart';
@@ -96,6 +96,7 @@ Widget buildTextButton(String text, {
double? width,
double? height,
Color? backgroundColor,
EdgeInsets? padding,
double? radius,
IconWebCallback? onTap,
}) {
@@ -106,6 +107,8 @@ Widget buildTextButton(String text, {
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith((states) => backgroundColor ?? AppColor.colorTextButton),
elevation: MaterialStateProperty.resolveWith((states) => 0),
padding: MaterialStateProperty.resolveWith<EdgeInsets>(
(Set<MaterialState> states) => padding ?? EdgeInsets.zero),
shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(radius ?? 0)))),
child: Text(text, style: textStyle ?? TextStyle(fontSize: 17, color: Colors.white, fontWeight: FontWeight.w500)),
onPressed: () => onTap?.call()
@@ -1,53 +0,0 @@
import 'package:flutter/material.dart';
typedef OnTapActionClick = void Function();
class TextFormFieldBuilder {
Key? _key;
ValueChanged<String>? _onTextChange;
InputDecoration? _inputDecoration;
TextInputAction? _textInputAction;
TextStyle? _textStyle;
OnTapActionClick? _onTapActionClick;
void key(Key key) {
_key = key;
}
void onChange(ValueChanged<String> onChange) {
_onTextChange = onChange;
}
void textStyle(TextStyle style) {
_textStyle = style;
}
void textDecoration(InputDecoration inputDecoration) {
_inputDecoration = inputDecoration;
}
void textInputAction(TextInputAction inputAction) {
_textInputAction = inputAction;
}
void addOnTapActionClick(OnTapActionClick onTapActionClick) {
_onTapActionClick = onTapActionClick;
}
TextFormField build() {
return TextFormField(
key: _key ?? Key('text_form_field_builder'),
keyboardType: TextInputType.multiline,
maxLines: null,
onChanged: _onTextChange,
style: _textStyle,
decoration: _inputDecoration,
textInputAction: _textInputAction,
onTap: () {
if (_onTapActionClick != null) {
_onTapActionClick!();
}
},
);
}
}
@@ -0,0 +1,29 @@
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/base/base_bindings.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/identity_creator_controller.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart';
class IdentityCreatorBindings extends BaseBindings {
@override
void bindingsController() {
Get.lazyPut(() => IdentityCreatorController(Get.find<VerifyNameInteractor>()));
}
@override
void bindingsDataSource() {}
@override
void bindingsDataSourceImpl() {}
@override
void bindingsInteractor() {
Get.lazyPut(() => VerifyNameInteractor());
}
@override
void bindingsRepository() {}
@override
void bindingsRepositoryImpl() {}
}
@@ -0,0 +1,165 @@
import 'package:core/core.dart';
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:html_editor_enhanced/html_editor.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/identities/identity.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/model/identity_creator_arguments.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/model/signature_type.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/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/routes/route_navigation.dart';
class IdentityCreatorController extends BaseController {
final VerifyNameInteractor _verifyNameInteractor;
final noneEmailAddress = EmailAddress(null, 'None');
final signatureType = SignatureType.plainText.obs;
final listEmailAddressDefault = <EmailAddress>[].obs;
final listEmailAddressOfReplyTo = <EmailAddress>[].obs;
final errorNameIdentity = Rxn<String>();
final emailOfIdentity = Rxn<EmailAddress>();
final replyToOfIdentity = Rxn<EmailAddress>();
final bccOfIdentity = Rxn<EmailAddress>();
final HtmlEditorController signatureHtmlEditorController = HtmlEditorController(processNewLineAsBr: true);
final TextEditingController signaturePlainEditorController = TextEditingController();
final TextEditingController inputNameIdentityController = TextEditingController();
final FocusNode inputNameIdentityFocusNode = FocusNode();
AccountId? accountId;
UserProfile? userProfile;
String? _nameIdentity;
String? _contentHtmlEditor;
void updateNameIdentity(BuildContext context, String? value) {
_nameIdentity = value;
errorNameIdentity.value = _getErrorInputNameString(context);
}
void updateContentHtmlEditor(String? text) => _contentHtmlEditor = text;
String? get contentHtmlEditor => _contentHtmlEditor;
IdentityCreatorController(this._verifyNameInteractor);
@override
void onReady() {
_getArguments();
_setDefaultValueForIdentity();
super.onReady();
}
@override
void onClose() {
signaturePlainEditorController.dispose();
inputNameIdentityFocusNode.dispose();
inputNameIdentityController.dispose();
super.onClose();
}
@override
void onDone() {
}
@override
void onError(error) {}
void _getArguments() {
final arguments = Get.arguments;
if (arguments is IdentityCreatorArguments) {
accountId = arguments.accountId;
userProfile = arguments.userProfile;
}
}
void _setDefaultValueForIdentity() {
listEmailAddressOfReplyTo.add(noneEmailAddress);
bccOfIdentity.value = noneEmailAddress;
replyToOfIdentity.value = noneEmailAddress;
if (userProfile != null && userProfile?.email.isNotEmpty == true) {
final userEmailAddress = EmailAddress(userProfile!.email, userProfile!.email);
listEmailAddressDefault.add(userEmailAddress);
listEmailAddressOfReplyTo.add(userEmailAddress);
emailOfIdentity.value = userEmailAddress;
}
}
void selectSignatureType(SignatureType newSignatureType) {
signatureType.value = newSignatureType;
}
void updateEmailOfIdentity(EmailAddress? newEmailAddress) {
emailOfIdentity.value = newEmailAddress;
}
void updaterReplyToOfIdentity(EmailAddress? newEmailAddress) {
replyToOfIdentity.value = newEmailAddress;
}
void updateBccOfIdentity(EmailAddress? newEmailAddress) {
bccOfIdentity.value = newEmailAddress;
}
void createNewIdentity(BuildContext context) async {
final error = _getErrorInputNameString(context);
if (error?.isNotEmpty == true) {
log('IdentityCreatorController::createNewIdentity(): error: $error');
errorNameIdentity.value = error;
return;
}
final newIdentity = Identity(
name: _nameIdentity,
email: emailOfIdentity.value?.email,
replyTo: replyToOfIdentity.value != null && replyToOfIdentity.value != noneEmailAddress
? {replyToOfIdentity.value!}
: null,
bcc: bccOfIdentity.value != null && bccOfIdentity.value != noneEmailAddress
? {bccOfIdentity.value!}
: null,
textSignature: Signature(signaturePlainEditorController.text),
htmlSignature: Signature(contentHtmlEditor ?? ''));
log('IdentityCreatorController::createNewIdentity(): $newIdentity');
_clearAll();
FocusScope.of(context).unfocus();
popBack(result: newIdentity);
}
String? _getErrorInputNameString(BuildContext context) {
return _verifyNameInteractor.execute(
_nameIdentity,
[EmptyNameValidator()]
).fold(
(failure) {
if (failure is VerifyNameFailure) {
return failure.getMessageIdentity(context);
} else {
return null;
}
},
(success) => null
);
}
void _clearAll() {
signaturePlainEditorController.clear();
inputNameIdentityController.clear();
}
void closeView(BuildContext context) {
_clearAll();
FocusScope.of(context).unfocus();
popBack();
}
}
@@ -0,0 +1,459 @@
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:html_editor_enhanced/html_editor.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/identity_creator_controller.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/model/signature_type.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_drop_list_field_builder.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_input_field_builder.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class IdentityCreatorView extends GetWidget<IdentityCreatorController> {
final _imagePaths = Get.find<ImagePaths>();
final _responsiveUtils = Get.find<ResponsiveUtils>();
IdentityCreatorView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ResponsiveWidget(
responsiveUtils: _responsiveUtils,
mobile: Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: ClipRRect(
borderRadius: _responsiveUtils.isMobile(context) && _responsiveUtils.isPortrait(context)
? const BorderRadius.only(topRight: Radius.circular(14), topLeft: Radius.circular(14))
: const BorderRadius.all(Radius.zero),
child: _buildBodyMobile(context)
),
)
),
tablet: Scaffold(
backgroundColor: Colors.black38,
body: Align(alignment: Alignment.center, child: Card(
color: Colors.transparent,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(16))),
width: _responsiveUtils.getSizeScreenWidth(context) * 0.9,
height: _responsiveUtils.getSizeScreenHeight(context) * 0.9,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(16)),
child: _buildBodyDesktop(context)
)
)
)
)
),
tabletLarge: Scaffold(
backgroundColor: Colors.black38,
body: Align(alignment: Alignment.center, child: Card(
color: Colors.transparent,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(16))),
width: _responsiveUtils.getSizeScreenWidth(context) * 0.6,
height: _responsiveUtils.getSizeScreenHeight(context) * 0.9,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(16)),
child: _buildBodyDesktop(context)
)
)
)
)
),
desktop: Scaffold(
backgroundColor: Colors.black38,
body: Align(alignment: Alignment.center, child: Card(
color: Colors.transparent,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(16))),
width: _responsiveUtils.getSizeScreenWidth(context) * 0.4,
height: _responsiveUtils.getSizeScreenHeight(context) * 0.9,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(16)),
child: _buildBodyDesktop(context)
)
)
)
)
)
);
}
Widget _buildBodyDesktop(BuildContext context) {
return Stack(
children: [
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Padding(
padding: const EdgeInsets.only(left: 24, top: 24),
child: Text(AppLocalizations.of(context).new_identity.inCaps,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24, color: Colors.black))),
const SizedBox(height: 8),
Expanded(child: SingleChildScrollView(
physics: const ClampingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(children: [
Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
Expanded(child: Obx(() => (IdentityInputFieldBuilder(
context,
_responsiveUtils,
AppLocalizations.of(context).name,
controller.errorNameIdentity.value,
editingController: controller.inputNameIdentityController,
focusNode: controller.inputNameIdentityFocusNode,
isMandatory: true)
..addOnChangeInputNameAction((value) => controller.updateNameIdentity(context, value)))
.build())),
const SizedBox(width: 24),
Expanded(child: Obx(() => (IdentityDropListFieldBuilder(
_imagePaths,
AppLocalizations.of(context).email.inCaps,
controller.emailOfIdentity.value,
controller.listEmailAddressDefault)
..addOnSelectEmailAddressDropListAction((emailAddress) =>
controller.updateEmailOfIdentity(emailAddress)))
.build())),
]),
const SizedBox(height: 24),
Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
Expanded(child: Obx(() => (IdentityDropListFieldBuilder(
_imagePaths,
AppLocalizations.of(context).reply_to_address,
controller.replyToOfIdentity.value,
controller.listEmailAddressOfReplyTo)
..addOnSelectEmailAddressDropListAction((emailAddress) =>
controller.updaterReplyToOfIdentity(emailAddress)))
.build())),
const SizedBox(width: 24),
Expanded(child: Obx(() => (IdentityDropListFieldBuilder(
_imagePaths,
AppLocalizations.of(context).bcc_to_address,
controller.bccOfIdentity.value,
controller.listEmailAddressOfReplyTo)
..addOnSelectEmailAddressDropListAction((emailAddress) =>
controller.updateBccOfIdentity(emailAddress)))
.build())),
]),
const SizedBox(height: 32),
Row(children: [
Text(AppLocalizations.of(context).signature,
style: const TextStyle(
fontWeight: FontWeight.normal,
fontSize: 14,
color: AppColor.colorContentEmail)),
const Spacer(),
Obx(() => buildTextButton(
AppLocalizations.of(context).plain_text,
textStyle: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 14,
color: controller.signatureType.value == SignatureType.plainText
? AppColor.colorContentEmail
: AppColor.colorHintSearchBar),
backgroundColor: controller.signatureType.value == SignatureType.plainText
? AppColor.emailAddressChipColor
: Colors.transparent,
width: 85,
height: 30,
radius: 10,
onTap: () => controller.selectSignatureType(SignatureType.plainText))),
const SizedBox(width: 10),
Obx(() => buildTextButton(
AppLocalizations.of(context).html_template,
textStyle: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 14,
color: controller.signatureType.value == SignatureType.htmlTemplate
? AppColor.colorContentEmail
: AppColor.colorHintSearchBar),
backgroundColor: controller.signatureType.value == SignatureType.htmlTemplate
? AppColor.emailAddressChipColor
: Colors.transparent,
width: 110,
height: 30,
radius: 10,
onTap: () => controller.selectSignatureType(SignatureType.htmlTemplate))),
]),
const SizedBox(height: 8),
Obx(() => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: AppColor.colorInputBorderCreateMailbox),
color: Colors.white),
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
child: Stack(
children: [
if (controller.signatureType.value == SignatureType.plainText)
SizedBox(
height: 230,
child: (TextFieldBuilder()
..key(const Key('signature_plain_text_editor'),)
..cursorColor(Colors.black)
..addController(controller.signaturePlainEditorController)
..textStyle(const TextStyle(
fontWeight: FontWeight.normal,
color: Colors.black,
fontSize: 16))
..maxLines(null))
.build())
else
HtmlEditor(
key: const Key('signature_html_editor'),
controller: controller.signatureHtmlEditorController,
htmlEditorOptions: const HtmlEditorOptions(
hint: '',
darkMode: false,
),
blockQuotedContent: controller.contentHtmlEditor ?? '<p></p>',
htmlToolbarOptions: const HtmlToolbarOptions(
toolbarPosition: ToolbarPosition.custom,
),
otherOptions: const OtherOptions(height: 230),
callbacks: Callbacks(onInit: () {
controller.signatureHtmlEditorController.setFullScreen();
}, onChangeContent: (String? changed) {
controller.updateContentHtmlEditor(changed);
}, onFocus: () {
FocusManager.instance.primaryFocus?.unfocus();
Future.delayed(const Duration(milliseconds: 500), () {
controller.signatureHtmlEditorController.setFocus();
});
}),
)
]
),
))
]),
),
)),
Container(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24),
alignment: Alignment.centerRight,
color: Colors.white,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
buildTextButton(
AppLocalizations.of(context).cancel,
textStyle: const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 17,
color: AppColor.colorTextButton),
backgroundColor: AppColor.emailAddressChipColor,
width: 128,
height: 44,
radius: 10,
onTap: () => controller.closeView(context)),
const SizedBox(width: 12),
buildTextButton(
AppLocalizations.of(context).create,
width: 128,
height: 44,
radius: 10,
onTap: () => controller.createNewIdentity(context)),
]
),
)
]),
Positioned(top: 8, right: 8,
child: buildIconWeb(
icon: SvgPicture.asset(_imagePaths.icCloseMailbox, fit: BoxFit.fill),
tooltip: AppLocalizations.of(context).close,
onTap: () => controller.closeView(context)))
]
);
}
Widget _buildBodyMobile(BuildContext context) {
return Stack(
children: [
Column(children: [
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text(AppLocalizations.of(context).new_identity.inCaps,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 21, color: Colors.black))),
const SizedBox(height: 8),
Expanded(child: SingleChildScrollView(
physics: const ClampingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(children: [
Obx(() => (IdentityInputFieldBuilder(
context,
_responsiveUtils,
AppLocalizations.of(context).name,
controller.errorNameIdentity.value,
editingController: controller.inputNameIdentityController,
focusNode: controller.inputNameIdentityFocusNode,
isMandatory: true)
..addOnChangeInputNameAction((value) => controller.updateNameIdentity(context, value)))
.build()),
const SizedBox(height: 24),
Obx(() => (IdentityDropListFieldBuilder(
_imagePaths,
AppLocalizations.of(context).email.inCaps,
controller.emailOfIdentity.value,
controller.listEmailAddressDefault)
..addOnSelectEmailAddressDropListAction((emailAddress) =>
controller.updateEmailOfIdentity(emailAddress)))
.build()),
const SizedBox(height: 24),
Obx(() => (IdentityDropListFieldBuilder(
_imagePaths,
AppLocalizations.of(context).reply_to_address,
controller.replyToOfIdentity.value,
controller.listEmailAddressOfReplyTo)
..addOnSelectEmailAddressDropListAction((newEmailAddress) =>
controller.updaterReplyToOfIdentity(newEmailAddress)))
.build()),
const SizedBox(height: 24),
Obx(() => (IdentityDropListFieldBuilder(
_imagePaths,
AppLocalizations.of(context).bcc_to_address,
controller.bccOfIdentity.value,
controller.listEmailAddressOfReplyTo)
..addOnSelectEmailAddressDropListAction((newEmailAddress) =>
controller.updateBccOfIdentity(newEmailAddress)))
.build()),
const SizedBox(height: 32),
Text(AppLocalizations.of(context).signature,
style: const TextStyle(
fontWeight: FontWeight.normal,
fontSize: 14,
color: AppColor.colorContentEmail)),
const SizedBox(height: 8),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Obx(() => buildTextButton(
AppLocalizations.of(context).plain_text,
textStyle: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 14,
color: controller.signatureType.value == SignatureType.plainText
? AppColor.colorContentEmail
: AppColor.colorHintSearchBar),
backgroundColor: controller.signatureType.value == SignatureType.plainText
? AppColor.emailAddressChipColor
: Colors.transparent,
width: 85,
height: 30,
radius: 10,
onTap: () => controller.selectSignatureType(SignatureType.plainText))),
const SizedBox(width: 10),
Obx(() => buildTextButton(
AppLocalizations.of(context).html_template,
textStyle: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 14,
color: controller.signatureType.value == SignatureType.htmlTemplate
? AppColor.colorContentEmail
: AppColor.colorHintSearchBar),
backgroundColor: controller.signatureType.value == SignatureType.htmlTemplate
? AppColor.emailAddressChipColor
: Colors.transparent,
width: 110,
height: 30,
radius: 10,
onTap: () => controller.selectSignatureType(SignatureType.htmlTemplate))),
]),
const SizedBox(height: 8),
Obx(() => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: AppColor.colorInputBorderCreateMailbox),
color: Colors.white),
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
child: Stack(
children: [
if (controller.signatureType.value == SignatureType.plainText)
SizedBox(
height: 230,
child: (TextFieldBuilder()
..key(const Key('signature_plain_text_editor'),)
..cursorColor(Colors.black)
..addController(controller.signaturePlainEditorController)
..textStyle(const TextStyle(
fontWeight: FontWeight.normal,
color: Colors.black,
fontSize: 16))
..maxLines(null))
.build())
else
HtmlEditor(
key: const Key('signature_html_editor'),
controller: controller.signatureHtmlEditorController,
htmlEditorOptions: const HtmlEditorOptions(
hint: '',
darkMode: false,
),
blockQuotedContent: controller.contentHtmlEditor ?? '<p></p>',
htmlToolbarOptions: const HtmlToolbarOptions(
toolbarPosition: ToolbarPosition.custom,
),
otherOptions: const OtherOptions(height: 230),
callbacks: Callbacks(onInit: () {
controller.signatureHtmlEditorController.setFullScreen();
}, onChangeContent: (String? changed) {
controller.updateContentHtmlEditor(changed);
}, onFocus: () {
FocusManager.instance.primaryFocus?.unfocus();
Future.delayed(const Duration(milliseconds: 500), () {
controller.signatureHtmlEditorController.setFocus();
});
}),
)
]
),
)),
const SizedBox(height: 24),
Container(
alignment: Alignment.centerRight,
color: Colors.white,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
buildTextButton(
AppLocalizations.of(context).cancel,
textStyle: const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 17,
color: AppColor.colorTextButton),
backgroundColor: AppColor.emailAddressChipColor,
width: 128,
height: 44,
radius: 10,
onTap: () => controller.closeView(context)),
const SizedBox(width: 12),
buildTextButton(
AppLocalizations.of(context).create,
width: 128,
height: 44,
radius: 10,
onTap: () => controller.createNewIdentity(context)),
]
),
)
]),
),
)),
]),
Positioned(top: 8, right: 8,
child: buildIconWeb(
icon: SvgPicture.asset(_imagePaths.icCloseMailbox, fit: BoxFit.fill),
tooltip: AppLocalizations.of(context).close,
onTap: () => controller.closeView(context)))
]
);
}
}
@@ -0,0 +1,14 @@
import 'package:equatable/equatable.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:model/model.dart';
class IdentityCreatorArguments with EquatableMixin {
final AccountId accountId;
final UserProfile userProfile;
IdentityCreatorArguments(this.accountId, this.userProfile);
@override
List<Object?> get props => [accountId, userProfile];
}
@@ -0,0 +1,5 @@
enum SignatureType {
plainText,
htmlTemplate
}
@@ -0,0 +1,82 @@
import 'package:core/core.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
typedef OnSelectEmailAddressDropListAction = Function(EmailAddress? emailAddress);
class IdentityDropListFieldBuilder {
final ImagePaths _imagePaths;
final String _label;
final EmailAddress? _emailAddressSelected;
final List<EmailAddress> _listEmailAddress;
OnSelectEmailAddressDropListAction? onSelectItemDropList;
IdentityDropListFieldBuilder(
this._imagePaths,
this._label,
this._emailAddressSelected,
this._listEmailAddress,
);
void addOnSelectEmailAddressDropListAction(OnSelectEmailAddressDropListAction action) {
onSelectItemDropList = action;
}
Widget build() {
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(_label, style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: AppColor.colorContentEmail)),
const SizedBox(height: 8),
DropdownButtonHideUnderline(
child: DropdownButton2<EmailAddress>(
isExpanded: true,
hint: Row(
children: [
Expanded(child: Text(
_emailAddressSelected?.email ?? '',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.normal, color: Colors.black),
maxLines: 1,
overflow: BuildUtils.isWeb ? null : TextOverflow.ellipsis,
)),
],
),
items: _listEmailAddress.map((item) => DropdownMenuItem<EmailAddress>(
value: item,
child: Text(
item.email ?? '',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.normal, color: Colors.black),
maxLines: 1,
overflow: BuildUtils.isWeb ? null : TextOverflow.ellipsis,
),
)).toList(),
value: _emailAddressSelected,
onChanged: (newEmailAddress) => onSelectItemDropList?.call(newEmailAddress),
icon: SvgPicture.asset(_imagePaths.icDropDown),
buttonPadding: const EdgeInsets.symmetric(horizontal: 12),
buttonDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: AppColor.colorInputBorderCreateMailbox, width: 0.5),
color: AppColor.colorInputBackgroundCreateMailbox),
itemHeight: 44,
buttonHeight: 44,
selectedItemHighlightColor: Colors.black12,
itemPadding: const EdgeInsets.symmetric(horizontal: 12),
dropdownMaxHeight: 200,
dropdownDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.white),
dropdownElevation: 4,
scrollbarRadius: const Radius.circular(40),
scrollbarThickness: 6,
),
)
]);
}
}
@@ -0,0 +1,37 @@
import 'package:core/core.dart';
import 'package:flutter/material.dart';
class IdentityInputDecorationBuilder extends InputDecorationBuilder {
@override
InputDecoration build() {
return InputDecoration(
enabledBorder: enabledBorder ?? const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
borderSide: BorderSide(width: 1, color: AppColor.colorInputBorderCreateMailbox)),
focusedBorder: enabledBorder ?? const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
borderSide: BorderSide(width: 1, color: AppColor.colorTextButton)),
errorBorder: errorBorder ?? const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
borderSide: BorderSide(width: 1, color: AppColor.colorInputBorderErrorVerifyName)),
focusedErrorBorder: errorBorder ?? const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
borderSide: BorderSide(width: 1, color: AppColor.colorInputBorderErrorVerifyName)),
prefixText: prefixText,
labelText: labelText,
floatingLabelBehavior: FloatingLabelBehavior.never,
labelStyle: labelStyle ?? const TextStyle(color: Colors.black, fontSize: 16),
hintText: hintText,
isDense: true,
hintStyle: hintStyle ?? const TextStyle(color: AppColor.colorHintInputCreateMailbox, fontSize: 16),
contentPadding: contentPadding ?? const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
errorText: errorText,
errorStyle: errorTextStyle ?? const TextStyle(color: AppColor.colorInputBorderErrorVerifyName, fontSize: 13),
filled: true,
fillColor: errorText?.isNotEmpty == true
? AppColor.colorInputBackgroundErrorVerifyName
: AppColor.colorInputBackgroundCreateMailbox);
}
}
@@ -0,0 +1,59 @@
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_input_decoration_builder.dart';
typedef OnChangeInputNameAction = Function(String? value);
class IdentityInputFieldBuilder {
final BuildContext _context;
final ResponsiveUtils _responsiveUtils;
final String _label;
final String? _error;
final TextEditingController? editingController;
final FocusNode? focusNode;
final TextInputType? inputType;
final bool isMandatory;
OnChangeInputNameAction? onChangeInputNameAction;
IdentityInputFieldBuilder(
this._context,
this._responsiveUtils,
this._label,
this._error, {
this.isMandatory = false,
this.editingController,
this.focusNode,
this.inputType
});
void addOnChangeInputNameAction(OnChangeInputNameAction action) {
onChangeInputNameAction = action;
}
Widget build() {
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(isMandatory ? '$_label*' : _label, style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: AppColor.colorContentEmail)),
const SizedBox(height: 8),
(TextFieldBuilder()
..onChange((value) => onChangeInputNameAction?.call(value))
..textInputAction(TextInputAction.next)
..addController(editingController ?? TextEditingController())
..addFocusNode(focusNode)
..textStyle(const TextStyle(color: Colors.black, fontSize: 16))
..keyboardType(inputType ?? TextInputType.text)
..textDecoration((IdentityInputDecorationBuilder()
..setContentPadding(EdgeInsets.symmetric(
vertical: _responsiveUtils.isDesktop(_context) ? 16 : 12,
horizontal: 12))
..setErrorText(_error))
.build()))
.build()
]);
}
}
@@ -24,4 +24,12 @@ extension ValicatorFailureExtension on VerifyNameFailure {
return '';
}
}
String getMessageIdentity(BuildContext context) {
if (exception is EmptyNameException) {
return AppLocalizations.of(context).this_field_cannot_be_blank;
} else {
return '';
}
}
}
@@ -6,14 +6,17 @@ import 'package:tmail_ui_user/features/manage_account/data/datasource_impl/manag
import 'package:tmail_ui_user/features/manage_account/data/network/manage_account_api.dart';
import 'package:tmail_ui_user/features/manage_account/data/repository/manage_account_repository_impl.dart';
import 'package:tmail_ui_user/features/manage_account/domain/repository/manage_account_repository.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/create_new_identity_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/delete_identity_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/profiles/identities/identities_controller.dart';
import 'package:uuid/uuid.dart';
class IdentitiesBindings extends BaseBindings {
@override
void dependencies() {
Get.lazyPut(() => const Uuid());
Get.lazyPut(() => ManageAccountAPI(Get.find<jmap_http_client.HttpClient>()));
super.dependencies();
}
@@ -22,6 +25,8 @@ class IdentitiesBindings extends BaseBindings {
void bindingsController() {
Get.lazyPut(() => IdentitiesController(
Get.find<GetAllIdentitiesInteractor>(),
Get.find<CreateNewIdentityInteractor>(),
Get.find<Uuid>(),
Get.find<DeleteIdentityInteractor>(),
));
}
@@ -39,6 +44,7 @@ class IdentitiesBindings extends BaseBindings {
@override
void bindingsInteractor() {
Get.lazyPut(() => GetAllIdentitiesInteractor(Get.find<ManageAccountRepository>()));
Get.lazyPut(() => CreateNewIdentityInteractor(Get.find<ManageAccountRepository>()));
Get.lazyPut(() => DeleteIdentityInteractor(Get.find<ManageAccountRepository>()));
}
@@ -4,24 +4,37 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/identities/identity.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/model/identity_creator_arguments.dart';
import 'package:tmail_ui_user/features/manage_account/domain/model/create_new_identity_request.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/create_new_identity_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/delete_identity_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/get_all_identities_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/create_new_identity_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/delete_identity_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/manage_account_dashboard_controller.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/app_routes.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
import 'package:uuid/uuid.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
class IdentitiesController extends BaseController {
final _accountDashBoardController = Get.find<ManageAccountDashBoardController>();
final _appToast = Get.find<AppToast>();
final _imagePaths = Get.find<ImagePaths>();
final _responsiveUtils = Get.find<ResponsiveUtils>();
final _imagePaths = Get.find<ImagePaths>();
final _appToast = Get.find<AppToast>();
final GetAllIdentitiesInteractor _getAllIdentitiesInteractor;
final CreateNewIdentityInteractor _createNewIdentityInteractor;
final Uuid _uuid;
final DeleteIdentityInteractor _deleteIdentityInteractor;
final identitySelected = Rxn<Identity>();
@@ -31,6 +44,11 @@ class IdentitiesController extends BaseController {
this._getAllIdentitiesInteractor,
this._deleteIdentityInteractor,
);
IdentitiesController(
this._getAllIdentitiesInteractor,
this._createNewIdentityInteractor,
this._uuid,
);
@override
void onInit() {
@@ -52,6 +70,8 @@ class IdentitiesController extends BaseController {
listIdentities.value = success.identities ?? [];
setIdentityDefault();
}
} else if (success is CreateNewIdentitySuccess) {
_createNewIdentitySuccess(success);
} else if (success is DeleteIdentitySuccess) {
_deleteIdentitySuccess(success);
}
@@ -89,6 +109,41 @@ class IdentitiesController extends BaseController {
identitySelected.value = newIdentity;
}
void goToCreateNewIdentity() async {
final accountId = _accountDashBoardController.accountId.value;
final userProfile = _accountDashBoardController.userProfile.value;
if (accountId != null && userProfile != null) {
final newIdentity = await push(
AppRoutes.IDENTITY_CREATOR,
arguments: IdentityCreatorArguments(accountId, userProfile));
if (newIdentity != null && newIdentity is Identity) {
final generateCreateId = Id(_uuid.v1());
_createNewIdentityAction(
accountId,
CreateNewIdentityRequest(generateCreateId, newIdentity));
}
}
}
void _createNewIdentityAction(AccountId accountId, CreateNewIdentityRequest identityRequest) async {
consumeState(_createNewIdentityInteractor.execute(accountId, identityRequest));
}
void _createNewIdentitySuccess(CreateNewIdentitySuccess success) {
if (currentOverlayContext != null && currentContext != null) {
_appToast.showToastWithIcon(
currentOverlayContext!,
message: AppLocalizations.of(currentContext!).you_have_created_a_new_identity,
icon: _imagePaths.icSelected);
}
final accountId = _accountDashBoardController.accountId.value;
if (accountId != null) {
_getAllIdentities(accountId);
}
}
void openConfirmationDialogDeleteIdentityAction(BuildContext context, Identity identity) {
popBack();
@@ -53,6 +53,7 @@ class IdentitiesView extends GetWidget<IdentitiesController> with PopupMenuWidge
color: AppColor.colorInputBackgroundCreateMailbox),
itemHeight: 44,
buttonHeight: 44,
selectedItemHighlightColor: Colors.black12,
itemPadding: const EdgeInsets.symmetric(horizontal: 12),
dropdownMaxHeight: 200,
dropdownDecoration: BoxDecoration(
@@ -75,7 +76,7 @@ class IdentitiesView extends GetWidget<IdentitiesController> with PopupMenuWidge
..radiusSplash(10)
..padding(const EdgeInsets.symmetric(vertical: 12))
..textStyle(const TextStyle(fontSize: 17, color: Colors.white, fontWeight: FontWeight.w500))
..onPressActionClick(() => {})
..onPressActionClick(() => controller.goToCreateNewIdentity())
..text(AppLocalizations.of(context).new_identity, isVertical: false))
.build()
]);
@@ -99,7 +100,7 @@ class IdentitiesView extends GetWidget<IdentitiesController> with PopupMenuWidge
floatingActionButton: _responsiveUtils.isMobile(context)
? FloatingActionButton(
key: const Key('add_new_identity'),
onPressed: () => {},
onPressed: () => controller.goToCreateNewIdentity(),
backgroundColor: AppColor.primaryColor,
child: SvgPicture.asset(_imagePaths.icAddIdentity, width: 24, height: 24))
: null),
+49 -1
View File
@@ -1,5 +1,5 @@
{
"@@last_modified": "2022-05-05T15:05:07.334710",
"@@last_modified": "2022-05-05T09:06:54.039059",
"initializing_data": "Initializing data...",
"@initializing_data": {
"type": "text",
@@ -1126,6 +1126,54 @@
"placeholders_order": [],
"placeholders": {}
},
"name": "Name",
"@name": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"reply_to_address": "Reply to address",
"@reply_to_address": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"bcc_to_address": "Bcc to address",
"@bcc_to_address": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"signature": "Signature",
"@signature": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"plain_text": "Plain text",
"@plain_text": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"html_template": "Html template",
"@html_template": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"create": "Create",
"@create": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"you_have_created_a_new_identity": "You have created a new identity",
"@you_have_created_a_new_identity": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"delete_identity": "Delete identity",
"@delete_identity": {
"type": "text",
@@ -1179,6 +1179,54 @@ class AppLocalizations {
name: 'new_identity');
}
String get name {
return Intl.message(
'Name',
name: 'name');
}
String get reply_to_address {
return Intl.message(
'Reply to address',
name: 'reply_to_address');
}
String get bcc_to_address {
return Intl.message(
'Bcc to address',
name: 'bcc_to_address');
}
String get signature {
return Intl.message(
'Signature',
name: 'signature');
}
String get plain_text {
return Intl.message(
'Plain text',
name: 'plain_text');
}
String get html_template {
return Intl.message(
'Html template',
name: 'html_template');
}
String get create {
return Intl.message(
'Create',
name: 'create');
}
String get you_have_created_a_new_identity {
return Intl.message(
'You have created a new identity',
name: 'you_have_created_a_new_identity');
}
String get delete_identity {
return Intl.message('Delete identity',
name: 'delete_identity');
+7
View File
@@ -7,10 +7,12 @@ import 'package:tmail_ui_user/features/destination_picker/presentation/destinati
import 'package:tmail_ui_user/features/email/presentation/email_view.dart' deferred as email;
import 'package:tmail_ui_user/features/home/presentation/home_bindings.dart';
import 'package:tmail_ui_user/features/home/presentation/home_view.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/identity_creator_bindings.dart';
import 'package:tmail_ui_user/features/login/presentation/login_bindings.dart';
import 'package:tmail_ui_user/features/login/presentation/login_view.dart' deferred as login;
import 'package:tmail_ui_user/features/mailbox_creator/presentation/mailbox_creator_bindings.dart';
import 'package:tmail_ui_user/features/mailbox_creator/presentation/mailbox_creator_view.dart' deferred as mailbox_creator;
import 'package:tmail_ui_user/features/identity_creator/presentation/identity_creator_view.dart' deferred as identity_creator;
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_bindings.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_view.dart'
if (dart.library.html) 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart' deferred as mailbox_dashboard;
@@ -61,5 +63,10 @@ class AppPages {
name: AppRoutes.MANAGE_ACCOUNT,
page: () => DeferredWidget(manage_account_dashboard.loadLibrary, () => manage_account_dashboard.ManageAccountDashBoardView()),
binding: ManageAccountDashBoardBindings()),
GetPage(
name: AppRoutes.IDENTITY_CREATOR,
opaque: false,
page: () => DeferredWidget(identity_creator.loadLibrary, () => identity_creator.IdentityCreatorView()),
binding: IdentityCreatorBindings()),
];
}
+1
View File
@@ -10,4 +10,5 @@ abstract class AppRoutes {
static const DESTINATION_PICKER = '/destinationPicker';
static const MAILBOX_CREATOR = '/mailboxCreator';
static const MANAGE_ACCOUNT = '$MAILBOX_DASHBOARD/manage_account';
static const IDENTITY_CREATOR = '/identity_creator';
}