TF-2948 Apply new identity view for web

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-07-30 19:09:33 +07:00
committed by Dat H. Pham
parent 02a7adc64a
commit 23ecdfbf11
61 changed files with 963 additions and 804 deletions
@@ -0,0 +1,46 @@
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/button/tmail_button_widget.dart';
import 'package:flutter/material.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
typedef OnCreateNewIdentityAction = void Function();
class CreateNewIdentityButtonWidget extends StatelessWidget {
final ImagePaths imagePaths;
final OnCreateNewIdentityAction onCreateNewIdentityAction;
final EdgeInsetsGeometry? margin;
const CreateNewIdentityButtonWidget({
super.key,
required this.imagePaths,
required this.onCreateNewIdentityAction,
this.margin,
});
@override
Widget build(BuildContext context) {
return TMailButtonWidget(
text: AppLocalizations.of(context).createNewIdentity,
icon: imagePaths.icAddIdentity,
backgroundColor: AppColor.primaryMain,
borderRadius: 100,
height: 48,
maxWidth: 300,
margin: margin,
textStyle: ThemeUtils.textStyleM3LabelLarge(color: Colors.white),
padding: const EdgeInsetsDirectional.symmetric(
horizontal: 32,
),
iconSize: 16,
iconColor: Colors.white,
iconSpace: 8,
maxLines: 1,
flexibleText: true,
textOverflow: TextOverflow.ellipsis,
mainAxisSize: MainAxisSize.min,
onTapActionCallback: onCreateNewIdentityAction,
);
}
}
@@ -0,0 +1,67 @@
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:core/presentation/views/dialog/confirmation_dialog_builder.dart';
import 'package:core/presentation/views/responsive/responsive_widget.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
typedef OnDeleteIdentityAction = void Function();
class DeleteIdentityDialogBuilder extends StatelessWidget {
final ResponsiveUtils responsiveUtils;
final ImagePaths imagePaths;
final OnDeleteIdentityAction onDeleteIdentityAction;
const DeleteIdentityDialogBuilder({
Key? key,
required this.responsiveUtils,
required this.imagePaths,
required this.onDeleteIdentityAction
}) : super(key: key);
@override
Widget build(BuildContext context) {
final appLocalizations = AppLocalizations.of(context);
final deleteDialogBuilder = ResponsiveWidget(
responsiveUtils: responsiveUtils,
mobile: _buildDeleteDialog(
appLocalizations: appLocalizations,
alignment: Alignment.bottomCenter,
outsideDialogPadding: EdgeInsets.only(bottom: PlatformInfo.isWeb ? 42 : 16),
),
landscapeMobile: _buildDeleteDialog(appLocalizations: appLocalizations),
tablet: _buildDeleteDialog(appLocalizations: appLocalizations),
tabletLarge: _buildDeleteDialog(appLocalizations: appLocalizations),
landscapeTablet: _buildDeleteDialog(appLocalizations: appLocalizations),
desktop: _buildDeleteDialog(appLocalizations: appLocalizations),
);
return PlatformInfo.isWeb
? PointerInterceptor(child: deleteDialogBuilder)
: deleteDialogBuilder;
}
ConfirmationDialogBuilder _buildDeleteDialog({
required AppLocalizations appLocalizations,
Alignment? alignment,
EdgeInsets? outsideDialogPadding,
}) {
return ConfirmationDialogBuilder(
key: const Key('confirm_dialog_delete_identity'),
imagePath: imagePaths,
title: appLocalizations.delete_identity,
textContent: appLocalizations.message_confirmation_dialog_delete_identity,
confirmText: appLocalizations.delete,
cancelText: appLocalizations.cancel,
alignment: alignment,
outsideDialogPadding: outsideDialogPadding,
onConfirmButtonAction: onDeleteIdentityAction,
onCancelButtonAction: popBack,
onCloseButtonAction: popBack,
);
}
}
@@ -0,0 +1,157 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:jmap_dart_client/jmap/identities/identity.dart';
import 'package:model/extensions/list_email_address_extension.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/identity_extension.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/identities/widgets/list_identity_item_actions_widget.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/identities/widgets/signature_builder.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/identities/widgets/signature_loading_widget.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class IdentityListTileBuilder extends StatelessWidget {
const IdentityListTileBuilder({
Key? key,
required this.identity,
required this.mapIdentitySignatures,
required this.signatureViewState,
required this.imagePaths,
required this.onEditIdentityAction,
required this.onDeleteIdentityAction,
this.isDesktop = false,
this.isSelected = false,
this.scrollController,
}) : super(key: key);
final Identity identity;
final Map<IdentityId, String> mapIdentitySignatures;
final Either<Failure, Success> signatureViewState;
final ImagePaths imagePaths;
final bool isSelected;
final bool isDesktop;
final OnEditIdentityAction onEditIdentityAction;
final OnDeleteIdentityAction onDeleteIdentityAction;
final ScrollController? scrollController;
@override
Widget build(BuildContext context) {
final selectedIcon = SvgPicture.asset(
isSelected ? imagePaths.icRadioSelected : imagePaths.icRadio,
width: 18,
height: 18,
fit: BoxFit.fill,
);
final signatureContent = mapIdentitySignatures[identity.id!] ??
identity.signatureAsString;
final identityContent = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
(identity.name ?? ''),
style: ThemeUtils.textStyleBodyBody1(color: Colors.black),
),
),
if (identity.email?.isNotEmpty == true)
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
identity.email ?? '',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: AppColor.steelGray400,
),
),
),
if (identity.replyTo?.isNotEmpty == true)
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
'${AppLocalizations.of(context).reply_to.toUpperCase()}: ${identity.replyTo?.listEmailAddressToString(isFullEmailAddress: true)}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: AppColor.steelGray400,
),
),
),
if (identity.bcc?.isNotEmpty == true)
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
'${AppLocalizations.of(context).bcc_email_address_prefix.toUpperCase()}: ${identity.bcc?.listEmailAddressToString(isFullEmailAddress: true)}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: AppColor.steelGray400,
),
),
),
if (signatureContent.trim().isNotEmpty)
...[
Text(
'--',
style: ThemeUtils.textStyleBodyBody2(
color: Colors.black,
),
),
SignatureLoadingWidget(signatureViewState: signatureViewState),
SignatureBuilder(
value: signatureContent,
width: isDesktop ? 280 : double.infinity,
scrollController: scrollController,
),
],
],
);
if (isDesktop) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
selectedIcon,
Container(
width: 280,
padding: const EdgeInsetsDirectional.only(start: 18, end: 12),
child: identityContent,
),
ListIdentityItemActionsWidget(
identity: identity,
imagePaths: imagePaths,
onEditIdentityAction: onEditIdentityAction,
onDeleteIdentityAction: onDeleteIdentityAction,
isDesktop: isDesktop,
),
const Spacer(),
],
);
} else {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
selectedIcon,
const SizedBox(width: 18),
Expanded(child: identityContent),
],
),
const SizedBox(height: 24),
ListIdentityItemActionsWidget(
identity: identity,
imagePaths: imagePaths,
onEditIdentityAction: onEditIdentityAction,
onDeleteIdentityAction: onDeleteIdentityAction,
isDesktop: isDesktop,
),
],
);
}
}
}
@@ -0,0 +1,32 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter/material.dart';
import 'package:tmail_ui_user/features/base/widget/circle_loading_widget.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/get_all_identities_state.dart';
class IdentityLoadingWidget extends StatelessWidget {
final Either<Failure, Success> identityViewState;
const IdentityLoadingWidget({
super.key,
required this.identityViewState,
});
@override
Widget build(BuildContext context) {
return identityViewState.fold(
(failure) => const SizedBox.shrink(),
(success) {
if (success is GetAllIdentitiesLoading) {
return const Center(
child: CircleLoadingWidget(margin: EdgeInsets.all(16.0)),
);
} else {
return const SizedBox.shrink();
}
},
);
}
}
@@ -0,0 +1,82 @@
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/button/tmail_button_widget.dart';
import 'package:flutter/material.dart';
import 'package:jmap_dart_client/jmap/identities/identity.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
typedef OnEditIdentityAction = Function(Identity identity);
typedef OnDeleteIdentityAction = Function(Identity identity);
class ListIdentityItemActionsWidget extends StatelessWidget {
final Identity identity;
final ImagePaths imagePaths;
final OnEditIdentityAction onEditIdentityAction;
final OnDeleteIdentityAction onDeleteIdentityAction;
final bool isDesktop;
const ListIdentityItemActionsWidget({
super.key,
required this.identity,
required this.imagePaths,
required this.onEditIdentityAction,
required this.onDeleteIdentityAction,
this.isDesktop = true,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
TMailButtonWidget(
icon: imagePaths.icCompose,
iconSize: 20,
text: AppLocalizations.of(context).edit,
backgroundColor: Colors.transparent,
iconColor: AppColor.primaryColor,
textStyle: ThemeUtils.textStyleInter500().copyWith(
fontSize: 13,
height: 1,
letterSpacing: 0.39,
color: AppColor.primaryColor,
),
flexibleText: true,
minWidth: 100,
mainAxisSize: MainAxisSize.min,
maxLines: 1,
iconSpace: 4,
padding: const EdgeInsetsDirectional.symmetric(
vertical: 5,
horizontal: 8,
),
onTapActionCallback: () => onEditIdentityAction(identity),
),
TMailButtonWidget(
icon: imagePaths.icDeleteRule,
iconSize: 20,
text: AppLocalizations.of(context).delete,
backgroundColor: Colors.transparent,
iconColor: AppColor.primaryColor,
textStyle: ThemeUtils.textStyleInter500().copyWith(
fontSize: 13,
height: 1,
letterSpacing: 0.39,
color: AppColor.primaryColor,
),
minWidth: 100,
flexibleText: true,
mainAxisSize: MainAxisSize.min,
maxLines: 1,
iconSpace: 4,
padding: const EdgeInsetsDirectional.symmetric(
vertical: 5,
horizontal: 8,
),
onTapActionCallback: () => onDeleteIdentityAction(identity),
),
],
);
}
}
@@ -0,0 +1,55 @@
import 'package:core/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart';
import 'package:core/presentation/views/html_viewer/html_content_viewer_widget.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart';
import 'package:tmail_ui_user/main/utils/app_utils.dart';
class SignatureBuilder extends StatelessWidget {
const SignatureBuilder({
Key? key,
required this.value,
this.height = 150,
this.width = 280,
this.scrollController,
}) : super(key: key);
final String value;
final double width;
final double height;
final ScrollController? scrollController;
@override
Widget build(BuildContext context) {
if (PlatformInfo.isWeb) {
return HtmlContentViewerOnWeb(
contentHtml: value,
widthContent: width,
heightContent: height,
contentPadding: 0,
viewMaxHeight: height,
htmlContentMinWidth: width,
htmlContentMinHeight: 0.0,
offsetHtmlContentHeight: 0.0,
allowResizeToDocumentSize: false,
direction: AppUtils.getCurrentDirection(context),
scrollController: scrollController,
keepAlive: true,
disableScrolling: true,
autoAdjustHeight: true,
);
} else {
return HtmlContentViewer(
contentHtml: value,
initialWidth: width,
maxViewHeight: height,
contentPadding: 0,
htmlContentMinHeight: 0.0,
offsetHtmlContentHeight: 0.0,
direction: AppUtils.getCurrentDirection(context),
keepAlive: true,
disableScrolling: true,
);
}
}
}
@@ -0,0 +1,35 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter/cupertino.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/transform_list_signature_state.dart';
class SignatureLoadingWidget extends StatelessWidget {
final Either<Failure, Success> signatureViewState;
const SignatureLoadingWidget({
super.key,
required this.signatureViewState,
});
@override
Widget build(BuildContext context) {
return signatureViewState.fold(
(failure) => const SizedBox.shrink(),
(success) {
if (success is TransformListSignatureLoading) {
return const Center(
child: Padding(
padding: EdgeInsets.all(8),
child: CupertinoActivityIndicator(color: AppColor.colorLoading),
),
);
} else {
return const SizedBox.shrink();
}
},
);
}
}