diff --git a/assets/images/ic_drop_zone_icon.svg b/assets/images/ic_drop_zone_icon.svg
new file mode 100644
index 000000000..37ec0f09e
--- /dev/null
+++ b/assets/images/ic_drop_zone_icon.svg
@@ -0,0 +1,4 @@
+
diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart
index b1116ad13..3143ae773 100644
--- a/core/lib/presentation/extensions/color_extension.dart
+++ b/core/lib/presentation/extensions/color_extension.dart
@@ -218,6 +218,8 @@ extension AppColor on Color {
static const colorSelected = Color(0xFFE3F1FF);
static const colorAttachmentBorder = Color(0xFFE5ECF3);
static const colorProgressLoadingBackground = Color(0xFFE3F1FF);
+ static const colorDropZoneBackground = Color(0xFFF6FAFF);
+ static const colorDropZoneBorder = Color(0xFF46A2FF);
static const mapGradientColor = [
[Color(0xFF21D4FD), Color(0xFFB721FF)],
diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart
index d4ffc99ee..d8b866c66 100644
--- a/core/lib/presentation/resources/image_paths.dart
+++ b/core/lib/presentation/resources/image_paths.dart
@@ -203,6 +203,7 @@ class ImagePaths {
String get icAttachFile => _getImagePath('ic_attach_file.svg');
String get icSend => _getImagePath('ic_send.svg');
String get icReadReceipt => _getImagePath('ic_read_receipt.svg');
+ String get icDropZoneIcon => _getImagePath('ic_drop_zone_icon.svg');
String _getImagePath(String imageName) {
return AssetsPaths.images + imageName;
diff --git a/lib/features/composer/presentation/styles/web/drop_zone_widget_style.dart b/lib/features/composer/presentation/styles/web/drop_zone_widget_style.dart
new file mode 100644
index 000000000..033c224f6
--- /dev/null
+++ b/lib/features/composer/presentation/styles/web/drop_zone_widget_style.dart
@@ -0,0 +1,22 @@
+import 'package:core/presentation/extensions/color_extension.dart';
+import 'package:flutter/material.dart';
+
+class DropZoneWidgetStyle {
+ static const double space = 20;
+ static const double borderWidth = 2;
+ static const double radius = 16;
+
+ static const List dashSize = [6, 3];
+
+ static const Color backgroundColor = AppColor.colorDropZoneBackground;
+ static const Color borderColor = AppColor.colorDropZoneBorder;
+
+ static const EdgeInsetsGeometry padding = EdgeInsets.all(20);
+ static const EdgeInsetsGeometry margin = EdgeInsetsDirectional.symmetric(vertical: 8);
+
+ static const TextStyle labelTextStyle = TextStyle(
+ color: Colors.black,
+ fontSize: 22,
+ fontWeight: FontWeight.w600
+ );
+}
\ No newline at end of file
diff --git a/lib/features/composer/presentation/widgets/web/drop_zone_widget.dart b/lib/features/composer/presentation/widgets/web/drop_zone_widget.dart
new file mode 100644
index 000000000..58b908323
--- /dev/null
+++ b/lib/features/composer/presentation/widgets/web/drop_zone_widget.dart
@@ -0,0 +1,87 @@
+
+import 'package:core/presentation/resources/image_paths.dart';
+import 'package:core/utils/app_logger.dart';
+import 'package:dotted_border/dotted_border.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:get/get.dart';
+import 'package:model/email/attachment.dart';
+import 'package:tmail_ui_user/features/composer/presentation/styles/web/drop_zone_widget_style.dart';
+import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
+
+class DropZoneWidget extends StatefulWidget {
+
+ final double? width;
+ final double? height;
+
+ const DropZoneWidget({super.key, this.width, this.height});
+
+ @override
+ State createState() => _DropZoneWidgetState();
+}
+
+class _DropZoneWidgetState extends State {
+
+ final _imagePaths = Get.find();
+
+ bool _isDragging = false;
+
+ @override
+ Widget build(BuildContext context) {
+ return DragTarget(
+ builder: (context, candidateData, rejectedData) {
+ if (_isDragging) {
+ return Padding(
+ padding: DropZoneWidgetStyle.margin,
+ child: DottedBorder(
+ borderType: BorderType.RRect,
+ radius: const Radius.circular(DropZoneWidgetStyle.radius),
+ color: DropZoneWidgetStyle.borderColor,
+ strokeWidth: DropZoneWidgetStyle.borderWidth,
+ dashPattern: DropZoneWidgetStyle.dashSize,
+ child: Container(
+ clipBehavior: Clip.antiAlias,
+ decoration: const ShapeDecoration(
+ color: DropZoneWidgetStyle.backgroundColor,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.all(Radius.circular(DropZoneWidgetStyle.radius)),
+ ),
+ ),
+ width: widget.width,
+ height: widget.height,
+ padding: DropZoneWidgetStyle.padding,
+ alignment: AlignmentDirectional.center,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ SvgPicture.asset(_imagePaths.icDropZoneIcon),
+ const SizedBox(height: DropZoneWidgetStyle.space),
+ Text(
+ AppLocalizations.of(context).dropFileHereToAttachThem,
+ style: DropZoneWidgetStyle.labelTextStyle,
+ )
+ ]
+ ),
+ ),
+ ),
+ );
+ } else {
+ return SizedBox(width: widget.width, height: widget.height);
+ }
+ },
+ onAccept: (attachment) {
+ log('_DropZoneWidgetState::build:onAccept: $attachment');
+ },
+ onLeave: (attachment) {
+ if (_isDragging) {
+ setState(() => _isDragging = false);
+ }
+ },
+ onMove: (details) {
+ if (!_isDragging) {
+ setState(() => _isDragging = true);
+ }
+ },
+ );
+ }
+}
diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb
index 53bafb7d3..e355d56a4 100644
--- a/lib/l10n/intl_messages.arb
+++ b/lib/l10n/intl_messages.arb
@@ -1,5 +1,5 @@
{
- "@@last_modified": "2023-09-07T19:09:09.089986",
+ "@@last_modified": "2023-10-02T15:04:12.165160",
"initializing_data": "Initializing data...",
"@initializing_data": {
"type": "text",
@@ -3239,5 +3239,11 @@
"type": "text",
"placeholders_order": [],
"placeholders": {}
+ },
+ "dropFileHereToAttachThem": "Drop file here to attach them",
+ "@dropFileHereToAttachThem": {
+ "type": "text",
+ "placeholders_order": [],
+ "placeholders": {}
}
}
\ No newline at end of file
diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart
index f046b6217..ec118870c 100644
--- a/lib/main/localizations/app_localizations.dart
+++ b/lib/main/localizations/app_localizations.dart
@@ -3340,4 +3340,11 @@ class AppLocalizations {
name: 'saveAsDraft',
);
}
+
+ String get dropFileHereToAttachThem {
+ return Intl.message(
+ 'Drop file here to attach them',
+ name: 'dropFileHereToAttachThem',
+ );
+ }
}
\ No newline at end of file
diff --git a/pubspec.lock b/pubspec.lock
index e37829e50..d40ffde9a 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -360,6 +360,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.0.0"
+ dotted_border:
+ dependency: "direct main"
+ description:
+ name: dotted_border
+ sha256: "108837e11848ca776c53b30bc870086f84b62ed6e01c503ed976e8f8c7df9c04"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
dropdown_button2:
dependency: "direct main"
description:
@@ -1142,6 +1150,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.8.3"
+ path_drawing:
+ dependency: transitive
+ description:
+ name: path_drawing
+ sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.1"
path_parsing:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 646ecb017..8d1670ecc 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -211,6 +211,8 @@ dependencies:
url_strategy: 0.2.0
+ dotted_border: 2.1.0
+
dev_dependencies:
flutter_test:
sdk: flutter