TF-2919 Fix memory leak when attach files in composer

This commit is contained in:
dab246
2024-06-10 18:59:24 +07:00
committed by Dat H. Pham
parent 9d800ba798
commit 82b51082fc
5 changed files with 77 additions and 31 deletions
@@ -2,7 +2,6 @@ import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:core/utils/platform_info.dart'; import 'package:core/utils/platform_info.dart';
import 'package:extended_text/extended_text.dart'; import 'package:extended_text/extended_text.dart';
import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -10,7 +9,7 @@ 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/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/composer/presentation/widgets/attachment_progress_loading_composer_widget.dart';
import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart'; import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.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';
typedef OnDeleteAttachmentAction = void Function(UploadTaskId uploadTaskId); typedef OnDeleteAttachmentAction = void Function(UploadTaskId uploadTaskId);
@@ -18,7 +17,12 @@ class AttachmentItemComposerWidget extends StatelessWidget with AppLoaderMixin {
final _imagePaths = Get.find<ImagePaths>(); final _imagePaths = Get.find<ImagePaths>();
final UploadFileState fileState; final String fileIcon;
final String fileName;
final String fileSize;
final UploadFileStatus uploadStatus;
final double percentUploading;
final UploadTaskId uploadTaskId;
final double? maxWidth; final double? maxWidth;
final EdgeInsetsGeometry? itemMargin; final EdgeInsetsGeometry? itemMargin;
final EdgeInsetsGeometry? itemPadding; final EdgeInsetsGeometry? itemPadding;
@@ -27,7 +31,12 @@ class AttachmentItemComposerWidget extends StatelessWidget with AppLoaderMixin {
AttachmentItemComposerWidget({ AttachmentItemComposerWidget({
super.key, super.key,
required this.fileState, required this.fileIcon,
required this.fileName,
required this.fileSize,
required this.uploadStatus,
required this.percentUploading,
required this.uploadTaskId,
this.maxWidth, this.maxWidth,
this.itemMargin, this.itemMargin,
this.itemPadding, this.itemPadding,
@@ -55,7 +64,7 @@ class AttachmentItemComposerWidget extends StatelessWidget with AppLoaderMixin {
Row( Row(
children: [ children: [
SvgPicture.asset( SvgPicture.asset(
fileState.getIcon(_imagePaths), fileIcon,
width: AttachmentItemComposerWidgetStyle.iconSize, width: AttachmentItemComposerWidgetStyle.iconSize,
height: AttachmentItemComposerWidgetStyle.iconSize, height: AttachmentItemComposerWidgetStyle.iconSize,
fit: BoxFit.fill fit: BoxFit.fill
@@ -64,7 +73,7 @@ class AttachmentItemComposerWidget extends StatelessWidget with AppLoaderMixin {
Expanded( Expanded(
child: PlatformInfo.isCanvasKit child: PlatformInfo.isCanvasKit
? ExtendedText( ? ExtendedText(
fileState.fileName, fileName,
maxLines: 1, maxLines: 1,
overflowWidget: const TextOverflowWidget( overflowWidget: const TextOverflowWidget(
position: TextOverflowPosition.middle, position: TextOverflowPosition.middle,
@@ -76,7 +85,7 @@ class AttachmentItemComposerWidget extends StatelessWidget with AppLoaderMixin {
style: AttachmentItemComposerWidgetStyle.labelTextStyle, style: AttachmentItemComposerWidgetStyle.labelTextStyle,
) )
: Text( : Text(
fileState.fileName, fileName,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: AttachmentItemComposerWidgetStyle.labelTextStyle, style: AttachmentItemComposerWidgetStyle.labelTextStyle,
@@ -84,14 +93,14 @@ class AttachmentItemComposerWidget extends StatelessWidget with AppLoaderMixin {
), ),
const SizedBox(width: AttachmentItemComposerWidgetStyle.space), const SizedBox(width: AttachmentItemComposerWidgetStyle.space),
Text( Text(
filesize(fileState.fileSize), fileSize,
style: AttachmentItemComposerWidgetStyle.sizeLabelTextStyle style: AttachmentItemComposerWidgetStyle.sizeLabelTextStyle
), ),
], ],
), ),
AttachmentProgressLoadingComposerWidget( AttachmentProgressLoadingComposerWidget(
fileState: fileState, uploadStatus: uploadStatus,
padding: AttachmentItemComposerWidgetStyle.progressLoadingPadding, percentUploading: percentUploading,
) )
], ],
), ),
@@ -103,7 +112,7 @@ class AttachmentItemComposerWidget extends StatelessWidget with AppLoaderMixin {
borderRadius: AttachmentItemComposerWidgetStyle.deleteIconRadius, borderRadius: AttachmentItemComposerWidgetStyle.deleteIconRadius,
padding: AttachmentItemComposerWidgetStyle.deleteIconPadding, padding: AttachmentItemComposerWidgetStyle.deleteIconPadding,
iconColor: AttachmentItemComposerWidgetStyle.deleteIconColor, iconColor: AttachmentItemComposerWidgetStyle.deleteIconColor,
onTapActionCallback: () => onDeleteAttachmentAction?.call(fileState.uploadTaskId), onTapActionCallback: () => onDeleteAttachmentAction?.call(uploadTaskId),
) )
], ],
), ),
@@ -1,27 +1,27 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:percent_indicator/linear_percent_indicator.dart'; import 'package:percent_indicator/linear_percent_indicator.dart';
import 'package:tmail_ui_user/features/composer/presentation/styles/attachment_item_composer_widget_style.dart';
import 'package:tmail_ui_user/features/composer/presentation/styles/attachment_progress_loading_composer_widget_style.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'; import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_status.dart';
class AttachmentProgressLoadingComposerWidget extends StatelessWidget { class AttachmentProgressLoadingComposerWidget extends StatelessWidget {
final UploadFileState fileState; final UploadFileStatus uploadStatus;
final EdgeInsetsGeometry? padding; final double percentUploading;
const AttachmentProgressLoadingComposerWidget({ const AttachmentProgressLoadingComposerWidget({
super.key, super.key,
required this.fileState, required this.uploadStatus,
this.padding, required this.percentUploading,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
switch (fileState.uploadStatus) { switch (uploadStatus) {
case UploadFileStatus.waiting: case UploadFileStatus.waiting:
return Padding( return const Padding(
padding: padding ?? EdgeInsets.zero, padding: AttachmentItemComposerWidgetStyle.progressLoadingPadding,
child: const LinearProgressIndicator( child: LinearProgressIndicator(
color: AttachmentProgressLoadingComposerWidgetStyle.progressColor, color: AttachmentProgressLoadingComposerWidgetStyle.progressColor,
minHeight: AttachmentProgressLoadingComposerWidgetStyle.height, minHeight: AttachmentProgressLoadingComposerWidgetStyle.height,
backgroundColor: AttachmentProgressLoadingComposerWidgetStyle.backgroundColor, backgroundColor: AttachmentProgressLoadingComposerWidgetStyle.backgroundColor,
@@ -29,13 +29,13 @@ class AttachmentProgressLoadingComposerWidget extends StatelessWidget {
); );
case UploadFileStatus.uploading: case UploadFileStatus.uploading:
return Padding( return Padding(
padding: padding ?? EdgeInsets.zero, padding: AttachmentItemComposerWidgetStyle.progressLoadingPadding,
child: LinearPercentIndicator( child: LinearPercentIndicator(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
lineHeight:AttachmentProgressLoadingComposerWidgetStyle.height, lineHeight:AttachmentProgressLoadingComposerWidgetStyle.height,
percent: fileState.percentUploading > 1.0 percent: percentUploading > 1.0
? 1.0 ? 1.0
: fileState.percentUploading, : percentUploading,
barRadius: const Radius.circular(AttachmentProgressLoadingComposerWidgetStyle.radius), barRadius: const Radius.circular(AttachmentProgressLoadingComposerWidgetStyle.radius),
backgroundColor: AttachmentProgressLoadingComposerWidgetStyle.backgroundColor, backgroundColor: AttachmentProgressLoadingComposerWidgetStyle.backgroundColor,
progressColor: AttachmentProgressLoadingComposerWidgetStyle.progressColor, progressColor: AttachmentProgressLoadingComposerWidgetStyle.progressColor,
@@ -3,6 +3,7 @@ import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:core/presentation/views/list/sliver_grid_delegate_fixed_height.dart'; import 'package:core/presentation/views/list/sliver_grid_delegate_fixed_height.dart';
import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:tmail_ui_user/features/composer/presentation/styles/attachment_item_composer_widget_style.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/attachment_item_composer_widget_style.dart';
@@ -73,8 +74,14 @@ class _MobileAttachmentComposerWidgetState extends State<MobileAttachmentCompose
primary: false, primary: false,
itemCount: _listFileDisplayed.length, itemCount: _listFileDisplayed.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final file = _listFileDisplayed[index];
return AttachmentItemComposerWidget( return AttachmentItemComposerWidget(
fileState: _listFileDisplayed[index], fileIcon: file.getIcon(_imagePaths),
fileName: file.fileName,
fileSize: filesize(file.fileSize),
uploadStatus: file.uploadStatus,
percentUploading: file.percentUploading,
uploadTaskId: file.uploadTaskId,
itemMargin: MobileAttachmentComposerWidgetStyle.itemMargin, itemMargin: MobileAttachmentComposerWidgetStyle.itemMargin,
onDeleteAttachmentAction: widget.onDeleteAttachmentAction onDeleteAttachmentAction: widget.onDeleteAttachmentAction
); );
@@ -130,8 +137,14 @@ class _MobileAttachmentComposerWidgetState extends State<MobileAttachmentCompose
crossAxisSpacing: MobileAttachmentComposerWidgetStyle.listItemSpace, crossAxisSpacing: MobileAttachmentComposerWidgetStyle.listItemSpace,
), ),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final file = _listFileDisplayed[index];
return AttachmentItemComposerWidget( return AttachmentItemComposerWidget(
fileState: _listFileDisplayed[index], fileIcon: file.getIcon(_imagePaths),
fileName: file.fileName,
fileSize: filesize(file.fileSize),
uploadStatus: file.uploadStatus,
percentUploading: file.percentUploading,
uploadTaskId: file.uploadTaskId,
itemMargin: MobileAttachmentComposerWidgetStyle.itemMargin, itemMargin: MobileAttachmentComposerWidgetStyle.itemMargin,
itemPadding: const EdgeInsets.symmetric(horizontal: 8), itemPadding: const EdgeInsets.symmetric(horizontal: 8),
onDeleteAttachmentAction: widget.onDeleteAttachmentAction onDeleteAttachmentAction: widget.onDeleteAttachmentAction
@@ -1,5 +1,8 @@
import 'package:core/presentation/resources/image_paths.dart';
import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/composer/presentation/styles/web/attachment_composer_widget_style.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/web/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_item_composer_widget.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/attachment_header_composer_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/attachment_header_composer_widget.dart';
@@ -28,6 +31,7 @@ class AttachmentComposerWidget extends StatefulWidget {
class _AttachmentComposerWidgetState extends State<AttachmentComposerWidget> { class _AttachmentComposerWidgetState extends State<AttachmentComposerWidget> {
final ImagePaths _imagePaths = Get.find<ImagePaths>();
bool _isCollapsed = false; bool _isCollapsed = false;
@override @override
@@ -36,6 +40,12 @@ class _AttachmentComposerWidgetState extends State<AttachmentComposerWidget> {
_isCollapsed = widget.isCollapsed; _isCollapsed = widget.isCollapsed;
} }
@override
void dispose() {
_isCollapsed = false;
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
@@ -66,8 +76,13 @@ class _AttachmentComposerWidgetState extends State<AttachmentComposerWidget> {
runSpacing: AttachmentComposerWidgetStyle.listItemSpace, runSpacing: AttachmentComposerWidgetStyle.listItemSpace,
children: widget.listFileUploaded children: widget.listFileUploaded
.map((file) => AttachmentItemComposerWidget( .map((file) => AttachmentItemComposerWidget(
fileState: file, fileIcon: file.getIcon(_imagePaths),
onDeleteAttachmentAction: widget.onDeleteAttachmentAction fileName: file.fileName,
fileSize: filesize(file.fileSize),
uploadStatus: file.uploadStatus,
percentUploading: file.percentUploading,
uploadTaskId: file.uploadTaskId,
onDeleteAttachmentAction: widget.onDeleteAttachmentAction,
)) ))
.toList(), .toList(),
), ),
@@ -1,4 +1,6 @@
import 'dart:async';
import 'package:async/async.dart'; import 'package:async/async.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/extensions/color_extension.dart';
@@ -45,6 +47,9 @@ class UploadController extends BaseController {
final StreamGroup<Either<Failure, Success>> _progressUploadInlineImageStateStreamGroup final StreamGroup<Either<Failure, Success>> _progressUploadInlineImageStateStreamGroup
= StreamGroup<Either<Failure, Success>>.broadcast(); = StreamGroup<Either<Failure, Success>>.broadcast();
StreamSubscription? _uploadAttachmentStreamSubscription;
StreamSubscription? _uploadInlineImageStreamSubscription;
final UploadFileStateList _uploadingStateFiles = UploadFileStateList(); final UploadFileStateList _uploadingStateFiles = UploadFileStateList();
final UploadFileStateList _uploadingStateInlineFiles = UploadFileStateList(); final UploadFileStateList _uploadingStateInlineFiles = UploadFileStateList();
@@ -58,17 +63,21 @@ class UploadController extends BaseController {
@override @override
void onClose() { void onClose() {
listUploadAttachments.clear();
_uploadingStateFiles.clear(); _uploadingStateFiles.clear();
_progressUploadStateStreamGroup.close();
_uploadingStateInlineFiles.clear(); _uploadingStateInlineFiles.clear();
_progressUploadInlineImageStateStreamGroup.close(); uploadInlineViewState.value = Right(UIClosedState());
dispatchState(Right(UIClosedState())); dispatchState(Right(UIClosedState()));
_progressUploadStateStreamGroup.close();
_progressUploadInlineImageStateStreamGroup.close();
_uploadAttachmentStreamSubscription?.cancel();
_uploadInlineImageStreamSubscription?.cancel();
super.onClose(); super.onClose();
} }
void _registerProgressUploadStateStream() { void _registerProgressUploadStateStream() {
_progressUploadStateStreamGroup.stream.listen(_handleProgressUploadStateStream); _uploadAttachmentStreamSubscription = _progressUploadStateStreamGroup.stream.listen(_handleProgressUploadStateStream);
_progressUploadInlineImageStateStreamGroup.stream.listen(_handleProgressUploadInlineImageStateStream); _uploadInlineImageStreamSubscription = _progressUploadInlineImageStateStreamGroup.stream.listen(_handleProgressUploadInlineImageStateStream);
} }
void _handleProgressUploadStateStream(Either<Failure, Success> uploadState) { void _handleProgressUploadStateStream(Either<Failure, Success> uploadState) {