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/either_view_state_extension.dart';
|
||||
export 'presentation/extensions/media_type_extension.dart';
|
||||
export 'presentation/extensions/scroll_controller_extension.dart';
|
||||
|
||||
// Exceptions
|
||||
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;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [emailId];
|
||||
}
|
||||
|
||||
class OpenAttachmentListAction extends EmailUIAction {
|
||||
OpenAttachmentListAction(this.emailId);
|
||||
|
||||
final EmailId? emailId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [emailId];
|
||||
}
|
||||
@@ -189,10 +189,11 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
final isEmailContentClipped = RxBool(false);
|
||||
final attendanceStatus = Rxn<AttendanceStatus>();
|
||||
final htmlContentViewKey = GlobalKey<HtmlContentViewState>();
|
||||
final attachmentListKey = GlobalKey();
|
||||
|
||||
Identity? _identitySelected;
|
||||
ButtonState? _printEmailButtonState;
|
||||
GlobalKey? attachmentListKey;
|
||||
|
||||
final obxListeners = <Worker>[];
|
||||
late final EmailActionReactor emailActionReactor;
|
||||
|
||||
@@ -249,6 +250,9 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
if (PlatformInfo.isWeb) {
|
||||
attachmentListKey = GlobalKey();
|
||||
}
|
||||
_threadDetailController = getBinding<ThreadDetailController>();
|
||||
_injectCalendarEventBindings(session, accountId);
|
||||
_registerObxStreamListener();
|
||||
|
||||
@@ -450,6 +450,9 @@ class EmailView extends GetWidget<SingleEmailController> {
|
||||
return Obx(() {
|
||||
if (controller.attachments.isNotEmpty) {
|
||||
return EmailAttachmentsWidget(
|
||||
key: PlatformInfo.isWeb && controller.responsiveUtils.isMobile(context)
|
||||
? controller.attachmentListKey
|
||||
: null,
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
attachments: controller.attachments,
|
||||
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 {
|
||||
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: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/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/popup_menu_item_email_action.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart';
|
||||
@@ -217,4 +218,10 @@ extension OnThreadDetailActionClick on ThreadDetailController {
|
||||
|
||||
return destinationMailbox.id;
|
||||
}
|
||||
|
||||
void onOpenAttachmentListAction() {
|
||||
mailboxDashBoardController.dispatchEmailUIAction(
|
||||
OpenAttachmentListAction(currentExpandedEmailId.value),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,9 @@ class ThreadDetailView extends GetWidget<ThreadDetailController> {
|
||||
threadDetailCanPermanentlyDelete: controller.threadDetailCanPermanentlyDelete,
|
||||
onThreadActionClick: controller.onThreadDetailActionClick,
|
||||
onThreadMoreActionClick: controller.onThreadDetailMoreActionClick,
|
||||
onOpenAttachmentListAction: controller.isEmailExpandedHasAttachments
|
||||
? controller.onOpenAttachmentListAction
|
||||
: null,
|
||||
optionWidgets: [
|
||||
if (controller.previousAvailable)
|
||||
TMailButtonWidget.fromIcon(
|
||||
@@ -135,8 +138,11 @@ class ThreadDetailView extends GetWidget<ThreadDetailController> {
|
||||
itemCount: manager.isThreadDetailEnabled
|
||||
? manager.availableThreadIds.length
|
||||
: manager.currentDisplayedEmails.length,
|
||||
itemBuilder: (context, index) {
|
||||
return SingleChildScrollView(child: threadBody);
|
||||
itemBuilder: (_, __) {
|
||||
return SingleChildScrollView(
|
||||
controller: controller.scrollController,
|
||||
child: threadBody,
|
||||
);
|
||||
},
|
||||
onPageChanged: controller.onThreadPageChanged,
|
||||
),
|
||||
|
||||
@@ -29,6 +29,7 @@ class ThreadDetailAppBar extends StatelessWidget {
|
||||
this.optionWidgets = const [],
|
||||
this.onThreadActionClick,
|
||||
this.onThreadMoreActionClick,
|
||||
this.onOpenAttachmentListAction,
|
||||
});
|
||||
|
||||
final ResponsiveUtils responsiveUtils;
|
||||
@@ -43,6 +44,7 @@ class ThreadDetailAppBar extends StatelessWidget {
|
||||
final List<Widget> optionWidgets;
|
||||
final OnThreadActionClick? onThreadActionClick;
|
||||
final OnThreadMoreActionClick? onThreadMoreActionClick;
|
||||
final VoidCallback? onOpenAttachmentListAction;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -114,6 +116,14 @@ class ThreadDetailAppBar extends StatelessWidget {
|
||||
childrenWidgets = [
|
||||
if (_supportDisplayMailboxNameTitle(context)) backButton,
|
||||
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,
|
||||
];
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user