TF-2116 Create AttachmentComposer widget and style

(cherry picked from commit 8245f079b4ca19950c32dd7386d4d1d213b53172)
This commit is contained in:
dab246
2023-09-06 19:12:28 +07:00
committed by Dat H. Pham
parent 11dcc38fab
commit d3533e2f28
10 changed files with 402 additions and 154 deletions
@@ -216,6 +216,8 @@ extension AppColor on Color {
static const colorRichButtonComposer = Color(0xFFAEAEC0);
static const colorMobileRichButtonComposer = Color(0xFF8C9CAF);
static const colorSelected = Color(0xFFE3F1FF);
static const colorAttachmentBorder = Color(0xFFE5ECF3);
static const colorProgressLoadingBackground = Color(0xFFE3F1FF);
static const mapGradientColor = [
[Color(0xFF21D4FD), Color(0xFFB721FF)],
@@ -0,0 +1,22 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:flutter/material.dart';
class AttachmentComposerWidgetStyle {
static const double maxHeight = 150;
static const double listItemSpace = 8;
static const Color backgroundColor = Colors.white;
static const EdgeInsetsGeometry listItemPadding = EdgeInsetsDirectional.symmetric(vertical: 8, horizontal: 16);
static const List<BoxShadow> shadow = [
BoxShadow(
color: AppColor.colorShadowBgContentEmail,
blurRadius: 24
),
BoxShadow(
color: AppColor.colorShadowBgContentEmail,
blurRadius: 2
),
];
}
@@ -0,0 +1,26 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:flutter/material.dart';
class AttachmentHeaderComposerWidgetStyle {
static const double space = 8;
static const double iconSize = 20;
static const double sizeLabelRadius = 12;
static const Color iconColor = AppColor.colorLabelComposer;
static const Color sizeLabelBackground = AppColor.primaryColor;
static const Color borderColor = AppColor.colorLineComposer;
static const EdgeInsetsGeometry sizeLabelPadding = EdgeInsetsDirectional.symmetric(horizontal: 5, vertical: 2);
static const EdgeInsetsGeometry padding = EdgeInsetsDirectional.all(8);
static const TextStyle labelTextSize = TextStyle(
fontSize: 13,
color: AppColor.colorLabelComposer,
fontWeight: FontWeight.w500
);
static const TextStyle sizeLabelTextSize = TextStyle(
fontSize: 12,
color: Colors.white,
fontWeight: FontWeight.w500
);
}
@@ -0,0 +1,35 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:flutter/material.dart';
class AttachmentItemComposerWidgetStyle {
static const double radius = 8;
static const double iconSize = 20;
static const double space = 8;
static const double deleteIconSize = 18;
static const double deleteIconRadius = 10;
static const double width = 260;
static const Color borderColor = AppColor.colorAttachmentBorder;
static const Color backgroundColor = Colors.white;
static const Color deleteIconColor = AppColor.colorRichButtonComposer;
static const EdgeInsetsGeometry padding = EdgeInsetsDirectional.all(8);
static const EdgeInsetsGeometry deleteIconPadding = EdgeInsetsDirectional.all(3);
static const EdgeInsetsGeometry progressLoadingPadding = EdgeInsetsDirectional.only(top: 8);
static const TextStyle labelTextStyle = TextStyle(
fontSize: 14,
color: Colors.black,
fontWeight: FontWeight.w500
);
static const TextStyle dotsLabelTextStyle = TextStyle(
fontSize: 12,
color: Colors.black,
fontWeight: FontWeight.w500
);
static const TextStyle sizeLabelTextStyle = TextStyle(
fontSize: 11,
color: AppColor.colorLabelComposer,
fontWeight: FontWeight.w500
);
}
@@ -0,0 +1,10 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:flutter/material.dart';
class AttachmentProgressLoadingComposerWidgetStyle {
static const double height = 2;
static const double radius = 1;
static const Color backgroundColor = AppColor.colorProgressLoadingBackground;
static const Color progressColor = AppColor.primaryColor;
}
@@ -0,0 +1,80 @@
import 'package:flutter/material.dart';
import 'package:tmail_ui_user/features/composer/presentation/styles/attachment_composer_widget_style.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/attachment_item_composer_widget.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/attachment_header_composer_widget.dart';
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state.dart';
typedef OnToggleExpandAttachmentAction = void Function(bool isCollapsed);
class AttachmentComposerWidget extends StatefulWidget {
final List<UploadFileState> listFileUploaded;
final bool isCollapsed;
final OnDeleteAttachmentAction onDeleteAttachmentAction;
final OnToggleExpandAttachmentAction onToggleExpandAttachmentAction;
const AttachmentComposerWidget({
super.key,
required this.listFileUploaded,
required this.isCollapsed,
required this.onDeleteAttachmentAction,
required this.onToggleExpandAttachmentAction,
});
@override
State<AttachmentComposerWidget> createState() => _AttachmentComposerWidgetState();
}
class _AttachmentComposerWidgetState extends State<AttachmentComposerWidget> {
bool _isCollapsed = false;
@override
void initState() {
super.initState();
_isCollapsed = widget.isCollapsed;
}
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
color: AttachmentComposerWidgetStyle.backgroundColor,
boxShadow: AttachmentComposerWidgetStyle.shadow
),
width: double.infinity,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
AttachmentHeaderComposerWidget(
listFileUploaded: widget.listFileUploaded,
isCollapsed: _isCollapsed,
onToggleExpandAction: (isCollapsed) {
setState(() => _isCollapsed = !isCollapsed);
widget.onToggleExpandAttachmentAction(!isCollapsed);
},
),
if (!_isCollapsed)
Container(
width: double.infinity,
padding: AttachmentComposerWidgetStyle.listItemPadding,
constraints: const BoxConstraints(maxHeight: AttachmentComposerWidgetStyle.maxHeight),
child: SingleChildScrollView(
child: Wrap(
spacing: AttachmentComposerWidgetStyle.listItemSpace,
runSpacing: AttachmentComposerWidgetStyle.listItemSpace,
children: widget.listFileUploaded
.map((file) => AttachmentItemComposerWidget(
fileState: file,
onDeleteAttachmentAction: widget.onDeleteAttachmentAction
))
.toList(),
),
),
),
]
),
);
}
}
@@ -1,154 +0,0 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/style_utils.dart';
import 'package:core/presentation/views/button/icon_button_web.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:core/utils/platform_info.dart';
import 'package:extended_text/extended_text.dart';
import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart';
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state.dart';
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_status.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
typedef OnDeleteAttachmentAction = void Function(UploadFileState fileState);
class AttachmentFileComposerBuilder extends StatelessWidget with AppLoaderMixin {
final _imagePaths = Get.find<ImagePaths>();
final UploadFileState fileState;
final double? maxWidth;
final EdgeInsetsGeometry? itemMargin;
final OnDeleteAttachmentAction? onDeleteAttachmentAction;
final Widget? buttonAction;
AttachmentFileComposerBuilder(this.fileState, {
super.key,
this.maxWidth,
this.itemMargin,
this.buttonAction,
this.onDeleteAttachmentAction,
});
@override
Widget build(BuildContext context) {
return Theme(
data: ThemeData(
splashColor: Colors.transparent ,
highlightColor: Colors.transparent),
child: Container(
margin: itemMargin ?? EdgeInsets.zero,
padding: EdgeInsets.zero,
alignment: Alignment.center,
width: maxWidth,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: AppColor.colorInputBorderCreateMailbox),
color: Colors.white),
child: Stack(children: [
ListTile(
contentPadding: EdgeInsets.zero,
focusColor: AppColor.primaryColor,
hoverColor: AppColor.primaryColor,
onTap: () {},
leading: Padding(
padding: const EdgeInsetsDirectional.only(
start: 8,
bottom: PlatformInfo.isWeb ? 6 : 14
),
child: SvgPicture.asset(
fileState.getIcon(_imagePaths),
width: 40,
height: 40,
fit: BoxFit.fill),
),
title: Transform(
transform: Matrix4.translationValues(
DirectionUtils.isDirectionRTLByLanguage(context) ? 0.0 : (PlatformInfo.isWeb ? 0.0 : -8.0),
PlatformInfo.isWeb ? -8.0 : -10.0,
0.0),
child: Padding(
padding: const EdgeInsetsDirectional.only(end: PlatformInfo.isWeb ? 20 : 16),
child: ExtendedText(
fileState.fileName,
maxLines: 1,
overflow: CommonTextStyle.defaultTextOverFlow,
softWrap: CommonTextStyle.defaultSoftWrap,
overflowWidget: TextOverflowWidget(
position: Directionality.maybeOf(context) == TextDirection.rtl
? TextOverflowPosition.start
: TextOverflowPosition.end,
child: const Text(
'...',
style: TextStyle(
fontSize: 12,
color: Colors.black,
fontWeight: FontWeight.w500
),
),
),
style: const TextStyle(
fontSize: 14,
color: Colors.black,
fontWeight: FontWeight.w500
),
),
)
),
subtitle: fileState.fileSize != 0
? Transform(
transform: Matrix4.translationValues(
DirectionUtils.isDirectionRTLByLanguage(context) ? 0.0 : PlatformInfo.isWeb ? 0.0 : -8.0,
PlatformInfo.isWeb ? -8.0 : -10.0,
0.0),
child: Text(
filesize(fileState.fileSize),
maxLines: 1,
softWrap: CommonTextStyle.defaultSoftWrap,
overflow: CommonTextStyle.defaultTextOverFlow,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
color: AppColor.colorContentEmail)))
: null,
),
PositionedDirectional(
end: PlatformInfo.isWeb ? -5 : -12,
top: PlatformInfo.isWeb ? -5 : -12,
child: buildIconWeb(
icon: SvgPicture.asset(_imagePaths.icDeleteAttachment, fit: BoxFit.fill),
tooltip: AppLocalizations.of(context).delete,
onTap: () {
if (onDeleteAttachmentAction != null) {
onDeleteAttachmentAction!.call(fileState);
}
}
)
),
Align(alignment: AlignmentDirectional.bottomCenter, child: _progressLoading),
]),
)
);
}
Widget get _progressLoading {
switch(fileState.uploadStatus) {
case UploadFileStatus.waiting:
return Padding(
padding: const EdgeInsets.only(left: 8, right: 8, top: 50),
child: horizontalLoadingWidget);
case UploadFileStatus.uploading:
return Padding(
padding: const EdgeInsets.only(top: 50),
child: horizontalPercentLoadingWidget(fileState.percentUploading));
case UploadFileStatus.uploadFailed:
case UploadFileStatus.succeed:
return const SizedBox.shrink();
}
}
}
@@ -0,0 +1,78 @@
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/composer/presentation/styles/attachment_header_composer_widget_style.dart';
import 'package:tmail_ui_user/features/upload/presentation/extensions/list_upload_file_state_extension.dart';
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
typedef OnToggleExpandAttachmentViewAction = Function(bool isCollapsed);
class AttachmentHeaderComposerWidget extends StatelessWidget {
final List<UploadFileState> listFileUploaded;
final bool isCollapsed;
final OnToggleExpandAttachmentViewAction onToggleExpandAction;
final _imagePaths = Get.find<ImagePaths>();
AttachmentHeaderComposerWidget({
super.key,
required this.listFileUploaded,
required this.isCollapsed,
required this.onToggleExpandAction,
});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () => onToggleExpandAction.call(isCollapsed),
child: Container(
padding: AttachmentHeaderComposerWidgetStyle.padding,
decoration: isCollapsed
? null
: const BoxDecoration(
border: Border(
bottom: BorderSide(
color: AttachmentHeaderComposerWidgetStyle.borderColor,
width: 1
)
)
),
child: Row(
children: [
TMailButtonWidget.fromIcon(
icon: isCollapsed
? _imagePaths.icArrowRight
: _imagePaths.icArrowBottom,
iconSize: AttachmentHeaderComposerWidgetStyle.iconSize,
iconColor: AttachmentHeaderComposerWidgetStyle.iconColor,
backgroundColor: Colors.white,
padding: EdgeInsets.zero,
onTapActionCallback: () => onToggleExpandAction.call(isCollapsed),
),
const SizedBox(width: AttachmentHeaderComposerWidgetStyle.space / 2),
Text(
'${listFileUploaded.length} ${AppLocalizations.of(context).attachments}',
style: AttachmentHeaderComposerWidgetStyle.labelTextSize
),
const SizedBox(width: AttachmentHeaderComposerWidgetStyle.space),
Container(
decoration: const BoxDecoration(
color: AttachmentHeaderComposerWidgetStyle.sizeLabelBackground,
borderRadius: BorderRadius.all(Radius.circular(AttachmentHeaderComposerWidgetStyle.sizeLabelRadius))
),
padding: AttachmentHeaderComposerWidgetStyle.sizeLabelPadding,
child: Text(
filesize(listFileUploaded.totalSize, 0),
style: AttachmentHeaderComposerWidgetStyle.sizeLabelTextSize,
),
)
],
),
),
);
}
}
@@ -0,0 +1,100 @@
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:extended_text/extended_text.dart';
import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart';
import 'package:tmail_ui_user/features/composer/presentation/styles/attachment_item_composer_widget_style.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/attachment_progress_loading_composer_widget.dart';
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state.dart';
typedef OnDeleteAttachmentAction = void Function(UploadFileState fileState);
class AttachmentItemComposerWidget extends StatelessWidget with AppLoaderMixin {
final _imagePaths = Get.find<ImagePaths>();
final UploadFileState fileState;
final double? maxWidth;
final EdgeInsetsGeometry? itemMargin;
final OnDeleteAttachmentAction? onDeleteAttachmentAction;
final Widget? buttonAction;
AttachmentItemComposerWidget({
super.key,
required this.fileState,
this.maxWidth,
this.itemMargin,
this.buttonAction,
this.onDeleteAttachmentAction,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(AttachmentItemComposerWidgetStyle.radius)),
border: Border.all(color: AttachmentItemComposerWidgetStyle.borderColor),
color: AttachmentItemComposerWidgetStyle.backgroundColor
),
width: AttachmentItemComposerWidgetStyle.width,
padding: AttachmentItemComposerWidgetStyle.padding,
child: Row(
children: [
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
SvgPicture.asset(
fileState.getIcon(_imagePaths),
width: AttachmentItemComposerWidgetStyle.iconSize,
height: AttachmentItemComposerWidgetStyle.iconSize,
fit: BoxFit.fill
),
const SizedBox(width: AttachmentItemComposerWidgetStyle.space),
Expanded(
child: ExtendedText(
fileState.fileName,
maxLines: 1,
overflowWidget: const TextOverflowWidget(
position: TextOverflowPosition.middle,
child: Text(
'...',
style: AttachmentItemComposerWidgetStyle.dotsLabelTextStyle,
),
),
style: AttachmentItemComposerWidgetStyle.labelTextStyle,
),
),
const SizedBox(width: AttachmentItemComposerWidgetStyle.space),
Text(
filesize(fileState.fileSize),
style: AttachmentItemComposerWidgetStyle.sizeLabelTextStyle
),
],
),
AttachmentProgressLoadingComposerWidget(
fileState: fileState,
padding: AttachmentItemComposerWidgetStyle.progressLoadingPadding,
)
],
),
),
const SizedBox(width: AttachmentItemComposerWidgetStyle.space),
TMailButtonWidget.fromIcon(
icon: _imagePaths.icCancel,
iconSize: AttachmentItemComposerWidgetStyle.deleteIconSize,
borderRadius: AttachmentItemComposerWidgetStyle.deleteIconRadius,
padding: AttachmentItemComposerWidgetStyle.deleteIconPadding,
iconColor: AttachmentItemComposerWidgetStyle.deleteIconColor,
onTapActionCallback: () => onDeleteAttachmentAction?.call(fileState),
)
],
),
);
}
}
@@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
import 'package:percent_indicator/linear_percent_indicator.dart';
import 'package:tmail_ui_user/features/composer/presentation/styles/attachment_progress_loading_composer_widget_style.dart';
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state.dart';
import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_status.dart';
class AttachmentProgressLoadingComposerWidget extends StatelessWidget {
final UploadFileState fileState;
final EdgeInsetsGeometry? padding;
const AttachmentProgressLoadingComposerWidget({
super.key,
required this.fileState,
this.padding,
});
@override
Widget build(BuildContext context) {
switch (fileState.uploadStatus) {
case UploadFileStatus.waiting:
return Padding(
padding: padding ?? EdgeInsets.zero,
child: const LinearProgressIndicator(
color: AttachmentProgressLoadingComposerWidgetStyle.progressColor,
minHeight: AttachmentProgressLoadingComposerWidgetStyle.height,
backgroundColor: AttachmentProgressLoadingComposerWidgetStyle.backgroundColor,
),
);
case UploadFileStatus.uploading:
return Padding(
padding: padding ?? EdgeInsets.zero,
child: LinearPercentIndicator(
padding: EdgeInsets.zero,
lineHeight:AttachmentProgressLoadingComposerWidgetStyle.height,
percent: fileState.percentUploading > 1.0
? 1.0
: fileState.percentUploading,
barRadius: const Radius.circular(AttachmentProgressLoadingComposerWidgetStyle.radius),
backgroundColor: AttachmentProgressLoadingComposerWidgetStyle.backgroundColor,
progressColor: AttachmentProgressLoadingComposerWidgetStyle.progressColor,
),
);
case UploadFileStatus.uploadFailed:
case UploadFileStatus.succeed:
return const SizedBox.shrink();
}
}
}