TF-4075 Jump to attachments list position on web responsive mobile
This commit is contained in:
@@ -17,6 +17,7 @@ export 'presentation/extensions/tap_down_details_extension.dart';
|
|||||||
export 'presentation/extensions/map_extensions.dart';
|
export 'presentation/extensions/map_extensions.dart';
|
||||||
export 'presentation/extensions/either_view_state_extension.dart';
|
export 'presentation/extensions/either_view_state_extension.dart';
|
||||||
export 'presentation/extensions/media_type_extension.dart';
|
export 'presentation/extensions/media_type_extension.dart';
|
||||||
|
export 'presentation/extensions/scroll_controller_extension.dart';
|
||||||
|
|
||||||
// Exceptions
|
// Exceptions
|
||||||
export 'domain/exceptions/download_file_exception.dart';
|
export 'domain/exceptions/download_file_exception.dart';
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
extension ScrollControllerExtension on ScrollController {
|
||||||
|
void scrollToWidgetTop({
|
||||||
|
required GlobalKey key,
|
||||||
|
double padding = 70,
|
||||||
|
}) {
|
||||||
|
final context = key.currentContext;
|
||||||
|
if (context == null) return;
|
||||||
|
|
||||||
|
final renderBox = context.findRenderObject();
|
||||||
|
if (renderBox is! RenderBox) return;
|
||||||
|
|
||||||
|
final offsetY = renderBox.localToGlobal(Offset.zero).dy;
|
||||||
|
final currentScroll = offset;
|
||||||
|
|
||||||
|
final targetOffset = (currentScroll + offsetY - padding).clamp(
|
||||||
|
position.minScrollExtent,
|
||||||
|
position.maxScrollExtent,
|
||||||
|
);
|
||||||
|
|
||||||
|
animateTo(
|
||||||
|
targetOffset,
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -81,6 +81,15 @@ class CollapseEmailInThreadDetailAction extends EmailUIAction {
|
|||||||
|
|
||||||
final EmailId emailId;
|
final EmailId emailId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [emailId];
|
||||||
|
}
|
||||||
|
|
||||||
|
class OpenAttachmentListAction extends EmailUIAction {
|
||||||
|
OpenAttachmentListAction(this.emailId);
|
||||||
|
|
||||||
|
final EmailId? emailId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [emailId];
|
List<Object?> get props => [emailId];
|
||||||
}
|
}
|
||||||
@@ -189,10 +189,11 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
final isEmailContentClipped = RxBool(false);
|
final isEmailContentClipped = RxBool(false);
|
||||||
final attendanceStatus = Rxn<AttendanceStatus>();
|
final attendanceStatus = Rxn<AttendanceStatus>();
|
||||||
final htmlContentViewKey = GlobalKey<HtmlContentViewState>();
|
final htmlContentViewKey = GlobalKey<HtmlContentViewState>();
|
||||||
final attachmentListKey = GlobalKey();
|
|
||||||
|
|
||||||
Identity? _identitySelected;
|
Identity? _identitySelected;
|
||||||
ButtonState? _printEmailButtonState;
|
ButtonState? _printEmailButtonState;
|
||||||
|
GlobalKey? attachmentListKey;
|
||||||
|
|
||||||
final obxListeners = <Worker>[];
|
final obxListeners = <Worker>[];
|
||||||
late final EmailActionReactor emailActionReactor;
|
late final EmailActionReactor emailActionReactor;
|
||||||
|
|
||||||
@@ -249,6 +250,9 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
|
if (PlatformInfo.isWeb) {
|
||||||
|
attachmentListKey = GlobalKey();
|
||||||
|
}
|
||||||
_threadDetailController = getBinding<ThreadDetailController>();
|
_threadDetailController = getBinding<ThreadDetailController>();
|
||||||
_injectCalendarEventBindings(session, accountId);
|
_injectCalendarEventBindings(session, accountId);
|
||||||
_registerObxStreamListener();
|
_registerObxStreamListener();
|
||||||
|
|||||||
@@ -450,6 +450,9 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
return Obx(() {
|
return Obx(() {
|
||||||
if (controller.attachments.isNotEmpty) {
|
if (controller.attachments.isNotEmpty) {
|
||||||
return EmailAttachmentsWidget(
|
return EmailAttachmentsWidget(
|
||||||
|
key: PlatformInfo.isWeb && controller.responsiveUtils.isMobile(context)
|
||||||
|
? controller.attachmentListKey
|
||||||
|
: null,
|
||||||
responsiveUtils: controller.responsiveUtils,
|
responsiveUtils: controller.responsiveUtils,
|
||||||
attachments: controller.attachments,
|
attachments: controller.attachments,
|
||||||
imagePaths: controller.imagePaths,
|
imagePaths: controller.imagePaths,
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import 'package:core/presentation/extensions/scroll_controller_extension.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
|
||||||
|
|
||||||
|
extension HandleOpenAttachmentListExtension on SingleEmailController {
|
||||||
|
void jumpToAttachmentList() {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (attachmentListKey == null) return;
|
||||||
|
|
||||||
|
threadDetailController?.scrollController?.scrollToWidgetTop(
|
||||||
|
key: attachmentListKey!,
|
||||||
|
padding: 70,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,4 +68,8 @@ extension GetThreadDetailActionStatus on ThreadDetailController {
|
|||||||
bool get threadDetailCanPermanentlyDelete {
|
bool get threadDetailCanPermanentlyDelete {
|
||||||
return threadDetailIsTrashed || threadDetailIsSpam || threadDetailIsDraft;
|
return threadDetailIsTrashed || threadDetailIsSpam || threadDetailIsDraft;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get isEmailExpandedHasAttachments {
|
||||||
|
return currentEmailLoaded.value?.attachments.isNotEmpty == true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:model/email/read_actions.dart';
|
|||||||
import 'package:model/mailbox/presentation_mailbox.dart';
|
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||||
import 'package:tmail_ui_user/features/base/widget/popup_menu/popup_menu_item_action_widget.dart';
|
import 'package:tmail_ui_user/features/base/widget/popup_menu/popup_menu_item_action_widget.dart';
|
||||||
import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart';
|
import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/model/context_item_email_action.dart';
|
import 'package:tmail_ui_user/features/email/presentation/model/context_item_email_action.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/model/popup_menu_item_email_action.dart';
|
import 'package:tmail_ui_user/features/email/presentation/model/popup_menu_item_email_action.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart';
|
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart';
|
||||||
@@ -217,4 +218,10 @@ extension OnThreadDetailActionClick on ThreadDetailController {
|
|||||||
|
|
||||||
return destinationMailbox.id;
|
return destinationMailbox.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onOpenAttachmentListAction() {
|
||||||
|
mailboxDashBoardController.dispatchEmailUIAction(
|
||||||
|
OpenAttachmentListAction(currentExpandedEmailId.value),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -44,6 +44,9 @@ class ThreadDetailView extends GetWidget<ThreadDetailController> {
|
|||||||
threadDetailCanPermanentlyDelete: controller.threadDetailCanPermanentlyDelete,
|
threadDetailCanPermanentlyDelete: controller.threadDetailCanPermanentlyDelete,
|
||||||
onThreadActionClick: controller.onThreadDetailActionClick,
|
onThreadActionClick: controller.onThreadDetailActionClick,
|
||||||
onThreadMoreActionClick: controller.onThreadDetailMoreActionClick,
|
onThreadMoreActionClick: controller.onThreadDetailMoreActionClick,
|
||||||
|
onOpenAttachmentListAction: controller.isEmailExpandedHasAttachments
|
||||||
|
? controller.onOpenAttachmentListAction
|
||||||
|
: null,
|
||||||
optionWidgets: [
|
optionWidgets: [
|
||||||
if (controller.previousAvailable)
|
if (controller.previousAvailable)
|
||||||
TMailButtonWidget.fromIcon(
|
TMailButtonWidget.fromIcon(
|
||||||
@@ -135,8 +138,11 @@ class ThreadDetailView extends GetWidget<ThreadDetailController> {
|
|||||||
itemCount: manager.isThreadDetailEnabled
|
itemCount: manager.isThreadDetailEnabled
|
||||||
? manager.availableThreadIds.length
|
? manager.availableThreadIds.length
|
||||||
: manager.currentDisplayedEmails.length,
|
: manager.currentDisplayedEmails.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (_, __) {
|
||||||
return SingleChildScrollView(child: threadBody);
|
return SingleChildScrollView(
|
||||||
|
controller: controller.scrollController,
|
||||||
|
child: threadBody,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onPageChanged: controller.onThreadPageChanged,
|
onPageChanged: controller.onThreadPageChanged,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class ThreadDetailAppBar extends StatelessWidget {
|
|||||||
this.optionWidgets = const [],
|
this.optionWidgets = const [],
|
||||||
this.onThreadActionClick,
|
this.onThreadActionClick,
|
||||||
this.onThreadMoreActionClick,
|
this.onThreadMoreActionClick,
|
||||||
|
this.onOpenAttachmentListAction,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ResponsiveUtils responsiveUtils;
|
final ResponsiveUtils responsiveUtils;
|
||||||
@@ -43,6 +44,7 @@ class ThreadDetailAppBar extends StatelessWidget {
|
|||||||
final List<Widget> optionWidgets;
|
final List<Widget> optionWidgets;
|
||||||
final OnThreadActionClick? onThreadActionClick;
|
final OnThreadActionClick? onThreadActionClick;
|
||||||
final OnThreadMoreActionClick? onThreadMoreActionClick;
|
final OnThreadMoreActionClick? onThreadMoreActionClick;
|
||||||
|
final VoidCallback? onOpenAttachmentListAction;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -114,6 +116,14 @@ class ThreadDetailAppBar extends StatelessWidget {
|
|||||||
childrenWidgets = [
|
childrenWidgets = [
|
||||||
if (_supportDisplayMailboxNameTitle(context)) backButton,
|
if (_supportDisplayMailboxNameTitle(context)) backButton,
|
||||||
if (isRTL) ...optionWidgets.reversed else ...optionWidgets,
|
if (isRTL) ...optionWidgets.reversed else ...optionWidgets,
|
||||||
|
if (onOpenAttachmentListAction != null)
|
||||||
|
_ThreadDetailAppBarButton(
|
||||||
|
icon: imagePaths.icAttachment,
|
||||||
|
tooltipMessage: AppLocalizations.of(context).attachments,
|
||||||
|
responsiveUtils: responsiveUtils,
|
||||||
|
iconColor: EmailViewAppBarWidgetStyles.iconColor,
|
||||||
|
onTapActionCallback: (_) => onOpenAttachmentListAction?.call(),
|
||||||
|
),
|
||||||
if (isThreadActionAvailable) ...listShortcutActions,
|
if (isThreadActionAvailable) ...listShortcutActions,
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user