From 5579e11f0f8b538beb81f3af5fb8ff402fc0b6f5 Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 13 Oct 2025 16:41:53 +0700 Subject: [PATCH 1/2] TF-4099 Reduce WebView memory footprint --- .../presentation/composer_controller.dart | 26 ++++++++++++++--- .../rich_text_mobile_tablet_controller.dart | 8 +++++- .../web_view_lifecycle_manager.dart | 28 +++++++++++++++++++ .../local_file_picker_interactor.dart | 4 +++ 4 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 lib/features/composer/presentation/controller/web_view_lifecycle_manager.dart diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index fa726c8a4d..e4da9a2851 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -10,7 +10,6 @@ import 'package:dio/dio.dart'; import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:file_picker/file_picker.dart'; import 'package:filesize/filesize.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; @@ -618,6 +617,9 @@ class ComposerController extends BaseController void onCreatedMobileEditorAction(BuildContext context, HtmlEditorApi editorApi, String? content) { richTextMobileTabletController?.htmlEditorApi = editorApi; + richTextMobileTabletController?.onInitWebViewLifecycleManager( + editorApi.webViewController, + ); richTextMobileTabletController?.richTextController.onCreateHTMLEditor( editorApi, onEnterKeyDown: _onEnterKeyDown, @@ -1181,14 +1183,18 @@ class ComposerController extends BaseController .build(); } - void openFilePickerByType(BuildContext context, FileType fileType) async { - if (!kIsWeb) { + void openFilePickerByType(BuildContext context, FileType fileType) { + if (PlatformInfo.isMobile) { popBack(); + richTextMobileTabletController?.webViewLifecycleManager?.pause(); } consumeState(_localFilePickerInteractor.execute(fileType: fileType)); } void _handlePickFileFailure(LocalFilePickerFailure failure) { + if (PlatformInfo.isMobile) { + richTextMobileTabletController?.webViewLifecycleManager?.resume(); + } if (currentOverlayContext != null && currentContext != null && failure.exception is! PickFileCanceledException) { appToast.showToastErrorMessage( currentOverlayContext!, @@ -1197,6 +1203,9 @@ class ComposerController extends BaseController } void _handlePickImageFailure(LocalImagePickerFailure failure) { + if (PlatformInfo.isMobile) { + richTextMobileTabletController?.webViewLifecycleManager?.resume(); + } if (currentOverlayContext != null && currentContext != null && failure.exception is! PickFileCanceledException) { appToast.showToastErrorMessage( currentOverlayContext!, @@ -1205,6 +1214,9 @@ class ComposerController extends BaseController } void _handlePickFileSuccess(LocalFilePickerSuccess success) { + if (PlatformInfo.isMobile) { + richTextMobileTabletController?.webViewLifecycleManager?.resume(); + } uploadController.validateTotalSizeAttachmentsBeforeUpload( totalSizePreparedFiles: success.pickedFiles.totalSize, onValidationSuccess: () => uploadAttachmentsAction(pickedFiles: success.pickedFiles) @@ -1212,6 +1224,9 @@ class ComposerController extends BaseController } void _handlePickImageSuccess(LocalImagePickerSuccess success) { + if (PlatformInfo.isMobile) { + richTextMobileTabletController?.webViewLifecycleManager?.resume(); + } uploadController.validateTotalSizeInlineAttachmentsBeforeUpload( totalSizePreparedFiles: success.fileInfo.fileSize, onValidationSuccess: () => uploadAttachmentsAction(pickedFiles: [success.fileInfo.withInline()]) @@ -1739,7 +1754,7 @@ class ComposerController extends BaseController } } - void insertImage(BuildContext context, double maxWith) async { + void insertImage(BuildContext context, double maxWith) { clearFocus(context); if (responsiveUtils.isMobile(context)) { @@ -1748,6 +1763,9 @@ class ComposerController extends BaseController maxWithEditor = maxWith - 70; } + if (PlatformInfo.isMobile) { + richTextMobileTabletController?.webViewLifecycleManager?.pause(); + } consumeState(_localImagePickerInteractor.execute()); } diff --git a/lib/features/composer/presentation/controller/rich_text_mobile_tablet_controller.dart b/lib/features/composer/presentation/controller/rich_text_mobile_tablet_controller.dart index e07c2a569b..bc3c824fc1 100644 --- a/lib/features/composer/presentation/controller/rich_text_mobile_tablet_controller.dart +++ b/lib/features/composer/presentation/controller/rich_text_mobile_tablet_controller.dart @@ -1,5 +1,4 @@ import 'dart:io'; - import 'package:core/utils/app_logger.dart'; import 'package:core/utils/html/html_utils.dart'; import 'package:core/utils/platform_info.dart'; @@ -7,12 +6,14 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; import 'package:rich_text_composer/rich_text_composer.dart'; +import 'package:tmail_ui_user/features/composer/presentation/controller/web_view_lifecycle_manager.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/header_style_type.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/inline_image.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; class RichTextMobileTabletController extends GetxController { HtmlEditorApi? htmlEditorApi; + WebViewLifecycleManager? webViewLifecycleManager; final RichTextController richTextController = RichTextController(); @@ -72,9 +73,14 @@ class RichTextMobileTabletController extends GetxController { } } + void onInitWebViewLifecycleManager(InAppWebViewController? controller) { + webViewLifecycleManager = WebViewLifecycleManager(controller); + } + @override void onClose() { richTextController.dispose(); + webViewLifecycleManager = null; htmlEditorApi = null; super.onClose(); } diff --git a/lib/features/composer/presentation/controller/web_view_lifecycle_manager.dart b/lib/features/composer/presentation/controller/web_view_lifecycle_manager.dart new file mode 100644 index 0000000000..32ef9f8587 --- /dev/null +++ b/lib/features/composer/presentation/controller/web_view_lifecycle_manager.dart @@ -0,0 +1,28 @@ +import 'package:core/utils/app_logger.dart'; +import 'package:rich_text_composer/rich_text_composer.dart'; + +class WebViewLifecycleManager { + final InAppWebViewController? controller; + + WebViewLifecycleManager(this.controller); + + Future pause() async { + if (controller == null) return; + try { + await controller!.pauseTimers(); + await controller!.pause(); + } catch (e) { + logError('$runtimeType::pause:[WebViewLifecycleManager] pause error: $e'); + } + } + + Future resume() async { + if (controller == null) return; + try { + await controller!.resume(); + await controller!.resumeTimers(); + } catch (e) { + logError('$runtimeType::resume:[WebViewLifecycleManager] resume error: $e'); + } + } +} diff --git a/lib/features/upload/domain/usecases/local_file_picker_interactor.dart b/lib/features/upload/domain/usecases/local_file_picker_interactor.dart index dd7cf46fab..d015d61c3f 100644 --- a/lib/features/upload/domain/usecases/local_file_picker_interactor.dart +++ b/lib/features/upload/domain/usecases/local_file_picker_interactor.dart @@ -1,6 +1,9 @@ +import 'dart:io'; + import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; +import 'package:core/utils/app_logger.dart'; import 'package:core/utils/platform_info.dart'; import 'package:dartz/dartz.dart'; import 'package:file_picker/file_picker.dart'; @@ -14,6 +17,7 @@ class LocalFilePickerInteractor { Stream> execute({FileType fileType = FileType.any}) async* { try { + log('$runtimeType::execute:Memory info: ${(ProcessInfo.currentRss / (1024 * 1024)).toStringAsFixed(2)} MB'); yield Right(LocalFilePickerLoading()); final filesResult = await FilePicker.platform.pickFiles( From d345f0a9fe7c75e497a8832107187088752dea4e Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 13 Oct 2025 16:47:22 +0700 Subject: [PATCH 2/2] TF-4099 Enable `android:largeHeap="true"` in AndroidManifest for allow process to keep more RAM --- android/app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 99702c146b..8ab8eb11f4 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -42,6 +42,7 @@