From 3eb8635a02a6e93732d26295f4dbe82c863b144f Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Tue, 29 Oct 2024 13:54:08 +0100 Subject: [PATCH 01/41] refactor: Share code of 'reportDisplayProblem' --- .../com/infomaniak/mail/data/api/ApiRepository.kt | 9 +++++++++ .../java/com/infomaniak/mail/ui/MainViewModel.kt | 14 +++----------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt b/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt index 152af001c3..1d6bff7ccf 100644 --- a/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt +++ b/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt @@ -451,6 +451,15 @@ object ApiRepository : ApiRepositoryCore() { return callApi(url = ApiRoutes.shareLink(mailboxUuid, folderId, mailId), method = POST) } + fun getDownloadedAttachment(mailboxUuid: String, folderId: String, shortUid: Int): Response { + val request = Request.Builder().url(ApiRoutes.downloadMessage(mailboxUuid, folderId, shortUid)) + .headers(HttpUtils.getHeaders(null)) + .get() + .build() + + return HttpClient.okHttpClient.newCall(request).execute() + } + fun getMyKSuiteData(okHttpClient: OkHttpClient): ApiResponse { return ApiController.callApi( url = MyKSuiteApiRoutes.myKSuiteData(), diff --git a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt index 775240b2b2..a3596119c7 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt @@ -18,8 +18,10 @@ package com.infomaniak.mail.ui import android.app.Application +import android.util.Log import androidx.lifecycle.* import com.infomaniak.lib.core.models.ApiResponse +import com.infomaniak.lib.core.networking.HttpClient import com.infomaniak.lib.core.networking.HttpUtils import com.infomaniak.lib.core.networking.NetworkAvailability import com.infomaniak.lib.core.utils.ApiErrorCode.Companion.translateError @@ -1019,19 +1021,9 @@ class MainViewModel @Inject constructor( fun reportDisplayProblem(messageUid: String) = viewModelScope.launch(ioCoroutineContext) { val message = messageController.getMessage(messageUid) ?: return@launch - val mailbox = currentMailbox.value ?: return@launch - val userApiToken = AccountUtils.getUserById(mailbox.userId)?.apiToken?.accessToken ?: return@launch - val headers = HttpUtils.getHeaders(contentType = null).newBuilder() - .set("Authorization", "Bearer $userApiToken") - .build() - val request = Request.Builder().url(ApiRoutes.downloadMessage(mailbox.uuid, message.folderId, message.shortUid)) - .headers(headers) - .get() - .build() - - val response = AccountUtils.getHttpClient(mailbox.userId).newCall(request).execute() + val response = ApiRepository.getDownloadedAttachment(mailbox.uuid, message.folderId, message.shortUid) if (!response.isSuccessful || response.body == null) { reportDisplayProblemTrigger.postValue(Unit) From 280513cd5296f87e4f234055d71610d2da40bb4f Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Tue, 29 Oct 2024 14:42:32 +0100 Subject: [PATCH 02/41] feat: Add save to kDrive option --- app/src/main/java/com/infomaniak/mail/MatomoMail.kt | 1 + .../main/thread/actions/MailActionsBottomSheetDialog.kt | 3 +++ .../thread/actions/MessageActionsBottomSheetDialog.kt | 6 ++++++ .../thread/actions/ThreadActionsBottomSheetDialog.kt | 5 +++++ app/src/main/res/layout/bottom_sheet_actions_menu.xml | 9 +++++++++ 5 files changed, 24 insertions(+) diff --git a/app/src/main/java/com/infomaniak/mail/MatomoMail.kt b/app/src/main/java/com/infomaniak/mail/MatomoMail.kt index 92d2c9d1ff..20df8890ac 100644 --- a/app/src/main/java/com/infomaniak/mail/MatomoMail.kt +++ b/app/src/main/java/com/infomaniak/mail/MatomoMail.kt @@ -56,6 +56,7 @@ object MatomoMail : MatomoCore { const val ACTION_SPAM_NAME = "spam" const val ACTION_PRINT_NAME = "print" const val ACTION_SHARE_LINK_NAME = "shareLink" + const val ACTION_SAVE_KDRIVE_NAME = "saveInkDrive" const val ACTION_POSTPONE_NAME = "postpone" const val ADD_MAILBOX_NAME = "addMailbox" const val DISCOVER_LATER = "discoverLater" diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MailActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MailActionsBottomSheetDialog.kt index b7570be3ff..83fa62a87a 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MailActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MailActionsBottomSheetDialog.kt @@ -57,6 +57,7 @@ abstract class MailActionsBottomSheetDialog : ActionsBottomSheetDialog() { override fun onReportJunk() = Unit override fun onPrint() = Unit override fun onShare() = Unit + override fun onSaveKDrive() = Unit override fun onReportDisplayProblem() = Unit //endregion } @@ -78,6 +79,7 @@ abstract class MailActionsBottomSheetDialog : ActionsBottomSheetDialog() { reportJunk.setClosingOnClickListener(shouldCloseMultiSelection) { onClickListener.onReportJunk() } print.setClosingOnClickListener(shouldCloseMultiSelection) { onClickListener.onPrint() } share.setClosingOnClickListener(shouldCloseMultiSelection) { onClickListener.onShare() } + saveKDrive.setClosingOnClickListener(shouldCloseMultiSelection) { onClickListener.onSaveKDrive() } reportDisplayProblem.setClosingOnClickListener(shouldCloseMultiSelection) { onClickListener.onReportDisplayProblem() } mainActions.setClosingOnClickListener(shouldCloseMultiSelection) { id: Int -> @@ -142,6 +144,7 @@ abstract class MailActionsBottomSheetDialog : ActionsBottomSheetDialog() { fun onReportJunk() fun onPrint() fun onShare() + fun onSaveKDrive() fun onReportDisplayProblem() } } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt index 0cccbc3418..858ae41056 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt @@ -33,6 +33,7 @@ import com.infomaniak.mail.MatomoMail.ACTION_POSTPONE_NAME import com.infomaniak.mail.MatomoMail.ACTION_PRINT_NAME import com.infomaniak.mail.MatomoMail.ACTION_REPLY_ALL_NAME import com.infomaniak.mail.MatomoMail.ACTION_REPLY_NAME +import com.infomaniak.mail.MatomoMail.ACTION_SAVE_KDRIVE_NAME import com.infomaniak.mail.MatomoMail.ACTION_SHARE_LINK_NAME import com.infomaniak.mail.MatomoMail.trackBottomSheetMessageActionsEvent import com.infomaniak.mail.MatomoMail.trackBottomSheetThreadActionsEvent @@ -168,6 +169,11 @@ class MessageActionsBottomSheetDialog : MailActionsBottomSheetDialog() { } } + override fun onSaveKDrive() { + trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) + + } + override fun onReportDisplayProblem() { descriptionDialog.show( title = getString(R.string.reportDisplayProblemTitle), diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt index bf2b0b6005..2284575440 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt @@ -35,6 +35,7 @@ import com.infomaniak.mail.MatomoMail.ACTION_POSTPONE_NAME import com.infomaniak.mail.MatomoMail.ACTION_PRINT_NAME import com.infomaniak.mail.MatomoMail.ACTION_REPLY_ALL_NAME import com.infomaniak.mail.MatomoMail.ACTION_REPLY_NAME +import com.infomaniak.mail.MatomoMail.ACTION_SAVE_KDRIVE_NAME import com.infomaniak.mail.MatomoMail.ACTION_SHARE_LINK_NAME import com.infomaniak.mail.MatomoMail.ACTION_SPAM_NAME import com.infomaniak.mail.MatomoMail.trackBottomSheetThreadActionsEvent @@ -195,6 +196,10 @@ class ThreadActionsBottomSheetDialog : MailActionsBottomSheetDialog() { } } + override fun onSaveKDrive() { + trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) + } + override fun onReportDisplayProblem() { descriptionDialog.show( title = getString(R.string.reportDisplayProblemTitle), diff --git a/app/src/main/res/layout/bottom_sheet_actions_menu.xml b/app/src/main/res/layout/bottom_sheet_actions_menu.xml index c0451e5cb7..3b50d04336 100644 --- a/app/src/main/res/layout/bottom_sheet_actions_menu.xml +++ b/app/src/main/res/layout/bottom_sheet_actions_menu.xml @@ -118,6 +118,15 @@ app:title="@string/shareEmail" app:visibleDivider="false" /> + + Date: Tue, 29 Oct 2024 17:26:54 +0100 Subject: [PATCH 03/41] feat: Save eml to kDrive --- app/build.gradle | 2 + app/src/main/AndroidManifest.xml | 2 +- .../com/infomaniak/mail/ui/MainViewModel.kt | 55 +++++++++++++++++-- .../MessageActionsBottomSheetDialog.kt | 2 +- .../actions/ThreadActionsBottomSheetDialog.kt | 1 + .../utils/extensions/AttachmentExtensions.kt | 19 +++++++ app/src/main/res/xml/exposed_files_path.xml | 9 +++ 7 files changed, 83 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 118210149d..97e276951f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -47,6 +47,8 @@ android { buildConfigField 'String', 'GITHUB_REPO_URL', '"https://github.com/Infomaniak/android-kMail"' resValue 'string', 'ATTACHMENTS_AUTHORITY', 'com.infomaniak.mail.attachments' + resValue 'string', 'EML_AUTHORITY', 'com.infomaniak.mail.eml' + resValue 'string', 'ALL_AUTHORITY', 'com.infomaniak.mail.attachments;com.infomaniak.mail.eml' resourceConfigurations += ["en", "de", "es", "fr", "it"] } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5e9fe00ae8..d092160581 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -204,7 +204,7 @@ + context?.let { + val uri = saveEmlToFile(context, byteArray, message.subject ?: "pas de nom test") + uri?.getIntentOrGoToPlayStore(it) + } + } + + } + + fun saveEmlToFile(context: Context, byteArray: ByteArray, fileName: String): Uri? { + val fileNameWithExtension = "$fileName.eml" + val fileDir = File(context.filesDir, "eml_export") + + if (!fileDir.exists()) { + fileDir.mkdirs() + } + + val file = File(fileDir, fileNameWithExtension) + + return try { + file.outputStream().use { it.write(byteArray) } + FileProvider.getUriForFile(context, context.getString(R.string.EML_AUTHORITY), file) + } catch (e: IOException) { + e.printStackTrace() + null + } + } + + companion object { private val TAG: String = MainViewModel::class.java.simpleName private val DEFAULT_SELECTED_FOLDER = FolderRole.INBOX private const val REFRESH_DELAY = 2_000L // We add this delay because `etop` isn't always big enough. private const val MAX_REFRESH_DELAY = 6_000L private const val EML_CONTENT_TYPE = "message/rfc822" + + private const val EML_EXPORT_DIR = "eml_export" } } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt index 858ae41056..f381bb0f5e 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt @@ -171,7 +171,7 @@ class MessageActionsBottomSheetDialog : MailActionsBottomSheetDialog() { override fun onSaveKDrive() { trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) - + mainViewModel.saveOnKDrive(message.uid, context) } override fun onReportDisplayProblem() { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt index 2284575440..1a65793dc4 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt @@ -197,6 +197,7 @@ class ThreadActionsBottomSheetDialog : MailActionsBottomSheetDialog() { } override fun onSaveKDrive() { + mainViewModel.saveOnKDrive(messageUidToReply, context) trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) } diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt index 4bbd19efbc..5b1cd92292 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt @@ -21,6 +21,7 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.net.Uri import android.os.Bundle import android.provider.MediaStore.Files.FileColumns import androidx.core.content.FileProvider @@ -81,6 +82,14 @@ object AttachmentExtensions { } } + private fun Uri.saveToDriveIntent(): Intent { + return Intent().apply { + component = ComponentName(DRIVE_PACKAGE, SAVE_EXTERNAL_ACTIVITY_CLASS) + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_STREAM, this@saveToDriveIntent) + } + } + private fun Attachment.openWithIntent(context: Context): Intent { val file = getUploadLocalFile() ?: getCacheFile(context) val uri = FileProvider.getUriForFile(context, context.getString(R.string.ATTACHMENTS_AUTHORITY), file) @@ -102,6 +111,15 @@ object AttachmentExtensions { } } + // TODO Keep same logic + fun Uri.getIntentOrGoToPlayStore(context: Context) { + if (canSaveOnKDrive(context)) { + saveToDriveIntent().let(context::startActivity) + } else { + context.goToPlayStore(DRIVE_PACKAGE) + } + } + fun Attachment.executeIntent( context: Context, intentType: AttachmentIntentType, @@ -114,6 +132,7 @@ object AttachmentExtensions { } } + fun Attachment.openAttachment( context: Context, navigateToDownloadProgressDialog: (Attachment, AttachmentIntentType) -> Unit, diff --git a/app/src/main/res/xml/exposed_files_path.xml b/app/src/main/res/xml/exposed_files_path.xml index 628e54431c..cedc5e5c45 100644 --- a/app/src/main/res/xml/exposed_files_path.xml +++ b/app/src/main/res/xml/exposed_files_path.xml @@ -22,4 +22,13 @@ + + + + + + + From 90da7f5c51c53b6ebe90cfab4cb1d94090405b96 Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Wed, 30 Oct 2024 09:20:28 +0100 Subject: [PATCH 04/41] refactor: Rename & Clean code --- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 2 +- .../com/infomaniak/mail/ui/MainViewModel.kt | 31 +++++++++---------- .../actions/ThreadActionsBottomSheetDialog.kt | 2 +- .../utils/extensions/AttachmentExtensions.kt | 4 +-- app/src/main/res/xml/exposed_files_path.xml | 6 ---- 6 files changed, 18 insertions(+), 29 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 97e276951f..3e594c74f9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -48,7 +48,7 @@ android { resValue 'string', 'ATTACHMENTS_AUTHORITY', 'com.infomaniak.mail.attachments' resValue 'string', 'EML_AUTHORITY', 'com.infomaniak.mail.eml' - resValue 'string', 'ALL_AUTHORITY', 'com.infomaniak.mail.attachments;com.infomaniak.mail.eml' + resValue 'string', 'FILES_AUTHORITY', 'com.infomaniak.mail.attachments;com.infomaniak.mail.eml' resourceConfigurations += ["en", "de", "es", "fr", "it"] } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d092160581..7cb0209dbf 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -204,7 +204,7 @@ context?.let { - val uri = saveEmlToFile(context, byteArray, message.subject ?: "pas de nom test") - uri?.getIntentOrGoToPlayStore(it) + val emlFileName: String = message.subject ?: NO_SUBJECT_FILE + val uri = saveEmlToFile(context, byteArray, emlFileName) + uri?.openKDriveOrPlayStore(it) } } - } - fun saveEmlToFile(context: Context, byteArray: ByteArray, fileName: String): Uri? { + fun saveEmlToFile(context: Context, emlByteArray: ByteArray, fileName: String): Uri? { val fileNameWithExtension = "$fileName.eml" - val fileDir = File(context.filesDir, "eml_export") - - if (!fileDir.exists()) { - fileDir.mkdirs() - } + val fileDir = File(context.filesDir, EML_EXPORT_DIR) - val file = File(fileDir, fileNameWithExtension) + if (!fileDir.exists()) fileDir.mkdirs() return try { - file.outputStream().use { it.write(byteArray) } + val file = File(fileDir, fileNameWithExtension) + file.outputStream().use { it.write(emlByteArray) } FileProvider.getUriForFile(context, context.getString(R.string.EML_AUTHORITY), file) - } catch (e: IOException) { - e.printStackTrace() + } catch (_: IOException) { + snackbarManager.postValue(appContext.getString(RCore.string.anErrorHasOccurred)) null } } @@ -1347,8 +1343,9 @@ class MainViewModel @Inject constructor( private val DEFAULT_SELECTED_FOLDER = FolderRole.INBOX private const val REFRESH_DELAY = 2_000L // We add this delay because `etop` isn't always big enough. private const val MAX_REFRESH_DELAY = 6_000L - private const val EML_CONTENT_TYPE = "message/rfc822" + private const val EML_CONTENT_TYPE = "message/rfc822" private const val EML_EXPORT_DIR = "eml_export" + private const val NO_SUBJECT_FILE = "message" } } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt index 1a65793dc4..ae5b9fdee8 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt @@ -197,8 +197,8 @@ class ThreadActionsBottomSheetDialog : MailActionsBottomSheetDialog() { } override fun onSaveKDrive() { - mainViewModel.saveOnKDrive(messageUidToReply, context) trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) + mainViewModel.saveOnKDrive(messageUidToReply, context) } override fun onReportDisplayProblem() { diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt index 5b1cd92292..d2a2c7b755 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt @@ -111,8 +111,7 @@ object AttachmentExtensions { } } - // TODO Keep same logic - fun Uri.getIntentOrGoToPlayStore(context: Context) { + fun Uri.openKDriveOrPlayStore(context: Context) { if (canSaveOnKDrive(context)) { saveToDriveIntent().let(context::startActivity) } else { @@ -132,7 +131,6 @@ object AttachmentExtensions { } } - fun Attachment.openAttachment( context: Context, navigateToDownloadProgressDialog: (Attachment, AttachmentIntentType) -> Unit, diff --git a/app/src/main/res/xml/exposed_files_path.xml b/app/src/main/res/xml/exposed_files_path.xml index cedc5e5c45..e771e44a14 100644 --- a/app/src/main/res/xml/exposed_files_path.xml +++ b/app/src/main/res/xml/exposed_files_path.xml @@ -25,10 +25,4 @@ - - - - - - From 8986eb9ceda8b7cff0cd7f72dff2f890a1b9fe51 Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Thu, 31 Oct 2024 12:56:31 +0100 Subject: [PATCH 05/41] feat: Replace illegal file characters with a blank space --- app/build.gradle | 3 +++ .../java/com/infomaniak/mail/ui/MainViewModel.kt | 16 ++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3e594c74f9..2aae07850b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,9 @@ android { resValue 'string', 'EML_AUTHORITY', 'com.infomaniak.mail.eml' resValue 'string', 'FILES_AUTHORITY', 'com.infomaniak.mail.attachments;com.infomaniak.mail.eml' + resValue 'string', 'EXPOSED_EML_DIR', 'eml' + resValue 'string', 'EXPOSED_EML_PATH', 'eml_export' + resourceConfigurations += ["en", "de", "es", "fr", "it"] } diff --git a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt index 28e04980e7..80f7a0a000 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt @@ -78,7 +78,6 @@ import io.sentry.SentryLevel import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import java.io.File -import java.io.IOException import java.util.Date import java.util.UUID import javax.inject.Inject @@ -1322,21 +1321,23 @@ class MainViewModel @Inject constructor( } fun saveEmlToFile(context: Context, emlByteArray: ByteArray, fileName: String): Uri? { - val fileNameWithExtension = "$fileName.eml" - val fileDir = File(context.filesDir, EML_EXPORT_DIR) + val fileNameWithExtension = "${fileName.removeIllegalFileNameCharacter()}.eml" + val fileDir = File(context.filesDir, context.getString(R.string.EXPOSED_EML_PATH)) if (!fileDir.exists()) fileDir.mkdirs() - return try { + runCatching { val file = File(fileDir, fileNameWithExtension) file.outputStream().use { it.write(emlByteArray) } - FileProvider.getUriForFile(context, context.getString(R.string.EML_AUTHORITY), file) - } catch (_: IOException) { + return FileProvider.getUriForFile(context, context.getString(R.string.EML_AUTHORITY), file) + }.onFailure { exception -> + exception.printStackTrace() snackbarManager.postValue(appContext.getString(RCore.string.anErrorHasOccurred)) - null } + return null } + fun String.removeIllegalFileNameCharacter(): String = this.replace(DownloadManagerUtils.regexInvalidSystemChar, "") companion object { private val TAG: String = MainViewModel::class.java.simpleName @@ -1345,7 +1346,6 @@ class MainViewModel @Inject constructor( private const val MAX_REFRESH_DELAY = 6_000L private const val EML_CONTENT_TYPE = "message/rfc822" - private const val EML_EXPORT_DIR = "eml_export" private const val NO_SUBJECT_FILE = "message" } } From 17dbf990f811ffde762a17578b89e3e1d966bc67 Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Thu, 31 Oct 2024 13:53:48 +0100 Subject: [PATCH 06/41] feat: Allow to download a thread instead of only one message --- app/build.gradle | 1 - .../com/infomaniak/mail/ui/MainViewModel.kt | 27 ++++++++++--------- .../MessageActionsBottomSheetDialog.kt | 2 +- .../actions/ThreadActionsBottomSheetDialog.kt | 2 +- .../utils/extensions/AttachmentExtensions.kt | 8 +++--- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 2aae07850b..872927fe41 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,7 +50,6 @@ android { resValue 'string', 'EML_AUTHORITY', 'com.infomaniak.mail.eml' resValue 'string', 'FILES_AUTHORITY', 'com.infomaniak.mail.attachments;com.infomaniak.mail.eml' - resValue 'string', 'EXPOSED_EML_DIR', 'eml' resValue 'string', 'EXPOSED_EML_PATH', 'eml_export' resourceConfigurations += ["en", "de", "es", "fr", "it"] diff --git a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt index 80f7a0a000..50fa60f178 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt @@ -1299,25 +1299,28 @@ class MainViewModel @Inject constructor( } } - fun saveOnKDrive(messageUid: String, context: Context?) = viewModelScope.launch(ioCoroutineContext) { - val message = messageController.getMessage(messageUid) ?: return@launch + fun saveOnKDrive(threadUid: String, context: Context?) = viewModelScope.launch(ioCoroutineContext) { + val thread = threadController.getThread(threadUid) ?: return@launch val mailbox = currentMailbox.value ?: return@launch - val response = ApiRepository.getDownloadedAttachment(mailbox.uuid, message.folderId, message.shortUid) + val listUri: MutableList = mutableListOf() + thread.messages.forEach { message -> + val response = ApiRepository.getDownloadedAttachment(mailbox.uuid, message.folderId, message.shortUid) - if (!response.isSuccessful || response.body == null) { - snackbarManager.postValue(appContext.getString(RCore.string.anErrorHasOccurred)) + if (!response.isSuccessful || response.body == null) { + snackbarManager.postValue(appContext.getString(RCore.string.anErrorHasOccurred)) - return@launch - } + return@launch + } - response.body?.bytes()?.let { byteArray -> - context?.let { - val emlFileName: String = message.subject ?: NO_SUBJECT_FILE - val uri = saveEmlToFile(context, byteArray, emlFileName) - uri?.openKDriveOrPlayStore(it) + response.body?.bytes()?.let { byteArray -> + context?.let { + val emlFileName: String = message.subject ?: NO_SUBJECT_FILE + saveEmlToFile(context, byteArray, emlFileName)?.let { listUri.add(it) } + } } } + context?.let { ArrayList(listUri).openKDriveOrPlayStore(it) } } fun saveEmlToFile(context: Context, emlByteArray: ByteArray, fileName: String): Uri? { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt index f381bb0f5e..5788a5d7e6 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt @@ -171,7 +171,7 @@ class MessageActionsBottomSheetDialog : MailActionsBottomSheetDialog() { override fun onSaveKDrive() { trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) - mainViewModel.saveOnKDrive(message.uid, context) + mainViewModel.saveOnKDrive(threadUid, context) } override fun onReportDisplayProblem() { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt index ae5b9fdee8..0213cfbcc2 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt @@ -198,7 +198,7 @@ class ThreadActionsBottomSheetDialog : MailActionsBottomSheetDialog() { override fun onSaveKDrive() { trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) - mainViewModel.saveOnKDrive(messageUidToReply, context) + mainViewModel.saveOnKDrive(threadUid, context) } override fun onReportDisplayProblem() { diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt index d2a2c7b755..3c092c6074 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt @@ -82,11 +82,11 @@ object AttachmentExtensions { } } - private fun Uri.saveToDriveIntent(): Intent { + private fun ArrayList.saveToDriveIntent(): Intent { return Intent().apply { component = ComponentName(DRIVE_PACKAGE, SAVE_EXTERNAL_ACTIVITY_CLASS) - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_STREAM, this@saveToDriveIntent) + action = Intent.ACTION_SEND_MULTIPLE + putParcelableArrayListExtra(Intent.EXTRA_STREAM, this@saveToDriveIntent) } } @@ -111,7 +111,7 @@ object AttachmentExtensions { } } - fun Uri.openKDriveOrPlayStore(context: Context) { + fun ArrayList.openKDriveOrPlayStore(context: Context) { if (canSaveOnKDrive(context)) { saveToDriveIntent().let(context::startActivity) } else { From d54ebc32fbd012d281137f77e44bc40930c20b63 Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Thu, 31 Oct 2024 15:38:33 +0100 Subject: [PATCH 07/41] feat: Add option in 'MultiSelectBottomSheet' --- .../com/infomaniak/mail/ui/MainViewModel.kt | 35 +++++++++++-------- .../MessageActionsBottomSheetDialog.kt | 2 +- .../actions/MultiSelectBottomSheetDialog.kt | 6 ++++ .../actions/ThreadActionsBottomSheetDialog.kt | 2 +- .../res/layout/bottom_sheet_multi_select.xml | 8 +++++ 5 files changed, 36 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt index 50fa60f178..5ed11ee1fe 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt @@ -1299,28 +1299,33 @@ class MainViewModel @Inject constructor( } } - fun saveOnKDrive(threadUid: String, context: Context?) = viewModelScope.launch(ioCoroutineContext) { - val thread = threadController.getThread(threadUid) ?: return@launch - val mailbox = currentMailbox.value ?: return@launch + fun saveOnKDrive(threadUid: List, context: Context?) { + viewModelScope.launch(ioCoroutineContext) { + val mailbox = currentMailbox.value ?: return@launch + val listUri: MutableList = mutableListOf() - val listUri: MutableList = mutableListOf() - thread.messages.forEach { message -> - val response = ApiRepository.getDownloadedAttachment(mailbox.uuid, message.folderId, message.shortUid) + threadUid.forEach { + val thread = threadController.getThread(it) ?: return@launch - if (!response.isSuccessful || response.body == null) { - snackbarManager.postValue(appContext.getString(RCore.string.anErrorHasOccurred)) + thread.messages.forEach { message -> + val response = ApiRepository.getDownloadedAttachment(mailbox.uuid, message.folderId, message.shortUid) - return@launch - } + if (!response.isSuccessful || response.body == null) { + snackbarManager.postValue(appContext.getString(RCore.string.anErrorHasOccurred)) - response.body?.bytes()?.let { byteArray -> - context?.let { - val emlFileName: String = message.subject ?: NO_SUBJECT_FILE - saveEmlToFile(context, byteArray, emlFileName)?.let { listUri.add(it) } + return@launch + } + + response.body?.bytes()?.let { byteArray -> + context?.let { + val emlFileName: String = message.subject ?: NO_SUBJECT_FILE + saveEmlToFile(context, byteArray, emlFileName)?.let { listUri.add(it) } + } + } } } + context?.let { ArrayList(listUri).openKDriveOrPlayStore(it) } } - context?.let { ArrayList(listUri).openKDriveOrPlayStore(it) } } fun saveEmlToFile(context: Context, emlByteArray: ByteArray, fileName: String): Uri? { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt index 5788a5d7e6..3d7ec8794d 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt @@ -171,7 +171,7 @@ class MessageActionsBottomSheetDialog : MailActionsBottomSheetDialog() { override fun onSaveKDrive() { trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) - mainViewModel.saveOnKDrive(threadUid, context) + mainViewModel.saveOnKDrive(listOf(threadUid), context) } override fun onReportDisplayProblem() { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt index 516d89cc30..9130deb81f 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt @@ -28,6 +28,7 @@ import com.infomaniak.mail.MatomoMail.ACTION_DELETE_NAME import com.infomaniak.mail.MatomoMail.ACTION_FAVORITE_NAME import com.infomaniak.mail.MatomoMail.ACTION_MARK_AS_SEEN_NAME import com.infomaniak.mail.MatomoMail.ACTION_MOVE_NAME +import com.infomaniak.mail.MatomoMail.ACTION_SAVE_KDRIVE_NAME import com.infomaniak.mail.MatomoMail.ACTION_SPAM_NAME import com.infomaniak.mail.MatomoMail.trackMultiSelectActionEvent import com.infomaniak.mail.R @@ -118,6 +119,11 @@ class MultiSelectBottomSheetDialog : ActionsBottomSheetDialog() { toggleThreadsFavoriteStatus(threadsUids, shouldFavorite) isMultiSelectOn = false } + binding.saveKDrive.setClosingOnClickListener(shouldCloseMultiSelection = true) { + trackMultiSelectActionEvent(ACTION_SAVE_KDRIVE_NAME, selectedThreadsCount, isFromBottomSheet = true) + saveOnKDrive(selectedThreadsUids, context) + isMultiSelectOn = false + } } private fun setStateDependentUi(shouldRead: Boolean, shouldFavorite: Boolean) { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt index 0213cfbcc2..0b920438b3 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt @@ -198,7 +198,7 @@ class ThreadActionsBottomSheetDialog : MailActionsBottomSheetDialog() { override fun onSaveKDrive() { trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) - mainViewModel.saveOnKDrive(threadUid, context) + mainViewModel.saveOnKDrive(listOf(threadUid), context) } override fun onReportDisplayProblem() { diff --git a/app/src/main/res/layout/bottom_sheet_multi_select.xml b/app/src/main/res/layout/bottom_sheet_multi_select.xml index 32657584f8..b7c64cf974 100644 --- a/app/src/main/res/layout/bottom_sheet_multi_select.xml +++ b/app/src/main/res/layout/bottom_sheet_multi_select.xml @@ -52,4 +52,12 @@ app:icon="@drawable/ic_star" app:title="@string/actionStar" /> + + From a6385e191a741128a5cc6ab469da5a51712d0ae3 Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Fri, 1 Nov 2024 10:18:29 +0100 Subject: [PATCH 08/41] feat: Allow to add different threads or messages with same name --- .../com/infomaniak/mail/ui/MainViewModel.kt | 45 ++++++++++++++----- .../MessageActionsBottomSheetDialog.kt | 2 +- .../actions/MultiSelectBottomSheetDialog.kt | 2 +- .../actions/ThreadActionsBottomSheetDialog.kt | 2 +- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt index 5ed11ee1fe..c334b81bbb 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt @@ -20,6 +20,7 @@ package com.infomaniak.mail.ui import android.app.Application import android.content.Context import android.net.Uri +import android.util.Log import androidx.core.content.FileProvider import androidx.lifecycle.* import com.infomaniak.lib.core.models.ApiResponse @@ -1299,13 +1300,16 @@ class MainViewModel @Inject constructor( } } - fun saveOnKDrive(threadUid: List, context: Context?) { + fun saveOnKDrive(threadUids: List, context: Context) { viewModelScope.launch(ioCoroutineContext) { val mailbox = currentMailbox.value ?: return@launch - val listUri: MutableList = mutableListOf() + val listUri = mutableListOf() + val listFileName = mutableSetOf().also { + it.addAll(getAllFileNameInExportEmlDir(context)) + } - threadUid.forEach { - val thread = threadController.getThread(it) ?: return@launch + threadUids.forEach { threadUid -> + val thread = threadController.getThread(threadUid) ?: return@launch thread.messages.forEach { message -> val response = ApiRepository.getDownloadedAttachment(mailbox.uuid, message.folderId, message.shortUid) @@ -1316,19 +1320,38 @@ class MainViewModel @Inject constructor( return@launch } - response.body?.bytes()?.let { byteArray -> - context?.let { - val emlFileName: String = message.subject ?: NO_SUBJECT_FILE - saveEmlToFile(context, byteArray, emlFileName)?.let { listUri.add(it) } - } + var messageSubject: String = message.subject?.removeIllegalFileNameCharacter() ?: NO_SUBJECT_FILE + createOriginalFileName(messageSubject, listFileName).let { fileName -> + listFileName.add(fileName) + saveEmlToFile(context, response.body!!.bytes(), fileName)?.let { listUri.add(it) } } } } - context?.let { ArrayList(listUri).openKDriveOrPlayStore(it) } + ArrayList(listUri).openKDriveOrPlayStore(context) + } + } + + // TODO Extract this code in core2 + private fun createOriginalFileName(originalFileName: String, listFileName: MutableSet): String { + var postfix = 1 + var fileName = originalFileName + + while (listFileName.contains(fileName)) { + fileName = "$originalFileName (${postfix++})" } + + return fileName + } + + private fun getAllFileNameInExportEmlDir(context: Context): List { + val fileDir = File(context.filesDir, context.getString(R.string.EXPOSED_EML_PATH)) + + if (!fileDir.exists()) fileDir.mkdirs() + + return fileDir.listFiles()?.map { it.name.removeSuffix(".eml") } ?: emptyList() } - fun saveEmlToFile(context: Context, emlByteArray: ByteArray, fileName: String): Uri? { + private fun saveEmlToFile(context: Context, emlByteArray: ByteArray, fileName: String): Uri? { val fileNameWithExtension = "${fileName.removeIllegalFileNameCharacter()}.eml" val fileDir = File(context.filesDir, context.getString(R.string.EXPOSED_EML_PATH)) diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt index 3d7ec8794d..65437343fc 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt @@ -171,7 +171,7 @@ class MessageActionsBottomSheetDialog : MailActionsBottomSheetDialog() { override fun onSaveKDrive() { trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) - mainViewModel.saveOnKDrive(listOf(threadUid), context) + context?.let { mainViewModel.saveOnKDrive(listOf(threadUid), it) } } override fun onReportDisplayProblem() { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt index 9130deb81f..76f979822f 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt @@ -121,7 +121,7 @@ class MultiSelectBottomSheetDialog : ActionsBottomSheetDialog() { } binding.saveKDrive.setClosingOnClickListener(shouldCloseMultiSelection = true) { trackMultiSelectActionEvent(ACTION_SAVE_KDRIVE_NAME, selectedThreadsCount, isFromBottomSheet = true) - saveOnKDrive(selectedThreadsUids, context) + context?.let { saveOnKDrive(selectedThreadsUids, it) } isMultiSelectOn = false } } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt index 0b920438b3..6fd7b6fc84 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt @@ -198,7 +198,7 @@ class ThreadActionsBottomSheetDialog : MailActionsBottomSheetDialog() { override fun onSaveKDrive() { trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) - mainViewModel.saveOnKDrive(listOf(threadUid), context) + context?.let { mainViewModel.saveOnKDrive(listOf(threadUid), it) } } override fun onReportDisplayProblem() { From 686b7da73a70b3d343a5800a597e9f3aaa94640d Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Fri, 1 Nov 2024 10:35:06 +0100 Subject: [PATCH 09/41] feat: Save in cache instead of files --- app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt | 5 ++--- app/src/main/res/xml/exposed_files_path.xml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt index c334b81bbb..d327a68ead 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt @@ -20,7 +20,6 @@ package com.infomaniak.mail.ui import android.app.Application import android.content.Context import android.net.Uri -import android.util.Log import androidx.core.content.FileProvider import androidx.lifecycle.* import com.infomaniak.lib.core.models.ApiResponse @@ -1344,7 +1343,7 @@ class MainViewModel @Inject constructor( } private fun getAllFileNameInExportEmlDir(context: Context): List { - val fileDir = File(context.filesDir, context.getString(R.string.EXPOSED_EML_PATH)) + val fileDir = File(context.cacheDir, context.getString(R.string.EXPOSED_EML_PATH)) if (!fileDir.exists()) fileDir.mkdirs() @@ -1353,7 +1352,7 @@ class MainViewModel @Inject constructor( private fun saveEmlToFile(context: Context, emlByteArray: ByteArray, fileName: String): Uri? { val fileNameWithExtension = "${fileName.removeIllegalFileNameCharacter()}.eml" - val fileDir = File(context.filesDir, context.getString(R.string.EXPOSED_EML_PATH)) + val fileDir = File(context.cacheDir, context.getString(R.string.EXPOSED_EML_PATH)) if (!fileDir.exists()) fileDir.mkdirs() diff --git a/app/src/main/res/xml/exposed_files_path.xml b/app/src/main/res/xml/exposed_files_path.xml index e771e44a14..052c7f1f4b 100644 --- a/app/src/main/res/xml/exposed_files_path.xml +++ b/app/src/main/res/xml/exposed_files_path.xml @@ -22,7 +22,7 @@ - From 25da2961cc1c674802327cf7df3ad027f8b4df1c Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Mon, 18 Nov 2024 09:06:09 +0100 Subject: [PATCH 10/41] refactor: Put all 'clickListener' from message bottom sheet together --- .../MessageActionsBottomSheetDialog.kt | 183 +++++++++--------- 1 file changed, 94 insertions(+), 89 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt index 65437343fc..640468eb97 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt @@ -40,6 +40,7 @@ import com.infomaniak.mail.MatomoMail.trackBottomSheetThreadActionsEvent import com.infomaniak.mail.R import com.infomaniak.mail.data.models.Folder.FolderRole import com.infomaniak.mail.data.models.draft.Draft.DraftMode +import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.ui.alertDialogs.DescriptionAlertDialog import com.infomaniak.mail.ui.main.move.MoveFragmentArgs import com.infomaniak.mail.ui.main.thread.PrintMailFragmentArgs @@ -80,109 +81,113 @@ class MessageActionsBottomSheetDialog : MailActionsBottomSheetDialog() { } } - initOnClickListener(object : OnActionClick { - //region Main actions - override fun onReply() { - trackBottomSheetMessageActionsEvent(ACTION_REPLY_NAME) - safeNavigateToNewMessageActivity( - draftMode = DraftMode.REPLY, - previousMessageUid = messageUid, - currentClassName = currentClassName, - shouldLoadDistantResources = navigationArgs.shouldLoadDistantResources, - ) - } + initActionClickListener(messageUid, message, threadUid) + } + } - override fun onReplyAll() { - trackBottomSheetMessageActionsEvent(ACTION_REPLY_ALL_NAME) - safeNavigateToNewMessageActivity( - draftMode = DraftMode.REPLY_ALL, - previousMessageUid = messageUid, - currentClassName = currentClassName, - shouldLoadDistantResources = navigationArgs.shouldLoadDistantResources, - ) - } + private fun initActionClickListener(messageUid: String, message: Message, threadUid: String) { + initOnClickListener(object : OnActionClick { + //region Main actions + override fun onReply() { + trackBottomSheetMessageActionsEvent(ACTION_REPLY_NAME) + safeNavigateToNewMessageActivity( + draftMode = DraftMode.REPLY, + previousMessageUid = messageUid, + currentClassName = currentClassName, + shouldLoadDistantResources = navigationArgs.shouldLoadDistantResources, + ) + } - override fun onForward() { - trackBottomSheetMessageActionsEvent(ACTION_FORWARD_NAME) - safeNavigateToNewMessageActivity( - draftMode = DraftMode.FORWARD, - previousMessageUid = messageUid, - currentClassName = currentClassName, - shouldLoadDistantResources = navigationArgs.shouldLoadDistantResources, - ) - } + override fun onReplyAll() { + trackBottomSheetMessageActionsEvent(ACTION_REPLY_ALL_NAME) + safeNavigateToNewMessageActivity( + draftMode = DraftMode.REPLY_ALL, + previousMessageUid = messageUid, + currentClassName = currentClassName, + shouldLoadDistantResources = navigationArgs.shouldLoadDistantResources, + ) + } - override fun onDelete() { - descriptionDialog.deleteWithConfirmationPopup(folderRole, count = 1) { - trackBottomSheetMessageActionsEvent(ACTION_DELETE_NAME) - mainViewModel.deleteMessage(threadUid, message) - } - } - //endregion + override fun onForward() { + trackBottomSheetMessageActionsEvent(ACTION_FORWARD_NAME) + safeNavigateToNewMessageActivity( + draftMode = DraftMode.FORWARD, + previousMessageUid = messageUid, + currentClassName = currentClassName, + shouldLoadDistantResources = navigationArgs.shouldLoadDistantResources, + ) + } - //region Actions - override fun onArchive() { - trackBottomSheetMessageActionsEvent(ACTION_ARCHIVE_NAME, message.folder.role == FolderRole.ARCHIVE) - mainViewModel.archiveMessage(threadUid, message) + override fun onDelete() { + descriptionDialog.deleteWithConfirmationPopup(folderRole, count = 1) { + trackBottomSheetMessageActionsEvent(ACTION_DELETE_NAME) + mainViewModel.deleteMessage(threadUid, message) } + } + //endregion - override fun onReadUnread() { - trackBottomSheetMessageActionsEvent(ACTION_MARK_AS_SEEN_NAME, message.isSeen) - mainViewModel.toggleMessageSeenStatus(threadUid, message) - twoPaneViewModel.closeThread() - } + //region Actions + override fun onArchive() { + trackBottomSheetMessageActionsEvent(ACTION_ARCHIVE_NAME, message.folder.role == FolderRole.ARCHIVE) + mainViewModel.archiveMessage(threadUid, message) + } - override fun onMove() { - trackBottomSheetMessageActionsEvent(ACTION_MOVE_NAME) - animatedNavigation( - resId = R.id.moveFragment, - args = MoveFragmentArgs(arrayOf(threadUid), messageUid).toBundle(), - currentClassName = currentClassName, - ) - } + override fun onReadUnread() { + trackBottomSheetMessageActionsEvent(ACTION_MARK_AS_SEEN_NAME, message.isSeen) + mainViewModel.toggleMessageSeenStatus(threadUid, message) + twoPaneViewModel.closeThread() + } - override fun onPostpone() { - trackBottomSheetMessageActionsEvent(ACTION_POSTPONE_NAME) - notYetImplemented() - } + override fun onMove() { + trackBottomSheetMessageActionsEvent(ACTION_MOVE_NAME) + animatedNavigation( + resId = R.id.moveFragment, + args = MoveFragmentArgs(arrayOf(threadUid), messageUid).toBundle(), + currentClassName = currentClassName, + ) + } - override fun onFavorite() { - trackBottomSheetMessageActionsEvent(ACTION_FAVORITE_NAME, message.isFavorite) - mainViewModel.toggleMessageFavoriteStatus(threadUid, message) - } + override fun onPostpone() { + trackBottomSheetMessageActionsEvent(ACTION_POSTPONE_NAME) + notYetImplemented() + } - override fun onReportJunk() = Unit + override fun onFavorite() { + trackBottomSheetMessageActionsEvent(ACTION_FAVORITE_NAME, message.isFavorite) + mainViewModel.toggleMessageFavoriteStatus(threadUid, message) + } - override fun onPrint() { - trackBottomSheetMessageActionsEvent(ACTION_PRINT_NAME) - safeNavigate( - resId = R.id.printMailFragment, - args = PrintMailFragmentArgs(messageUid).toBundle(), - currentClassName = MessageActionsBottomSheetDialog::class.java.name, - ) - } + override fun onReportJunk() = Unit - override fun onShare() { - activity?.apply { - trackBottomSheetThreadActionsEvent(ACTION_SHARE_LINK_NAME) - mainViewModel.shareThreadUrl(message.uid) - } - } + override fun onPrint() { + trackBottomSheetMessageActionsEvent(ACTION_PRINT_NAME) + safeNavigate( + resId = R.id.printMailFragment, + args = PrintMailFragmentArgs(messageUid).toBundle(), + currentClassName = MessageActionsBottomSheetDialog::class.java.name, + ) + } - override fun onSaveKDrive() { - trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) - context?.let { mainViewModel.saveOnKDrive(listOf(threadUid), it) } + override fun onShare() { + activity?.apply { + trackBottomSheetThreadActionsEvent(ACTION_SHARE_LINK_NAME) + mainViewModel.shareThreadUrl(message.uid) } + } - override fun onReportDisplayProblem() { - descriptionDialog.show( - title = getString(R.string.reportDisplayProblemTitle), - description = getString(R.string.reportDisplayProblemDescription), - onPositiveButtonClicked = { mainViewModel.reportDisplayProblem(message.uid) }, - ) - } - //endregion - }) - } + override fun onSaveKDrive() { + trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) + context?.let { mainViewModel.saveOnKDrive(listOf(threadUid), it) } + } + + override fun onReportDisplayProblem() { + descriptionDialog.show( + title = getString(R.string.reportDisplayProblemTitle), + description = getString(R.string.reportDisplayProblemDescription), + onPositiveButtonClicked = { mainViewModel.reportDisplayProblem(message.uid) }, + ) + } + //endregion + }) } } From 3e3f829f7416daa2ece11a35c4b87ae9d07f7050 Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Mon, 18 Nov 2024 09:06:55 +0100 Subject: [PATCH 11/41] refactor: Use a list instead of a set --- app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt index d327a68ead..04cdd1d6d6 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt @@ -1319,8 +1319,8 @@ class MainViewModel @Inject constructor( return@launch } - var messageSubject: String = message.subject?.removeIllegalFileNameCharacter() ?: NO_SUBJECT_FILE - createOriginalFileName(messageSubject, listFileName).let { fileName -> + val messageSubject: String = message.subject?.removeIllegalFileNameCharacter() ?: NO_SUBJECT_FILE + createOriginalFileName(messageSubject, listFileName.toList()).let { fileName -> listFileName.add(fileName) saveEmlToFile(context, response.body!!.bytes(), fileName)?.let { listUri.add(it) } } @@ -1331,7 +1331,7 @@ class MainViewModel @Inject constructor( } // TODO Extract this code in core2 - private fun createOriginalFileName(originalFileName: String, listFileName: MutableSet): String { + private fun createOriginalFileName(originalFileName: String, listFileName: List): String { var postfix = 1 var fileName = originalFileName From 98133c0f746c6f5adf522c6d8db4ffd6e093208e Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Thu, 21 Nov 2024 13:22:23 +0100 Subject: [PATCH 12/41] feat: Display progression while loading messages --- .idea/navEditor.xml | 218 +++++------------- .../com/infomaniak/mail/ui/MainViewModel.kt | 76 ------ .../mail/ui/main/folder/TwoPaneFragment.kt | 2 + .../actions/DownloadThreadsProgressDialog.kt | 88 +++++++ .../actions/DownloadThreadsViewModel.kt | 124 ++++++++++ .../MessageActionsBottomSheetDialog.kt | 3 +- .../actions/MultiSelectBottomSheetDialog.kt | 7 +- .../actions/ThreadActionsBottomSheetDialog.kt | 2 +- .../utils/extensions/AttachmentExtensions.kt | 17 +- .../utils/extensions/NavigationExtensions.kt | 9 + .../main/res/navigation/main_navigation.xml | 10 + 11 files changed, 314 insertions(+), 242 deletions(-) create mode 100644 app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsProgressDialog.kt create mode 100644 app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsViewModel.kt diff --git a/.idea/navEditor.xml b/.idea/navEditor.xml index d4ab77659a..94f86c2952 100644 --- a/.idea/navEditor.xml +++ b/.idea/navEditor.xml @@ -112,7 +112,7 @@ @@ -124,8 +124,8 @@ @@ -136,7 +136,7 @@ @@ -148,7 +148,7 @@ @@ -160,7 +160,7 @@ @@ -172,7 +172,7 @@ @@ -193,7 +193,7 @@ @@ -205,7 +205,7 @@ @@ -217,7 +217,7 @@ @@ -229,7 +229,7 @@ @@ -241,7 +241,7 @@ @@ -253,7 +253,7 @@ @@ -265,7 +265,7 @@ @@ -291,7 +291,7 @@ @@ -303,8 +303,20 @@ + + + + + + + @@ -315,7 +327,7 @@ @@ -327,7 +339,7 @@ @@ -339,7 +351,7 @@ @@ -363,7 +375,7 @@ @@ -375,7 +387,7 @@ @@ -387,7 +399,7 @@ @@ -399,7 +411,7 @@ @@ -430,7 +442,7 @@ @@ -454,7 +466,7 @@ @@ -466,7 +478,7 @@ @@ -482,30 +494,6 @@ - - - - - - - - - - - - - - @@ -523,7 +511,7 @@ @@ -535,7 +523,7 @@ @@ -547,7 +535,7 @@ @@ -571,19 +559,7 @@ - - - - - - - @@ -595,7 +571,7 @@ @@ -616,7 +592,7 @@ @@ -642,7 +618,7 @@ @@ -718,7 +694,7 @@ @@ -730,7 +706,7 @@ @@ -742,7 +718,7 @@ @@ -754,7 +730,7 @@ @@ -775,7 +751,7 @@ @@ -787,7 +763,7 @@ @@ -799,7 +775,7 @@ @@ -811,7 +787,7 @@ @@ -823,7 +799,7 @@ @@ -879,7 +855,7 @@ @@ -891,7 +867,7 @@ @@ -915,40 +891,6 @@ - - - - - - - @@ -959,7 +901,7 @@ @@ -971,7 +913,7 @@ @@ -983,7 +925,7 @@ @@ -1002,19 +944,7 @@ - - - - - - - - + - - - - - - - - - - - - - - diff --git a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt index 04cdd1d6d6..a6877c2d46 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt @@ -18,9 +18,6 @@ package com.infomaniak.mail.ui import android.app.Application -import android.content.Context -import android.net.Uri -import androidx.core.content.FileProvider import androidx.lifecycle.* import com.infomaniak.lib.core.models.ApiResponse import com.infomaniak.lib.core.networking.NetworkAvailability @@ -64,7 +61,6 @@ import com.infomaniak.mail.utils.SharedUtils.Companion.updateSignatures import com.infomaniak.mail.utils.Utils.isPermanentDeleteFolder import com.infomaniak.mail.utils.Utils.runCatchingRealm import com.infomaniak.mail.utils.extensions.* -import com.infomaniak.mail.utils.extensions.AttachmentExtensions.openKDriveOrPlayStore import com.infomaniak.mail.views.itemViews.AvatarMergedContactData import com.infomaniak.mail.views.itemViews.MyKSuiteStorageBanner.StorageLevel import dagger.hilt.android.lifecycle.HiltViewModel @@ -77,7 +73,6 @@ import io.sentry.Sentry import io.sentry.SentryLevel import kotlinx.coroutines.* import kotlinx.coroutines.flow.* -import java.io.File import java.util.Date import java.util.UUID import javax.inject.Inject @@ -1299,76 +1294,6 @@ class MainViewModel @Inject constructor( } } - fun saveOnKDrive(threadUids: List, context: Context) { - viewModelScope.launch(ioCoroutineContext) { - val mailbox = currentMailbox.value ?: return@launch - val listUri = mutableListOf() - val listFileName = mutableSetOf().also { - it.addAll(getAllFileNameInExportEmlDir(context)) - } - - threadUids.forEach { threadUid -> - val thread = threadController.getThread(threadUid) ?: return@launch - - thread.messages.forEach { message -> - val response = ApiRepository.getDownloadedAttachment(mailbox.uuid, message.folderId, message.shortUid) - - if (!response.isSuccessful || response.body == null) { - snackbarManager.postValue(appContext.getString(RCore.string.anErrorHasOccurred)) - - return@launch - } - - val messageSubject: String = message.subject?.removeIllegalFileNameCharacter() ?: NO_SUBJECT_FILE - createOriginalFileName(messageSubject, listFileName.toList()).let { fileName -> - listFileName.add(fileName) - saveEmlToFile(context, response.body!!.bytes(), fileName)?.let { listUri.add(it) } - } - } - } - ArrayList(listUri).openKDriveOrPlayStore(context) - } - } - - // TODO Extract this code in core2 - private fun createOriginalFileName(originalFileName: String, listFileName: List): String { - var postfix = 1 - var fileName = originalFileName - - while (listFileName.contains(fileName)) { - fileName = "$originalFileName (${postfix++})" - } - - return fileName - } - - private fun getAllFileNameInExportEmlDir(context: Context): List { - val fileDir = File(context.cacheDir, context.getString(R.string.EXPOSED_EML_PATH)) - - if (!fileDir.exists()) fileDir.mkdirs() - - return fileDir.listFiles()?.map { it.name.removeSuffix(".eml") } ?: emptyList() - } - - private fun saveEmlToFile(context: Context, emlByteArray: ByteArray, fileName: String): Uri? { - val fileNameWithExtension = "${fileName.removeIllegalFileNameCharacter()}.eml" - val fileDir = File(context.cacheDir, context.getString(R.string.EXPOSED_EML_PATH)) - - if (!fileDir.exists()) fileDir.mkdirs() - - runCatching { - val file = File(fileDir, fileNameWithExtension) - file.outputStream().use { it.write(emlByteArray) } - return FileProvider.getUriForFile(context, context.getString(R.string.EML_AUTHORITY), file) - }.onFailure { exception -> - exception.printStackTrace() - snackbarManager.postValue(appContext.getString(RCore.string.anErrorHasOccurred)) - } - return null - } - - fun String.removeIllegalFileNameCharacter(): String = this.replace(DownloadManagerUtils.regexInvalidSystemChar, "") - companion object { private val TAG: String = MainViewModel::class.java.simpleName private val DEFAULT_SELECTED_FOLDER = FolderRole.INBOX @@ -1376,6 +1301,5 @@ class MainViewModel @Inject constructor( private const val MAX_REFRESH_DELAY = 6_000L private const val EML_CONTENT_TYPE = "message/rfc822" - private const val NO_SUBJECT_FILE = "message" } } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/folder/TwoPaneFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/main/folder/TwoPaneFragment.kt index cb97c7ce18..48bf4d3edb 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/folder/TwoPaneFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/folder/TwoPaneFragment.kt @@ -41,6 +41,7 @@ import com.infomaniak.mail.ui.MainActivity import com.infomaniak.mail.ui.MainViewModel import com.infomaniak.mail.ui.main.search.SearchFragment import com.infomaniak.mail.ui.main.thread.ThreadFragment +import com.infomaniak.mail.ui.main.thread.actions.DownloadThreadsProgressDialog.Companion.DOWNLOAD_THREADS_RESULT import com.infomaniak.mail.utils.extensions.* import javax.inject.Inject @@ -120,6 +121,7 @@ abstract class TwoPaneFragment : Fragment() { private fun observeThreadNavigation() = with(twoPaneViewModel) { getBackNavigationResult(AttachmentExtensions.DOWNLOAD_ATTACHMENT_RESULT, ::startActivity) + getBackNavigationResult(DOWNLOAD_THREADS_RESULT, ::startActivity) newMessageArgs.observe(viewLifecycleOwner) { safeNavigateToNewMessageActivity(args = it.toBundle()) diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsProgressDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsProgressDialog.kt new file mode 100644 index 0000000000..6efeaef269 --- /dev/null +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsProgressDialog.kt @@ -0,0 +1,88 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2024 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.mail.ui.main.thread.actions + +import android.app.Dialog +import android.os.Bundle +import android.view.KeyEvent +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.activityViewModels +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.infomaniak.lib.core.R +import com.infomaniak.lib.core.utils.SnackbarUtils.showSnackbar +import com.infomaniak.lib.core.utils.setBackNavigationResult +import com.infomaniak.mail.databinding.DialogDownloadProgressBinding +import com.infomaniak.mail.ui.MainViewModel +import com.infomaniak.mail.utils.extensions.AttachmentExtensions.openKDriveOrPlayStore +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch + +@AndroidEntryPoint +class DownloadThreadsProgressDialog : DialogFragment() { + private val binding by lazy { DialogDownloadProgressBinding.inflate(layoutInflater) } + private val mainViewModel: MainViewModel by activityViewModels() + private val downloadThreadsViewModel: DownloadThreadsViewModel by viewModels() + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + isCancelable = false + return MaterialAlertDialogBuilder(requireContext()) + .setTitle("test") + .setView(binding.root) + .setOnKeyListener { _, keyCode, event -> + if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) { + findNavController().popBackStack() + true + } else false + } + .create() + } + + override fun onStart() { + downloadThreads() + super.onStart() + } + + private fun downloadThreads() { + downloadThreadsViewModel.downloadThreads(mainViewModel.currentMailbox.value).observe(this) { threadUris -> + if (threadUris == null) { + popBackStackWithError() + } else { + ArrayList(threadUris).openKDriveOrPlayStore(requireContext())?.let { openKDriveIntent -> + setBackNavigationResult(DOWNLOAD_THREADS_RESULT, openKDriveIntent) + } ?: run { findNavController().popBackStack() } + } + } + } + + private fun popBackStackWithError() { + lifecycleScope.launch { + mainViewModel.isNetworkAvailable.first { it != null }?.let { isNetworkAvailable -> + showSnackbar(title = if (isNetworkAvailable) R.string.anErrorHasOccurred else R.string.noConnection) + findNavController().popBackStack() + } + } + } + + companion object { + const val DOWNLOAD_THREADS_RESULT = "download_threads_result" + } +} diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsViewModel.kt new file mode 100644 index 0000000000..8d3b6b03f2 --- /dev/null +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsViewModel.kt @@ -0,0 +1,124 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2024 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.mail.ui.main.thread.actions + +import android.app.Application +import android.content.Context +import android.net.Uri +import androidx.core.content.FileProvider +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.liveData +import androidx.lifecycle.viewModelScope +import com.infomaniak.lib.core.utils.DownloadManagerUtils +import com.infomaniak.mail.data.api.ApiRepository +import com.infomaniak.mail.data.cache.mailboxContent.ThreadController +import com.infomaniak.mail.data.models.mailbox.Mailbox +import com.infomaniak.mail.di.IoDispatcher +import com.infomaniak.mail.utils.coroutineContext +import com.infomaniak.mail.utils.extensions.appContext +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineDispatcher +import java.io.File +import javax.inject.Inject + +@HiltViewModel +class DownloadThreadsViewModel @Inject constructor( + application: Application, + private val savedStateHandle: SavedStateHandle, + private val threadController: ThreadController, + @IoDispatcher private val ioDispatcher: CoroutineDispatcher, +) : AndroidViewModel(application) { + + private val ioCoroutineContext = viewModelScope.coroutineContext(ioDispatcher) + + private val messageLocalUids + inline get() = savedStateHandle.get>(DownloadThreadsProgressDialogArgs::threadUuids.name)!! + + fun downloadThreads(currentMailbox: Mailbox?) = liveData(ioCoroutineContext) { + val downloadedThreadUris: List? = runCatching { + val mailbox = currentMailbox ?: return@runCatching null + val listUri = mutableListOf() + val listFileName = mutableSetOf().also { it.addAll(getAllFileNameInExportEmlDir(appContext)) } + + messageLocalUids.forEach { threadUid -> + val thread = threadController.getThread(threadUid) ?: return@runCatching null + + thread.messages.forEach { message -> + val response = ApiRepository.getDownloadedAttachment(mailbox.uuid, message.folderId, message.shortUid) + + if (!response.isSuccessful || response.body == null) { + return@runCatching null + } + + val messageSubject: String = message.subject?.removeIllegalFileNameCharacter() ?: NO_SUBJECT_FILE + createOriginalFileName(messageSubject, listFileName.toList()).let { fileName -> + listFileName.add(fileName) + saveEmlToFile(appContext, response.body!!.bytes(), fileName)?.let { listUri.add(it) } + } + } + } + listUri + }.getOrNull() + + emit(downloadedThreadUris) + } + + // TODO Extract this code in core2 + private fun createOriginalFileName(originalFileName: String, listFileName: List): String { + var postfix = 1 + var fileName = originalFileName + + while (listFileName.contains(fileName)) { + fileName = "$originalFileName (${postfix++})" + } + + return fileName + } + + private fun getAllFileNameInExportEmlDir(context: Context): List { + val fileDir = File(context.cacheDir, context.getString(com.infomaniak.mail.R.string.EXPOSED_EML_PATH)) + + if (!fileDir.exists()) fileDir.mkdirs() + + return fileDir.listFiles()?.map { it.name.removeSuffix(".eml") } ?: emptyList() + } + + private fun saveEmlToFile(context: Context, emlByteArray: ByteArray, fileName: String): Uri? { + val fileNameWithExtension = "${fileName.removeIllegalFileNameCharacter()}.eml" + val fileDir = File(context.cacheDir, context.getString(com.infomaniak.mail.R.string.EXPOSED_EML_PATH)) + + if (!fileDir.exists()) fileDir.mkdirs() + + runCatching { + val file = File(fileDir, fileNameWithExtension) + file.outputStream().use { it.write(emlByteArray) } + return FileProvider.getUriForFile(context, context.getString(com.infomaniak.mail.R.string.EML_AUTHORITY), file) + }.onFailure { exception -> + exception.printStackTrace() + } + return null + } + + private fun String.removeIllegalFileNameCharacter(): String = this.replace(DownloadManagerUtils.regexInvalidSystemChar, "") + + companion object { + private const val EML_CONTENT_TYPE = "message/rfc822" + private const val NO_SUBJECT_FILE = "message" + } +} diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt index 640468eb97..20be917eb2 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt @@ -18,6 +18,7 @@ package com.infomaniak.mail.ui.main.thread.actions import android.os.Bundle +import android.util.Log import android.view.View import androidx.core.view.isVisible import androidx.navigation.fragment.navArgs @@ -177,7 +178,7 @@ class MessageActionsBottomSheetDialog : MailActionsBottomSheetDialog() { override fun onSaveKDrive() { trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) - context?.let { mainViewModel.saveOnKDrive(listOf(threadUid), it) } + navigateToDownloadThreadsProgressDialog(listOf(messageUid), MessageActionsBottomSheetDialog::class.java.name) } override fun onReportDisplayProblem() { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt index 76f979822f..da415334e3 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt @@ -23,6 +23,7 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels import com.infomaniak.lib.core.utils.safeBinding +import com.infomaniak.lib.core.utils.safeNavigate import com.infomaniak.mail.MatomoMail.ACTION_ARCHIVE_NAME import com.infomaniak.mail.MatomoMail.ACTION_DELETE_NAME import com.infomaniak.mail.MatomoMail.ACTION_FAVORITE_NAME @@ -121,7 +122,11 @@ class MultiSelectBottomSheetDialog : ActionsBottomSheetDialog() { } binding.saveKDrive.setClosingOnClickListener(shouldCloseMultiSelection = true) { trackMultiSelectActionEvent(ACTION_SAVE_KDRIVE_NAME, selectedThreadsCount, isFromBottomSheet = true) - context?.let { saveOnKDrive(selectedThreadsUids, it) } + safeNavigate( + resId = R.id.downloadThreadsProgressDialog, + args = DownloadThreadsProgressDialogArgs(threadUuids = selectedThreadsUids.toTypedArray()).toBundle(), + currentClassName = currentClassName, + ) isMultiSelectOn = false } } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt index 6fd7b6fc84..cb7bb2e708 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt @@ -198,7 +198,7 @@ class ThreadActionsBottomSheetDialog : MailActionsBottomSheetDialog() { override fun onSaveKDrive() { trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) - context?.let { mainViewModel.saveOnKDrive(listOf(threadUid), it) } + navigateToDownloadThreadsProgressDialog(listOf(threadUid), ThreadActionsBottomSheetDialog::class.java.name) } override fun onReportDisplayProblem() { diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt index 3c092c6074..31629dead0 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt @@ -59,6 +59,15 @@ object AttachmentExtensions { private const val DRIVE_PACKAGE = "com.infomaniak.drive" private const val SAVE_EXTERNAL_ACTIVITY_CLASS = "com.infomaniak.drive.ui.SaveExternalFilesActivity" + fun ArrayList.openKDriveOrPlayStore(context: Context): Intent? { + return if (canSaveOnKDrive(context)) { + saveToDriveIntent() + } else { + context.goToPlayStore(DRIVE_PACKAGE) + null + } + } + //region Intent private fun canSaveOnKDrive(context: Context) = runCatching { val packageInfo = context.packageManager.getPackageInfo(DRIVE_PACKAGE, PackageManager.GET_ACTIVITIES) @@ -111,13 +120,7 @@ object AttachmentExtensions { } } - fun ArrayList.openKDriveOrPlayStore(context: Context) { - if (canSaveOnKDrive(context)) { - saveToDriveIntent().let(context::startActivity) - } else { - context.goToPlayStore(DRIVE_PACKAGE) - } - } + fun Attachment.executeIntent( context: Context, diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt index 0c9a11e13f..da0b258cc6 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt @@ -36,6 +36,7 @@ import com.infomaniak.mail.ui.login.LoginActivity import com.infomaniak.mail.ui.login.LoginActivityArgs import com.infomaniak.mail.ui.login.NoMailboxActivity import com.infomaniak.mail.ui.main.thread.actions.AttachmentActionsBottomSheetDialog +import com.infomaniak.mail.ui.main.thread.actions.DownloadThreadsProgressDialogArgs import com.infomaniak.mail.ui.newMessage.NewMessageActivityArgs import com.infomaniak.mail.ui.noValidMailboxes.NoValidMailboxesActivity import com.infomaniak.mail.utils.AccountUtils @@ -90,6 +91,14 @@ fun Fragment.navigateToDownloadProgressDialog( ) } +fun Fragment.navigateToDownloadThreadsProgressDialog(threadUuids: List, currentClassName: String) { + safeNavigate( + resId = R.id.downloadThreadsProgressDialog, + args = DownloadThreadsProgressDialogArgs(threadUuids = threadUuids.toTypedArray()).toBundle(), + currentClassName = currentClassName, + ) +} + //region Launch Activities fun Context.getLoginActivityIntent(args: LoginActivityArgs? = null, shouldClearStack: Boolean = false): Intent { return Intent(this, LoginActivity::class.java).apply { diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml index 5278fc0212..470117a9aa 100644 --- a/app/src/main/res/navigation/main_navigation.xml +++ b/app/src/main/res/navigation/main_navigation.xml @@ -590,6 +590,16 @@ app:argType="com.infomaniak.mail.utils.extensions.AttachmentExtensions$AttachmentIntentType" /> + + + + Date: Thu, 21 Nov 2024 14:32:59 +0100 Subject: [PATCH 13/41] refactor: Send messages uids instead of thread uuids --- .../com/infomaniak/mail/ui/MainViewModel.kt | 9 ++++++ .../actions/DownloadThreadsViewModel.kt | 29 ++++++++----------- .../actions/MultiSelectBottomSheetDialog.kt | 9 +++--- .../actions/ThreadActionsBottomSheetDialog.kt | 5 +++- .../utils/extensions/NavigationExtensions.kt | 4 +-- .../main/res/navigation/main_navigation.xml | 2 +- 6 files changed, 32 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt index a6877c2d46..6430f1dcdf 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt @@ -1294,6 +1294,15 @@ class MainViewModel @Inject constructor( } } + fun getMessagesUidsFromThreadUids(selectedThreadsUuids: List): List { + val messageUids = mutableListOf() + selectedThreadsUuids.forEach { threadUuid -> + val thread = threadController.getThread(threadUuid) ?: return@forEach + messageUids.addAll(thread.messages.map { it.uid }) + } + return messageUids + } + companion object { private val TAG: String = MainViewModel::class.java.simpleName private val DEFAULT_SELECTED_FOLDER = FolderRole.INBOX diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsViewModel.kt index 8d3b6b03f2..c3311166e6 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsViewModel.kt @@ -27,7 +27,7 @@ import androidx.lifecycle.liveData import androidx.lifecycle.viewModelScope import com.infomaniak.lib.core.utils.DownloadManagerUtils import com.infomaniak.mail.data.api.ApiRepository -import com.infomaniak.mail.data.cache.mailboxContent.ThreadController +import com.infomaniak.mail.data.cache.mailboxContent.MessageController import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.di.IoDispatcher import com.infomaniak.mail.utils.coroutineContext @@ -41,14 +41,14 @@ import javax.inject.Inject class DownloadThreadsViewModel @Inject constructor( application: Application, private val savedStateHandle: SavedStateHandle, - private val threadController: ThreadController, + private val messageController: MessageController, @IoDispatcher private val ioDispatcher: CoroutineDispatcher, ) : AndroidViewModel(application) { private val ioCoroutineContext = viewModelScope.coroutineContext(ioDispatcher) - private val messageLocalUids - inline get() = savedStateHandle.get>(DownloadThreadsProgressDialogArgs::threadUuids.name)!! + private val messageLocalUuids + inline get() = savedStateHandle.get>(DownloadThreadsProgressDialogArgs::messageUuids.name)!! fun downloadThreads(currentMailbox: Mailbox?) = liveData(ioCoroutineContext) { val downloadedThreadUris: List? = runCatching { @@ -56,21 +56,16 @@ class DownloadThreadsViewModel @Inject constructor( val listUri = mutableListOf() val listFileName = mutableSetOf().also { it.addAll(getAllFileNameInExportEmlDir(appContext)) } - messageLocalUids.forEach { threadUid -> - val thread = threadController.getThread(threadUid) ?: return@runCatching null + messageLocalUuids.forEach { messageUid -> + val message = messageController.getMessage(messageUid) ?: return@runCatching null + val response = ApiRepository.getDownloadedAttachment(mailbox.uuid, message.folderId, message.shortUid) - thread.messages.forEach { message -> - val response = ApiRepository.getDownloadedAttachment(mailbox.uuid, message.folderId, message.shortUid) + if (!response.isSuccessful || response.body == null) return@runCatching null - if (!response.isSuccessful || response.body == null) { - return@runCatching null - } - - val messageSubject: String = message.subject?.removeIllegalFileNameCharacter() ?: NO_SUBJECT_FILE - createOriginalFileName(messageSubject, listFileName.toList()).let { fileName -> - listFileName.add(fileName) - saveEmlToFile(appContext, response.body!!.bytes(), fileName)?.let { listUri.add(it) } - } + val messageSubject: String = message.subject?.removeIllegalFileNameCharacter() ?: NO_SUBJECT_FILE + createOriginalFileName(messageSubject, listFileName.toList()).let { fileName -> + listFileName.add(fileName) + saveEmlToFile(appContext, response.body!!.bytes(), fileName)?.let { listUri.add(it) } } } listUri diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt index da415334e3..dbe8047a77 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt @@ -23,7 +23,6 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels import com.infomaniak.lib.core.utils.safeBinding -import com.infomaniak.lib.core.utils.safeNavigate import com.infomaniak.mail.MatomoMail.ACTION_ARCHIVE_NAME import com.infomaniak.mail.MatomoMail.ACTION_DELETE_NAME import com.infomaniak.mail.MatomoMail.ACTION_FAVORITE_NAME @@ -42,6 +41,7 @@ import com.infomaniak.mail.ui.main.folder.ThreadListMultiSelection import com.infomaniak.mail.ui.main.folder.ThreadListMultiSelection.Companion.getReadIconAndShortText import com.infomaniak.mail.utils.extensions.animatedNavigation import com.infomaniak.mail.utils.extensions.deleteWithConfirmationPopup +import com.infomaniak.mail.utils.extensions.navigateToDownloadThreadsProgressDialog import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -122,10 +122,9 @@ class MultiSelectBottomSheetDialog : ActionsBottomSheetDialog() { } binding.saveKDrive.setClosingOnClickListener(shouldCloseMultiSelection = true) { trackMultiSelectActionEvent(ACTION_SAVE_KDRIVE_NAME, selectedThreadsCount, isFromBottomSheet = true) - safeNavigate( - resId = R.id.downloadThreadsProgressDialog, - args = DownloadThreadsProgressDialogArgs(threadUuids = selectedThreadsUids.toTypedArray()).toBundle(), - currentClassName = currentClassName, + navigateToDownloadThreadsProgressDialog( + messageUuids = mainViewModel.getMessagesUidsFromThreadUids(selectedThreadsUids), + MultiSelectBottomSheetDialog::class.java.name ) isMultiSelectOn = false } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt index cb7bb2e708..3ec94c0230 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt @@ -198,7 +198,10 @@ class ThreadActionsBottomSheetDialog : MailActionsBottomSheetDialog() { override fun onSaveKDrive() { trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) - navigateToDownloadThreadsProgressDialog(listOf(threadUid), ThreadActionsBottomSheetDialog::class.java.name) + navigateToDownloadThreadsProgressDialog( + messageUuids = thread.messages.map { it.uid }, + ThreadActionsBottomSheetDialog::class.java.name + ) } override fun onReportDisplayProblem() { diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt index da0b258cc6..0679da21b2 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt @@ -91,10 +91,10 @@ fun Fragment.navigateToDownloadProgressDialog( ) } -fun Fragment.navigateToDownloadThreadsProgressDialog(threadUuids: List, currentClassName: String) { +fun Fragment.navigateToDownloadThreadsProgressDialog(messageUuids: List, currentClassName: String) { safeNavigate( resId = R.id.downloadThreadsProgressDialog, - args = DownloadThreadsProgressDialogArgs(threadUuids = threadUuids.toTypedArray()).toBundle(), + args = DownloadThreadsProgressDialogArgs(messageUuids = messageUuids.toTypedArray()).toBundle(), currentClassName = currentClassName, ) } diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml index 470117a9aa..332c156b27 100644 --- a/app/src/main/res/navigation/main_navigation.xml +++ b/app/src/main/res/navigation/main_navigation.xml @@ -596,7 +596,7 @@ android:label="DownloadThreadsProgressDialog" tools:layout="@layout/dialog_download_progress"> From d828f7e8ca786bc8a696b18b7ce81c217d158ac7 Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Thu, 21 Nov 2024 15:05:30 +0100 Subject: [PATCH 14/41] refactor: Rename 'messageUuids' to 'messageUids' --- .../mail/ui/main/thread/actions/DownloadThreadsViewModel.kt | 6 +++--- .../ui/main/thread/actions/MultiSelectBottomSheetDialog.kt | 2 +- .../main/thread/actions/ThreadActionsBottomSheetDialog.kt | 2 +- .../mail/utils/extensions/NavigationExtensions.kt | 4 ++-- app/src/main/res/navigation/main_navigation.xml | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsViewModel.kt index c3311166e6..9465d345c4 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsViewModel.kt @@ -47,8 +47,8 @@ class DownloadThreadsViewModel @Inject constructor( private val ioCoroutineContext = viewModelScope.coroutineContext(ioDispatcher) - private val messageLocalUuids - inline get() = savedStateHandle.get>(DownloadThreadsProgressDialogArgs::messageUuids.name)!! + private val messageLocalUids + inline get() = savedStateHandle.get>(DownloadThreadsProgressDialogArgs::messageUids.name)!! fun downloadThreads(currentMailbox: Mailbox?) = liveData(ioCoroutineContext) { val downloadedThreadUris: List? = runCatching { @@ -56,7 +56,7 @@ class DownloadThreadsViewModel @Inject constructor( val listUri = mutableListOf() val listFileName = mutableSetOf().also { it.addAll(getAllFileNameInExportEmlDir(appContext)) } - messageLocalUuids.forEach { messageUid -> + messageLocalUids.forEach { messageUid -> val message = messageController.getMessage(messageUid) ?: return@runCatching null val response = ApiRepository.getDownloadedAttachment(mailbox.uuid, message.folderId, message.shortUid) diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt index dbe8047a77..c7d5f578c4 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt @@ -123,7 +123,7 @@ class MultiSelectBottomSheetDialog : ActionsBottomSheetDialog() { binding.saveKDrive.setClosingOnClickListener(shouldCloseMultiSelection = true) { trackMultiSelectActionEvent(ACTION_SAVE_KDRIVE_NAME, selectedThreadsCount, isFromBottomSheet = true) navigateToDownloadThreadsProgressDialog( - messageUuids = mainViewModel.getMessagesUidsFromThreadUids(selectedThreadsUids), + messageUids = mainViewModel.getMessagesUidsFromThreadUids(selectedThreadsUids), MultiSelectBottomSheetDialog::class.java.name ) isMultiSelectOn = false diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt index 3ec94c0230..3df496f2db 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt @@ -199,7 +199,7 @@ class ThreadActionsBottomSheetDialog : MailActionsBottomSheetDialog() { override fun onSaveKDrive() { trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) navigateToDownloadThreadsProgressDialog( - messageUuids = thread.messages.map { it.uid }, + messageUids = thread.messages.map { it.uid }, ThreadActionsBottomSheetDialog::class.java.name ) } diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt index 0679da21b2..a46104b194 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt @@ -91,10 +91,10 @@ fun Fragment.navigateToDownloadProgressDialog( ) } -fun Fragment.navigateToDownloadThreadsProgressDialog(messageUuids: List, currentClassName: String) { +fun Fragment.navigateToDownloadThreadsProgressDialog(messageUids: List, currentClassName: String) { safeNavigate( resId = R.id.downloadThreadsProgressDialog, - args = DownloadThreadsProgressDialogArgs(messageUuids = messageUuids.toTypedArray()).toBundle(), + args = DownloadThreadsProgressDialogArgs(messageUids = messageUids.toTypedArray()).toBundle(), currentClassName = currentClassName, ) } diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml index 332c156b27..223965518c 100644 --- a/app/src/main/res/navigation/main_navigation.xml +++ b/app/src/main/res/navigation/main_navigation.xml @@ -596,7 +596,7 @@ android:label="DownloadThreadsProgressDialog" tools:layout="@layout/dialog_download_progress"> From f7eefdc60299048f5ab323b5fcd51091eca9d9f4 Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Thu, 21 Nov 2024 16:36:23 +0100 Subject: [PATCH 15/41] feat: Import downloading String --- app/src/main/res/values-de/strings.xml | 4 ++++ app/src/main/res/values-es/strings.xml | 4 ++++ app/src/main/res/values-fr/strings.xml | 5 +++++ app/src/main/res/values-it/strings.xml | 4 ++++ app/src/main/res/values/strings.xml | 4 ++++ 5 files changed, 21 insertions(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 2f1b414c19..164c1f8640 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -248,6 +248,10 @@ Benötigen Sie mehr Speicherplatz und Funktionen? Um Anzeigeprobleme zu beheben, aktualisieren Sie bitte die Anwendung Android System Webview. Problem mit der Anzeige Ihrer E-Mails + + Herunterladen von %d-E-Mails + Herunterladen von %d-E-Mails + Entwürfe (Entwurf) Diese Nachricht wird in Ihre Entwürfe verschoben, damit Sie sie senden können, wann immer Sie möchten. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d505e4ebb4..69656d6e79 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -248,6 +248,10 @@ ¿Necesita más almacenamiento y funciones? Para solucionar los problemas de visualización, actualice la aplicación Android System Webview. Problema de visualización de sus emails + + Descargando %d correos electrónicos + Descargando %d correos electrónicos + Borradores (Borrador) Este mensaje se moverá a tus borradores para que puedas enviarlo cuando lo desees. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 6c621ffb68..b3458cd1ba 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -250,6 +250,11 @@ Besoin de plus de stockage et de fonctionnalités ? Pour corriger les problèmes d’affichage, veuillez mettre à jour l’application Android System Webview. Problème d’affichage de vos e-mails + + Téléchargement de %d e-mails + Téléchargement de %d e-mails + Téléchargement de %d d’e-mails + Brouillons (Brouillon) Ce message sera déplacé dans vos brouillons pour être envoyé quand vous le souhaitez. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index ebcb4bfdc1..34e85b09cd 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -248,6 +248,10 @@ Hai bisogno di più spazio di archiviazione e funzionalità? Per risolvere i problemi di visualizzazione, aggiornare l’applicazione Android System Webview. Problema di visualizzazione delle email + + Download di %d e-mail + Download di %d email + Bozze (Bozza) Questo messaggio verrà spostato nelle tue bozze per essere inviato quando vuoi. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f054021784..33b0f655d6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -254,6 +254,10 @@ Need more storage and features? To fix display problems, please update the Android System Webview application. Problem displaying your emails + + Downloading %d emails + Downloading %d emails + Drafts (Draft) This message will be moved to your drafts to be sent whenever you want. From 45bcaa6dd689657771d71e4758ed2bb3305e5c0d Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Fri, 22 Nov 2024 09:30:21 +0100 Subject: [PATCH 16/41] feat: Add dialog name --- .../com/infomaniak/mail/ui/MainViewModel.kt | 4 ++++ .../actions/DownloadThreadsProgressDialog.kt | 23 +++++++++++++++---- .../actions/DownloadThreadsViewModel.kt | 6 +---- .../MessageActionsBottomSheetDialog.kt | 7 ++++-- .../actions/MultiSelectBottomSheetDialog.kt | 3 ++- .../actions/ThreadActionsBottomSheetDialog.kt | 1 + .../utils/extensions/NavigationExtensions.kt | 11 +++++++-- .../main/res/navigation/main_navigation.xml | 4 ++++ 8 files changed, 45 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt index 6430f1dcdf..3fdac2f910 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt @@ -1303,6 +1303,10 @@ class MainViewModel @Inject constructor( return messageUids } + fun getSubject(threadUuid: String): String? { + return threadController.getThread(threadUuid)?.subject + } + companion object { private val TAG: String = MainViewModel::class.java.simpleName private val DEFAULT_SELECTED_FOLDER = FolderRole.INBOX diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsProgressDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsProgressDialog.kt index 6efeaef269..263de5b674 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsProgressDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsProgressDialog.kt @@ -20,32 +20,47 @@ package com.infomaniak.mail.ui.main.thread.actions import android.app.Dialog import android.os.Bundle import android.view.KeyEvent +import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.infomaniak.lib.core.R import com.infomaniak.lib.core.utils.SnackbarUtils.showSnackbar import com.infomaniak.lib.core.utils.setBackNavigationResult +import com.infomaniak.mail.R import com.infomaniak.mail.databinding.DialogDownloadProgressBinding import com.infomaniak.mail.ui.MainViewModel import com.infomaniak.mail.utils.extensions.AttachmentExtensions.openKDriveOrPlayStore import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import com.infomaniak.lib.core.R as RCore @AndroidEntryPoint class DownloadThreadsProgressDialog : DialogFragment() { private val binding by lazy { DialogDownloadProgressBinding.inflate(layoutInflater) } private val mainViewModel: MainViewModel by activityViewModels() private val downloadThreadsViewModel: DownloadThreadsViewModel by viewModels() + private val navigationArgs: DownloadThreadsProgressDialogArgs by navArgs() override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val context = requireContext() + isCancelable = false - return MaterialAlertDialogBuilder(requireContext()) - .setTitle("test") + binding.icon.isVisible = false + + val textTitleDialog = if (navigationArgs.messageUids.size == 1) { + navigationArgs.nameFirstMessage + } else { + context.resources.getQuantityString(R.plurals.downloadingEmailsTitle, 1, navigationArgs.messageUids.size) + // TODO LOOK + } + + return MaterialAlertDialogBuilder(context) + .setTitle(textTitleDialog) .setView(binding.root) .setOnKeyListener { _, keyCode, event -> if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) { @@ -76,7 +91,7 @@ class DownloadThreadsProgressDialog : DialogFragment() { private fun popBackStackWithError() { lifecycleScope.launch { mainViewModel.isNetworkAvailable.first { it != null }?.let { isNetworkAvailable -> - showSnackbar(title = if (isNetworkAvailable) R.string.anErrorHasOccurred else R.string.noConnection) + showSnackbar(title = if (isNetworkAvailable) RCore.string.anErrorHasOccurred else RCore.string.noConnection) findNavController().popBackStack() } } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsViewModel.kt index 9465d345c4..c3e04423d3 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsViewModel.kt @@ -79,18 +79,14 @@ class DownloadThreadsViewModel @Inject constructor( var postfix = 1 var fileName = originalFileName - while (listFileName.contains(fileName)) { - fileName = "$originalFileName (${postfix++})" - } + while (listFileName.contains(fileName)) fileName = "$originalFileName (${postfix++})" return fileName } private fun getAllFileNameInExportEmlDir(context: Context): List { val fileDir = File(context.cacheDir, context.getString(com.infomaniak.mail.R.string.EXPOSED_EML_PATH)) - if (!fileDir.exists()) fileDir.mkdirs() - return fileDir.listFiles()?.map { it.name.removeSuffix(".eml") } ?: emptyList() } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt index 20be917eb2..295ab274c3 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt @@ -18,7 +18,6 @@ package com.infomaniak.mail.ui.main.thread.actions import android.os.Bundle -import android.util.Log import android.view.View import androidx.core.view.isVisible import androidx.navigation.fragment.navArgs @@ -178,7 +177,11 @@ class MessageActionsBottomSheetDialog : MailActionsBottomSheetDialog() { override fun onSaveKDrive() { trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) - navigateToDownloadThreadsProgressDialog(listOf(messageUid), MessageActionsBottomSheetDialog::class.java.name) + navigateToDownloadThreadsProgressDialog( + listOf(messageUid), + mainViewModel.getSubject(threadUid), + MessageActionsBottomSheetDialog::class.java.name + ) } override fun onReportDisplayProblem() { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt index c7d5f578c4..5fca7a7c24 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt @@ -124,7 +124,8 @@ class MultiSelectBottomSheetDialog : ActionsBottomSheetDialog() { trackMultiSelectActionEvent(ACTION_SAVE_KDRIVE_NAME, selectedThreadsCount, isFromBottomSheet = true) navigateToDownloadThreadsProgressDialog( messageUids = mainViewModel.getMessagesUidsFromThreadUids(selectedThreadsUids), - MultiSelectBottomSheetDialog::class.java.name + nameFirstMessage = selectedThreads.firstOrNull()?.subject, + MultiSelectBottomSheetDialog::class.java.name, ) isMultiSelectOn = false } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt index 3df496f2db..23ad1ef584 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt @@ -200,6 +200,7 @@ class ThreadActionsBottomSheetDialog : MailActionsBottomSheetDialog() { trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) navigateToDownloadThreadsProgressDialog( messageUids = thread.messages.map { it.uid }, + nameFirstMessage = thread.subject, ThreadActionsBottomSheetDialog::class.java.name ) } diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt index a46104b194..80b061a7e2 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt @@ -91,10 +91,17 @@ fun Fragment.navigateToDownloadProgressDialog( ) } -fun Fragment.navigateToDownloadThreadsProgressDialog(messageUids: List, currentClassName: String) { +fun Fragment.navigateToDownloadThreadsProgressDialog( + messageUids: List, + nameFirstMessage: String?, + currentClassName: String +) { safeNavigate( resId = R.id.downloadThreadsProgressDialog, - args = DownloadThreadsProgressDialogArgs(messageUids = messageUids.toTypedArray()).toBundle(), + args = DownloadThreadsProgressDialogArgs( + messageUids = messageUids.toTypedArray(), + nameFirstMessage = nameFirstMessage + ).toBundle(), currentClassName = currentClassName, ) } diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml index 223965518c..e489367f96 100644 --- a/app/src/main/res/navigation/main_navigation.xml +++ b/app/src/main/res/navigation/main_navigation.xml @@ -598,6 +598,10 @@ + Date: Fri, 22 Nov 2024 11:00:05 +0100 Subject: [PATCH 17/41] feat: Centralize shared code in abstract class --- .../DownloadAttachmentProgressDialog.kt | 53 +---------- .../thread/actions/DownloadProgressDialog.kt | 81 +++++++++++++++++ .../actions/DownloadThreadsProgressDialog.kt | 87 +++++++------------ .../com/infomaniak/mail/utils/KDriveUtils.kt | 35 ++++++++ .../utils/extensions/AttachmentExtensions.kt | 27 +----- 5 files changed, 155 insertions(+), 128 deletions(-) create mode 100644 app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadProgressDialog.kt create mode 100644 app/src/main/java/com/infomaniak/mail/utils/KDriveUtils.kt diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadAttachmentProgressDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadAttachmentProgressDialog.kt index c63933bb92..f34d0744fa 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadAttachmentProgressDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadAttachmentProgressDialog.kt @@ -17,59 +17,23 @@ */ package com.infomaniak.mail.ui.main.thread.actions -import android.app.Dialog -import android.os.Bundle -import android.view.KeyEvent -import androidx.appcompat.content.res.AppCompatResources -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.infomaniak.lib.core.R -import com.infomaniak.lib.core.utils.SnackbarUtils.showSnackbar import com.infomaniak.lib.core.utils.setBackNavigationResult -import com.infomaniak.mail.databinding.DialogDownloadProgressBinding -import com.infomaniak.mail.ui.MainViewModel import com.infomaniak.mail.utils.extensions.AttachmentExtensions import com.infomaniak.mail.utils.extensions.AttachmentExtensions.getIntentOrGoToPlayStore import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch @AndroidEntryPoint -class DownloadAttachmentProgressDialog : DialogFragment() { - - private val binding by lazy { DialogDownloadProgressBinding.inflate(layoutInflater) } +class DownloadAttachmentProgressDialog : DownloadProgressDialog() { private val navigationArgs: DownloadAttachmentProgressDialogArgs by navArgs() - private val mainViewModel: MainViewModel by activityViewModels() private val downloadAttachmentViewModel: DownloadAttachmentViewModel by viewModels() - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - isCancelable = false - val iconDrawable = AppCompatResources.getDrawable(requireContext(), navigationArgs.attachmentType.icon) - binding.icon.setImageDrawable(iconDrawable) - - return MaterialAlertDialogBuilder(requireContext()) - .setTitle(navigationArgs.attachmentName) - .setView(binding.root) - .setOnKeyListener { _, keyCode, event -> - if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) { - findNavController().popBackStack() - true - } else false - } - .create() - } - - override fun onStart() { - super.onStart() - downloadAttachment() - } + override val dialogTitle: String? by lazy { navigationArgs.attachmentName } + override val dialogIconDrawableRes: Int? by lazy { navigationArgs.attachmentType.icon } - private fun downloadAttachment() { + override fun download() { downloadAttachmentViewModel.downloadAttachment().observe(this) { cachedAttachment -> if (cachedAttachment == null) { popBackStackWithError() @@ -80,13 +44,4 @@ class DownloadAttachmentProgressDialog : DialogFragment() { } } } - - private fun popBackStackWithError() { - lifecycleScope.launch { - mainViewModel.isNetworkAvailable.first { it != null }?.let { isNetworkAvailable -> - showSnackbar(title = if (isNetworkAvailable) R.string.anErrorHasOccurred else R.string.noConnection) - findNavController().popBackStack() - } - } - } } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadProgressDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadProgressDialog.kt new file mode 100644 index 0000000000..36880ea3a5 --- /dev/null +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadProgressDialog.kt @@ -0,0 +1,81 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2024 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.mail.ui.main.thread.actions + +import android.app.Dialog +import android.os.Bundle +import android.view.KeyEvent +import androidx.annotation.DrawableRes +import androidx.appcompat.content.res.AppCompatResources +import androidx.core.view.isVisible +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.infomaniak.lib.core.R +import com.infomaniak.lib.core.utils.SnackbarUtils.showSnackbar +import com.infomaniak.mail.databinding.DialogDownloadProgressBinding +import com.infomaniak.mail.ui.MainViewModel +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch + +@AndroidEntryPoint +abstract class DownloadProgressDialog : DialogFragment() { + protected val binding by lazy { DialogDownloadProgressBinding.inflate(layoutInflater) } + protected val mainViewModel: MainViewModel by activityViewModels() + abstract val dialogTitle: String? + @get:DrawableRes abstract val dialogIconDrawableRes: Int? + + override fun onStart() { + download() + super.onStart() + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val context = requireContext() + + dialogIconDrawableRes?.let { binding.icon.setImageDrawable(AppCompatResources.getDrawable(requireContext(), it)) } + binding.icon.isVisible = dialogIconDrawableRes == null + + isCancelable = false + + return MaterialAlertDialogBuilder(context) + .setTitle(dialogTitle) + .setView(binding.root) + .setOnKeyListener { _, keyCode, event -> + if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) { + findNavController().popBackStack() + true + } else false + } + .create() + } + + abstract fun download() + + protected fun popBackStackWithError() { + lifecycleScope.launch { + mainViewModel.isNetworkAvailable.first { it != null }?.let { isNetworkAvailable -> + showSnackbar(title = if (isNetworkAvailable) R.string.anErrorHasOccurred else R.string.noConnection) + findNavController().popBackStack() + } + } + } +} diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsProgressDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsProgressDialog.kt index 263de5b674..b13af290c2 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsProgressDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsProgressDialog.kt @@ -17,66 +17,29 @@ */ package com.infomaniak.mail.ui.main.thread.actions -import android.app.Dialog -import android.os.Bundle -import android.view.KeyEvent -import androidx.core.view.isVisible -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.activityViewModels +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.net.Uri import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.infomaniak.lib.core.utils.SnackbarUtils.showSnackbar +import com.infomaniak.lib.core.utils.goToPlayStore import com.infomaniak.lib.core.utils.setBackNavigationResult import com.infomaniak.mail.R -import com.infomaniak.mail.databinding.DialogDownloadProgressBinding -import com.infomaniak.mail.ui.MainViewModel -import com.infomaniak.mail.utils.extensions.AttachmentExtensions.openKDriveOrPlayStore +import com.infomaniak.mail.utils.KDriveUtils.DRIVE_PACKAGE +import com.infomaniak.mail.utils.KDriveUtils.SAVE_EXTERNAL_ACTIVITY_CLASS +import com.infomaniak.mail.utils.KDriveUtils.canSaveOnKDrive import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch -import com.infomaniak.lib.core.R as RCore @AndroidEntryPoint -class DownloadThreadsProgressDialog : DialogFragment() { - private val binding by lazy { DialogDownloadProgressBinding.inflate(layoutInflater) } - private val mainViewModel: MainViewModel by activityViewModels() +class DownloadThreadsProgressDialog : DownloadProgressDialog() { private val downloadThreadsViewModel: DownloadThreadsViewModel by viewModels() private val navigationArgs: DownloadThreadsProgressDialogArgs by navArgs() + override val dialogTitle: String? by lazy { getDialogTitleFromArgs() } + override val dialogIconDrawableRes: Int? by lazy { null } - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val context = requireContext() - - isCancelable = false - binding.icon.isVisible = false - - val textTitleDialog = if (navigationArgs.messageUids.size == 1) { - navigationArgs.nameFirstMessage - } else { - context.resources.getQuantityString(R.plurals.downloadingEmailsTitle, 1, navigationArgs.messageUids.size) - // TODO LOOK - } - - return MaterialAlertDialogBuilder(context) - .setTitle(textTitleDialog) - .setView(binding.root) - .setOnKeyListener { _, keyCode, event -> - if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) { - findNavController().popBackStack() - true - } else false - } - .create() - } - - override fun onStart() { - downloadThreads() - super.onStart() - } - - private fun downloadThreads() { + override fun download() { downloadThreadsViewModel.downloadThreads(mainViewModel.currentMailbox.value).observe(this) { threadUris -> if (threadUris == null) { popBackStackWithError() @@ -88,12 +51,26 @@ class DownloadThreadsProgressDialog : DialogFragment() { } } - private fun popBackStackWithError() { - lifecycleScope.launch { - mainViewModel.isNetworkAvailable.first { it != null }?.let { isNetworkAvailable -> - showSnackbar(title = if (isNetworkAvailable) RCore.string.anErrorHasOccurred else RCore.string.noConnection) - findNavController().popBackStack() - } + private fun getDialogTitleFromArgs() = if (navigationArgs.messageUids.size == 1) { + navigationArgs.nameFirstMessage + } else { + requireContext().resources.getQuantityString(R.plurals.downloadingEmailsTitle, 1, navigationArgs.messageUids.size) + } + + private fun ArrayList.openKDriveOrPlayStore(context: Context): Intent? { + return if (canSaveOnKDrive(context)) { + saveToDriveIntent() + } else { + context.goToPlayStore(DRIVE_PACKAGE) + null + } + } + + private fun ArrayList.saveToDriveIntent(): Intent { + return Intent().apply { + component = ComponentName(DRIVE_PACKAGE, SAVE_EXTERNAL_ACTIVITY_CLASS) + action = Intent.ACTION_SEND_MULTIPLE + putParcelableArrayListExtra(Intent.EXTRA_STREAM, this@saveToDriveIntent) } } diff --git a/app/src/main/java/com/infomaniak/mail/utils/KDriveUtils.kt b/app/src/main/java/com/infomaniak/mail/utils/KDriveUtils.kt new file mode 100644 index 0000000000..98029ef026 --- /dev/null +++ b/app/src/main/java/com/infomaniak/mail/utils/KDriveUtils.kt @@ -0,0 +1,35 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2024 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.mail.utils + +import android.content.Context +import android.content.pm.PackageManager +import io.sentry.Sentry + +object KDriveUtils { + const val DRIVE_PACKAGE = "com.infomaniak.drive" + const val SAVE_EXTERNAL_ACTIVITY_CLASS = "com.infomaniak.drive.ui.SaveExternalFilesActivity" + + fun canSaveOnKDrive(context: Context) = runCatching { + val packageInfo = context.packageManager.getPackageInfo(DRIVE_PACKAGE, PackageManager.GET_ACTIVITIES) + packageInfo.activities.any { it.name == SAVE_EXTERNAL_ACTIVITY_CLASS } + }.getOrElse { + Sentry.captureException(it) + false + } +} diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt index 31629dead0..b699756257 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt @@ -20,8 +20,6 @@ package com.infomaniak.mail.utils.extensions import android.content.ComponentName import android.content.Context import android.content.Intent -import android.content.pm.PackageManager -import android.net.Uri import android.os.Bundle import android.provider.MediaStore.Files.FileColumns import androidx.core.content.FileProvider @@ -39,6 +37,9 @@ import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.ui.main.SnackbarManager import com.infomaniak.mail.ui.main.thread.actions.DownloadAttachmentProgressDialogArgs import com.infomaniak.mail.utils.AccountUtils +import com.infomaniak.mail.utils.KDriveUtils.DRIVE_PACKAGE +import com.infomaniak.mail.utils.KDriveUtils.SAVE_EXTERNAL_ACTIVITY_CLASS +import com.infomaniak.mail.utils.KDriveUtils.canSaveOnKDrive import com.infomaniak.mail.utils.SentryDebug import com.infomaniak.mail.utils.WorkerUtils.UploadMissingLocalFileException import com.infomaniak.mail.utils.extensions.AttachmentExtensions.AttachmentIntentType.OPEN_WITH @@ -56,18 +57,6 @@ object AttachmentExtensions { const val ATTACHMENT_TAG = "attachmentUpload" const val DOWNLOAD_ATTACHMENT_RESULT = "download_attachment_result" - private const val DRIVE_PACKAGE = "com.infomaniak.drive" - private const val SAVE_EXTERNAL_ACTIVITY_CLASS = "com.infomaniak.drive.ui.SaveExternalFilesActivity" - - fun ArrayList.openKDriveOrPlayStore(context: Context): Intent? { - return if (canSaveOnKDrive(context)) { - saveToDriveIntent() - } else { - context.goToPlayStore(DRIVE_PACKAGE) - null - } - } - //region Intent private fun canSaveOnKDrive(context: Context) = runCatching { val packageInfo = context.packageManager.getPackageInfo(DRIVE_PACKAGE, PackageManager.GET_ACTIVITIES) @@ -91,14 +80,6 @@ object AttachmentExtensions { } } - private fun ArrayList.saveToDriveIntent(): Intent { - return Intent().apply { - component = ComponentName(DRIVE_PACKAGE, SAVE_EXTERNAL_ACTIVITY_CLASS) - action = Intent.ACTION_SEND_MULTIPLE - putParcelableArrayListExtra(Intent.EXTRA_STREAM, this@saveToDriveIntent) - } - } - private fun Attachment.openWithIntent(context: Context): Intent { val file = getUploadLocalFile() ?: getCacheFile(context) val uri = FileProvider.getUriForFile(context, context.getString(R.string.ATTACHMENTS_AUTHORITY), file) @@ -120,8 +101,6 @@ object AttachmentExtensions { } } - - fun Attachment.executeIntent( context: Context, intentType: AttachmentIntentType, From b5d6537435b03aeea7a5320d1210ac2f42c31aaf Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Fri, 22 Nov 2024 12:44:27 +0100 Subject: [PATCH 18/41] refactor: Rename for better clarity --- .../java/com/infomaniak/mail/data/api/ApiRepository.kt | 2 +- .../main/java/com/infomaniak/mail/ui/MainViewModel.kt | 3 +-- .../infomaniak/mail/ui/main/folder/TwoPaneFragment.kt | 4 ++-- ...ressDialog.kt => DownloadMessagesProgressDialog.kt} | 10 +++++----- ...hreadsViewModel.kt => DownloadMessagesViewModel.kt} | 6 +++--- .../thread/actions/MessageActionsBottomSheetDialog.kt | 2 +- .../thread/actions/MultiSelectBottomSheetDialog.kt | 4 ++-- .../thread/actions/ThreadActionsBottomSheetDialog.kt | 2 +- .../mail/utils/extensions/NavigationExtensions.kt | 8 ++++---- app/src/main/res/navigation/main_navigation.xml | 6 +++--- 10 files changed, 23 insertions(+), 24 deletions(-) rename app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/{DownloadThreadsProgressDialog.kt => DownloadMessagesProgressDialog.kt} (88%) rename app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/{DownloadThreadsViewModel.kt => DownloadMessagesViewModel.kt} (95%) diff --git a/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt b/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt index 1d6bff7ccf..c177b3e52b 100644 --- a/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt +++ b/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt @@ -451,7 +451,7 @@ object ApiRepository : ApiRepositoryCore() { return callApi(url = ApiRoutes.shareLink(mailboxUuid, folderId, mailId), method = POST) } - fun getDownloadedAttachment(mailboxUuid: String, folderId: String, shortUid: Int): Response { + fun getDownloadedMessage(mailboxUuid: String, folderId: String, shortUid: Int): Response { val request = Request.Builder().url(ApiRoutes.downloadMessage(mailboxUuid, folderId, shortUid)) .headers(HttpUtils.getHeaders(null)) .get() diff --git a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt index 3fdac2f910..6551fd16ef 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt @@ -1018,7 +1018,7 @@ class MainViewModel @Inject constructor( val message = messageController.getMessage(messageUid) ?: return@launch val mailbox = currentMailbox.value ?: return@launch - val response = ApiRepository.getDownloadedAttachment(mailbox.uuid, message.folderId, message.shortUid) + val response = ApiRepository.getDownloadedMessage(mailbox.uuid, message.folderId, message.shortUid) if (!response.isSuccessful || response.body == null) { reportDisplayProblemTrigger.postValue(Unit) @@ -1312,7 +1312,6 @@ class MainViewModel @Inject constructor( private val DEFAULT_SELECTED_FOLDER = FolderRole.INBOX private const val REFRESH_DELAY = 2_000L // We add this delay because `etop` isn't always big enough. private const val MAX_REFRESH_DELAY = 6_000L - private const val EML_CONTENT_TYPE = "message/rfc822" } } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/folder/TwoPaneFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/main/folder/TwoPaneFragment.kt index 48bf4d3edb..2f46055afc 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/folder/TwoPaneFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/folder/TwoPaneFragment.kt @@ -41,7 +41,7 @@ import com.infomaniak.mail.ui.MainActivity import com.infomaniak.mail.ui.MainViewModel import com.infomaniak.mail.ui.main.search.SearchFragment import com.infomaniak.mail.ui.main.thread.ThreadFragment -import com.infomaniak.mail.ui.main.thread.actions.DownloadThreadsProgressDialog.Companion.DOWNLOAD_THREADS_RESULT +import com.infomaniak.mail.ui.main.thread.actions.DownloadMessagesProgressDialog import com.infomaniak.mail.utils.extensions.* import javax.inject.Inject @@ -121,7 +121,7 @@ abstract class TwoPaneFragment : Fragment() { private fun observeThreadNavigation() = with(twoPaneViewModel) { getBackNavigationResult(AttachmentExtensions.DOWNLOAD_ATTACHMENT_RESULT, ::startActivity) - getBackNavigationResult(DOWNLOAD_THREADS_RESULT, ::startActivity) + getBackNavigationResult(DownloadMessagesProgressDialog.DOWNLOAD_MESSAGES_RESULT, ::startActivity) newMessageArgs.observe(viewLifecycleOwner) { safeNavigateToNewMessageActivity(args = it.toBundle()) diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsProgressDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesProgressDialog.kt similarity index 88% rename from app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsProgressDialog.kt rename to app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesProgressDialog.kt index b13af290c2..30f0ad574f 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsProgressDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesProgressDialog.kt @@ -33,9 +33,9 @@ import com.infomaniak.mail.utils.KDriveUtils.canSaveOnKDrive import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint -class DownloadThreadsProgressDialog : DownloadProgressDialog() { - private val downloadThreadsViewModel: DownloadThreadsViewModel by viewModels() - private val navigationArgs: DownloadThreadsProgressDialogArgs by navArgs() +class DownloadMessagesProgressDialog : DownloadProgressDialog() { + private val downloadThreadsViewModel: DownloadMessagesViewModel by viewModels() + private val navigationArgs: DownloadMessagesProgressDialogArgs by navArgs() override val dialogTitle: String? by lazy { getDialogTitleFromArgs() } override val dialogIconDrawableRes: Int? by lazy { null } @@ -45,7 +45,7 @@ class DownloadThreadsProgressDialog : DownloadProgressDialog() { popBackStackWithError() } else { ArrayList(threadUris).openKDriveOrPlayStore(requireContext())?.let { openKDriveIntent -> - setBackNavigationResult(DOWNLOAD_THREADS_RESULT, openKDriveIntent) + setBackNavigationResult(DOWNLOAD_MESSAGES_RESULT, openKDriveIntent) } ?: run { findNavController().popBackStack() } } } @@ -75,6 +75,6 @@ class DownloadThreadsProgressDialog : DownloadProgressDialog() { } companion object { - const val DOWNLOAD_THREADS_RESULT = "download_threads_result" + const val DOWNLOAD_MESSAGES_RESULT = "download_messages_result" } } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt similarity index 95% rename from app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsViewModel.kt rename to app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt index c3e04423d3..b87e6c8a96 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadThreadsViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt @@ -38,7 +38,7 @@ import java.io.File import javax.inject.Inject @HiltViewModel -class DownloadThreadsViewModel @Inject constructor( +class DownloadMessagesViewModel @Inject constructor( application: Application, private val savedStateHandle: SavedStateHandle, private val messageController: MessageController, @@ -48,7 +48,7 @@ class DownloadThreadsViewModel @Inject constructor( private val ioCoroutineContext = viewModelScope.coroutineContext(ioDispatcher) private val messageLocalUids - inline get() = savedStateHandle.get>(DownloadThreadsProgressDialogArgs::messageUids.name)!! + inline get() = savedStateHandle.get>(DownloadMessagesProgressDialogArgs::messageUids.name)!! fun downloadThreads(currentMailbox: Mailbox?) = liveData(ioCoroutineContext) { val downloadedThreadUris: List? = runCatching { @@ -58,7 +58,7 @@ class DownloadThreadsViewModel @Inject constructor( messageLocalUids.forEach { messageUid -> val message = messageController.getMessage(messageUid) ?: return@runCatching null - val response = ApiRepository.getDownloadedAttachment(mailbox.uuid, message.folderId, message.shortUid) + val response = ApiRepository.getDownloadedMessage(mailbox.uuid, message.folderId, message.shortUid) if (!response.isSuccessful || response.body == null) return@runCatching null diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt index 295ab274c3..585d0fa8e0 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt @@ -177,7 +177,7 @@ class MessageActionsBottomSheetDialog : MailActionsBottomSheetDialog() { override fun onSaveKDrive() { trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) - navigateToDownloadThreadsProgressDialog( + navigateToDownloadMessagesProgressDialog( listOf(messageUid), mainViewModel.getSubject(threadUid), MessageActionsBottomSheetDialog::class.java.name diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt index 5fca7a7c24..18e4d532d6 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt @@ -41,7 +41,7 @@ import com.infomaniak.mail.ui.main.folder.ThreadListMultiSelection import com.infomaniak.mail.ui.main.folder.ThreadListMultiSelection.Companion.getReadIconAndShortText import com.infomaniak.mail.utils.extensions.animatedNavigation import com.infomaniak.mail.utils.extensions.deleteWithConfirmationPopup -import com.infomaniak.mail.utils.extensions.navigateToDownloadThreadsProgressDialog +import com.infomaniak.mail.utils.extensions.navigateToDownloadMessagesProgressDialog import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -122,7 +122,7 @@ class MultiSelectBottomSheetDialog : ActionsBottomSheetDialog() { } binding.saveKDrive.setClosingOnClickListener(shouldCloseMultiSelection = true) { trackMultiSelectActionEvent(ACTION_SAVE_KDRIVE_NAME, selectedThreadsCount, isFromBottomSheet = true) - navigateToDownloadThreadsProgressDialog( + navigateToDownloadMessagesProgressDialog( messageUids = mainViewModel.getMessagesUidsFromThreadUids(selectedThreadsUids), nameFirstMessage = selectedThreads.firstOrNull()?.subject, MultiSelectBottomSheetDialog::class.java.name, diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt index 23ad1ef584..3aeec162c9 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt @@ -198,7 +198,7 @@ class ThreadActionsBottomSheetDialog : MailActionsBottomSheetDialog() { override fun onSaveKDrive() { trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) - navigateToDownloadThreadsProgressDialog( + navigateToDownloadMessagesProgressDialog( messageUids = thread.messages.map { it.uid }, nameFirstMessage = thread.subject, ThreadActionsBottomSheetDialog::class.java.name diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt index 80b061a7e2..95659dd45b 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt @@ -36,7 +36,7 @@ import com.infomaniak.mail.ui.login.LoginActivity import com.infomaniak.mail.ui.login.LoginActivityArgs import com.infomaniak.mail.ui.login.NoMailboxActivity import com.infomaniak.mail.ui.main.thread.actions.AttachmentActionsBottomSheetDialog -import com.infomaniak.mail.ui.main.thread.actions.DownloadThreadsProgressDialogArgs +import com.infomaniak.mail.ui.main.thread.actions.DownloadMessagesProgressDialogArgs import com.infomaniak.mail.ui.newMessage.NewMessageActivityArgs import com.infomaniak.mail.ui.noValidMailboxes.NoValidMailboxesActivity import com.infomaniak.mail.utils.AccountUtils @@ -91,14 +91,14 @@ fun Fragment.navigateToDownloadProgressDialog( ) } -fun Fragment.navigateToDownloadThreadsProgressDialog( +fun Fragment.navigateToDownloadMessagesProgressDialog( messageUids: List, nameFirstMessage: String?, currentClassName: String ) { safeNavigate( - resId = R.id.downloadThreadsProgressDialog, - args = DownloadThreadsProgressDialogArgs( + resId = R.id.downloadMessagesProgressDialog, + args = DownloadMessagesProgressDialogArgs( messageUids = messageUids.toTypedArray(), nameFirstMessage = nameFirstMessage ).toBundle(), diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml index e489367f96..8a5a730782 100644 --- a/app/src/main/res/navigation/main_navigation.xml +++ b/app/src/main/res/navigation/main_navigation.xml @@ -591,9 +591,9 @@ Date: Fri, 22 Nov 2024 15:43:43 +0100 Subject: [PATCH 19/41] refactor: Apply suggestion from code review --- .../infomaniak/mail/data/api/ApiRepository.kt | 2 +- .../com/infomaniak/mail/ui/MainViewModel.kt | 9 ++++--- .../DownloadAttachmentProgressDialog.kt | 14 ++++++++-- .../actions/DownloadMessagesProgressDialog.kt | 15 ++++++----- .../actions/DownloadMessagesViewModel.kt | 26 ++++++++++++------- .../thread/actions/DownloadProgressDialog.kt | 14 +++------- .../MessageActionsBottomSheetDialog.kt | 6 ++--- .../actions/MultiSelectBottomSheetDialog.kt | 20 ++++++++++---- .../actions/ThreadActionsBottomSheetDialog.kt | 2 +- .../{KDriveUtils.kt => SaveOnKDriveUtils.kt} | 2 +- .../utils/extensions/AttachmentExtensions.kt | 6 ++--- .../utils/extensions/NavigationExtensions.kt | 4 +-- .../res/layout/dialog_download_progress.xml | 1 + .../main/res/navigation/main_navigation.xml | 3 +-- 14 files changed, 73 insertions(+), 51 deletions(-) rename app/src/main/java/com/infomaniak/mail/utils/{KDriveUtils.kt => SaveOnKDriveUtils.kt} (98%) diff --git a/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt b/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt index c177b3e52b..0b87932a3a 100644 --- a/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt +++ b/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt @@ -453,7 +453,7 @@ object ApiRepository : ApiRepositoryCore() { fun getDownloadedMessage(mailboxUuid: String, folderId: String, shortUid: Int): Response { val request = Request.Builder().url(ApiRoutes.downloadMessage(mailboxUuid, folderId, shortUid)) - .headers(HttpUtils.getHeaders(null)) + .headers(HttpUtils.getHeaders()) .get() .build() diff --git a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt index 6551fd16ef..9c618aa84a 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt @@ -1294,17 +1294,18 @@ class MainViewModel @Inject constructor( } } - fun getMessagesUidsFromThreadUids(selectedThreadsUuids: List): List { + fun getMessagesUidsFromThreadUids(selectedThreadsUids: List): List { val messageUids = mutableListOf() - selectedThreadsUuids.forEach { threadUuid -> + selectedThreadsUids.forEach { threadUuid -> val thread = threadController.getThread(threadUuid) ?: return@forEach messageUids.addAll(thread.messages.map { it.uid }) } + return messageUids } - fun getSubject(threadUuid: String): String? { - return threadController.getThread(threadUuid)?.subject + fun getSubject(threadUid: String): String { + return threadController.getThread(threadUid)?.subject ?: "" } companion object { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadAttachmentProgressDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadAttachmentProgressDialog.kt index f34d0744fa..1c0e40f487 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadAttachmentProgressDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadAttachmentProgressDialog.kt @@ -17,6 +17,12 @@ */ package com.infomaniak.mail.ui.main.thread.actions +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.content.res.AppCompatResources +import androidx.core.view.isVisible import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs @@ -25,13 +31,17 @@ import com.infomaniak.mail.utils.extensions.AttachmentExtensions import com.infomaniak.mail.utils.extensions.AttachmentExtensions.getIntentOrGoToPlayStore import dagger.hilt.android.AndroidEntryPoint -@AndroidEntryPoint class DownloadAttachmentProgressDialog : DownloadProgressDialog() { private val navigationArgs: DownloadAttachmentProgressDialogArgs by navArgs() private val downloadAttachmentViewModel: DownloadAttachmentViewModel by viewModels() override val dialogTitle: String? by lazy { navigationArgs.attachmentName } - override val dialogIconDrawableRes: Int? by lazy { navigationArgs.attachmentType.icon } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + binding.icon.isVisible = true + binding.icon.setImageDrawable(AppCompatResources.getDrawable(requireContext(), navigationArgs.attachmentType.icon)) + return super.onCreateView(inflater, container, savedInstanceState) + } override fun download() { downloadAttachmentViewModel.downloadAttachment().observe(this) { cachedAttachment -> diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesProgressDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesProgressDialog.kt index 30f0ad574f..de5068dd36 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesProgressDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesProgressDialog.kt @@ -27,17 +27,14 @@ import androidx.navigation.fragment.navArgs import com.infomaniak.lib.core.utils.goToPlayStore import com.infomaniak.lib.core.utils.setBackNavigationResult import com.infomaniak.mail.R -import com.infomaniak.mail.utils.KDriveUtils.DRIVE_PACKAGE -import com.infomaniak.mail.utils.KDriveUtils.SAVE_EXTERNAL_ACTIVITY_CLASS -import com.infomaniak.mail.utils.KDriveUtils.canSaveOnKDrive -import dagger.hilt.android.AndroidEntryPoint +import com.infomaniak.mail.utils.SaveOnKDriveUtils.DRIVE_PACKAGE +import com.infomaniak.mail.utils.SaveOnKDriveUtils.SAVE_EXTERNAL_ACTIVITY_CLASS +import com.infomaniak.mail.utils.SaveOnKDriveUtils.canSaveOnKDrive -@AndroidEntryPoint class DownloadMessagesProgressDialog : DownloadProgressDialog() { private val downloadThreadsViewModel: DownloadMessagesViewModel by viewModels() private val navigationArgs: DownloadMessagesProgressDialogArgs by navArgs() override val dialogTitle: String? by lazy { getDialogTitleFromArgs() } - override val dialogIconDrawableRes: Int? by lazy { null } override fun download() { downloadThreadsViewModel.downloadThreads(mainViewModel.currentMailbox.value).observe(this) { threadUris -> @@ -54,7 +51,11 @@ class DownloadMessagesProgressDialog : DownloadProgressDialog() { private fun getDialogTitleFromArgs() = if (navigationArgs.messageUids.size == 1) { navigationArgs.nameFirstMessage } else { - requireContext().resources.getQuantityString(R.plurals.downloadingEmailsTitle, 1, navigationArgs.messageUids.size) + requireContext().resources.getQuantityString( + R.plurals.downloadingEmailsTitle, + navigationArgs.messageUids.size, + navigationArgs.messageUids.size + ) } private fun ArrayList.openKDriveOrPlayStore(context: Context): Intent? { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt index b87e6c8a96..9152a93e45 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt @@ -26,6 +26,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.liveData import androidx.lifecycle.viewModelScope import com.infomaniak.lib.core.utils.DownloadManagerUtils +import com.infomaniak.mail.R import com.infomaniak.mail.data.api.ApiRepository import com.infomaniak.mail.data.cache.mailboxContent.MessageController import com.infomaniak.mail.data.models.mailbox.Mailbox @@ -47,23 +48,28 @@ class DownloadMessagesViewModel @Inject constructor( private val ioCoroutineContext = viewModelScope.coroutineContext(ioDispatcher) - private val messageLocalUids - inline get() = savedStateHandle.get>(DownloadMessagesProgressDialogArgs::messageUids.name)!! + private val messageLocalUids: Array + inline get() = savedStateHandle[DownloadMessagesProgressDialogArgs::messageUids.name]!! fun downloadThreads(currentMailbox: Mailbox?) = liveData(ioCoroutineContext) { val downloadedThreadUris: List? = runCatching { val mailbox = currentMailbox ?: return@runCatching null val listUri = mutableListOf() - val listFileName = mutableSetOf().also { it.addAll(getAllFileNameInExportEmlDir(appContext)) } + val listFileName = getAllFileNameInExportEmlDir(appContext).toMutableSet() messageLocalUids.forEach { messageUid -> val message = messageController.getMessage(messageUid) ?: return@runCatching null - val response = ApiRepository.getDownloadedMessage(mailbox.uuid, message.folderId, message.shortUid) + val response = ApiRepository.getDownloadedMessage( + mailboxUuid = mailbox.uuid, + folderId = message.folderId, + shortUid = message.shortUid, + contentType = EML_CONTENT_TYPE, + ) if (!response.isSuccessful || response.body == null) return@runCatching null val messageSubject: String = message.subject?.removeIllegalFileNameCharacter() ?: NO_SUBJECT_FILE - createOriginalFileName(messageSubject, listFileName.toList()).let { fileName -> + createUniqueFileName(messageSubject, listFileName.toList()).let { fileName -> listFileName.add(fileName) saveEmlToFile(appContext, response.body!!.bytes(), fileName)?.let { listUri.add(it) } } @@ -75,7 +81,7 @@ class DownloadMessagesViewModel @Inject constructor( } // TODO Extract this code in core2 - private fun createOriginalFileName(originalFileName: String, listFileName: List): String { + private fun createUniqueFileName(originalFileName: String, listFileName: List): String { var postfix = 1 var fileName = originalFileName @@ -85,21 +91,21 @@ class DownloadMessagesViewModel @Inject constructor( } private fun getAllFileNameInExportEmlDir(context: Context): List { - val fileDir = File(context.cacheDir, context.getString(com.infomaniak.mail.R.string.EXPOSED_EML_PATH)) + val fileDir = File(context.cacheDir, context.getString(R.string.EXPOSED_EML_PATH)) if (!fileDir.exists()) fileDir.mkdirs() return fileDir.listFiles()?.map { it.name.removeSuffix(".eml") } ?: emptyList() } private fun saveEmlToFile(context: Context, emlByteArray: ByteArray, fileName: String): Uri? { val fileNameWithExtension = "${fileName.removeIllegalFileNameCharacter()}.eml" - val fileDir = File(context.cacheDir, context.getString(com.infomaniak.mail.R.string.EXPOSED_EML_PATH)) + val fileDir = File(context.cacheDir, context.getString(R.string.EXPOSED_EML_PATH)) if (!fileDir.exists()) fileDir.mkdirs() runCatching { val file = File(fileDir, fileNameWithExtension) file.outputStream().use { it.write(emlByteArray) } - return FileProvider.getUriForFile(context, context.getString(com.infomaniak.mail.R.string.EML_AUTHORITY), file) + return FileProvider.getUriForFile(context, context.getString(R.string.EML_AUTHORITY), file) }.onFailure { exception -> exception.printStackTrace() } @@ -109,7 +115,7 @@ class DownloadMessagesViewModel @Inject constructor( private fun String.removeIllegalFileNameCharacter(): String = this.replace(DownloadManagerUtils.regexInvalidSystemChar, "") companion object { - private const val EML_CONTENT_TYPE = "message/rfc822" private const val NO_SUBJECT_FILE = "message" + private const val EML_CONTENT_TYPE = "message/rfc822" } } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadProgressDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadProgressDialog.kt index 36880ea3a5..7334080963 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadProgressDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadProgressDialog.kt @@ -20,9 +20,6 @@ package com.infomaniak.mail.ui.main.thread.actions import android.app.Dialog import android.os.Bundle import android.view.KeyEvent -import androidx.annotation.DrawableRes -import androidx.appcompat.content.res.AppCompatResources -import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope @@ -38,10 +35,13 @@ import kotlinx.coroutines.launch @AndroidEntryPoint abstract class DownloadProgressDialog : DialogFragment() { + protected val binding by lazy { DialogDownloadProgressBinding.inflate(layoutInflater) } protected val mainViewModel: MainViewModel by activityViewModels() + abstract val dialogTitle: String? - @get:DrawableRes abstract val dialogIconDrawableRes: Int? + + protected abstract fun download() override fun onStart() { download() @@ -50,10 +50,6 @@ abstract class DownloadProgressDialog : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val context = requireContext() - - dialogIconDrawableRes?.let { binding.icon.setImageDrawable(AppCompatResources.getDrawable(requireContext(), it)) } - binding.icon.isVisible = dialogIconDrawableRes == null - isCancelable = false return MaterialAlertDialogBuilder(context) @@ -68,8 +64,6 @@ abstract class DownloadProgressDialog : DialogFragment() { .create() } - abstract fun download() - protected fun popBackStackWithError() { lifecycleScope.launch { mainViewModel.isNetworkAvailable.first { it != null }?.let { isNetworkAvailable -> diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt index 585d0fa8e0..4f84927f4b 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt @@ -178,9 +178,9 @@ class MessageActionsBottomSheetDialog : MailActionsBottomSheetDialog() { override fun onSaveKDrive() { trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) navigateToDownloadMessagesProgressDialog( - listOf(messageUid), - mainViewModel.getSubject(threadUid), - MessageActionsBottomSheetDialog::class.java.name + messageUids = listOf(messageUid), + nameFirstMessage = mainViewModel.getSubject(threadUid), + currentClassName = MessageActionsBottomSheetDialog::class.java.name, ) } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt index 18e4d532d6..aa9b1c049b 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt @@ -22,6 +22,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels +import com.infomaniak.lib.core.utils.SentryLog import com.infomaniak.lib.core.utils.safeBinding import com.infomaniak.mail.MatomoMail.ACTION_ARCHIVE_NAME import com.infomaniak.mail.MatomoMail.ACTION_DELETE_NAME @@ -120,13 +121,18 @@ class MultiSelectBottomSheetDialog : ActionsBottomSheetDialog() { toggleThreadsFavoriteStatus(threadsUids, shouldFavorite) isMultiSelectOn = false } + binding.saveKDrive.setClosingOnClickListener(shouldCloseMultiSelection = true) { trackMultiSelectActionEvent(ACTION_SAVE_KDRIVE_NAME, selectedThreadsCount, isFromBottomSheet = true) - navigateToDownloadMessagesProgressDialog( - messageUids = mainViewModel.getMessagesUidsFromThreadUids(selectedThreadsUids), - nameFirstMessage = selectedThreads.firstOrNull()?.subject, - MultiSelectBottomSheetDialog::class.java.name, - ) + runCatching { + navigateToDownloadMessagesProgressDialog( + messageUids = mainViewModel.getMessagesUidsFromThreadUids(selectedThreadsUids), + nameFirstMessage = mainViewModel.getSubject(selectedThreadsUids.first()), + currentClassName = MultiSelectBottomSheetDialog::class.java.name, + ) + }.onFailure { + SentryLog.e(TAG, "SelectedThreadUids is empty, it should not happened") + } isMultiSelectOn = false } } @@ -156,4 +162,8 @@ class MultiSelectBottomSheetDialog : ActionsBottomSheetDialog() { private fun getSpamIconAndText(isFromSpam: Boolean): Pair { return if (isFromSpam) R.drawable.ic_non_spam to R.string.actionNonSpam else R.drawable.ic_spam to R.string.actionSpam } + + companion object { + private val TAG = this::class.java.simpleName + } } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt index 3aeec162c9..6241f2c365 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt @@ -200,7 +200,7 @@ class ThreadActionsBottomSheetDialog : MailActionsBottomSheetDialog() { trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) navigateToDownloadMessagesProgressDialog( messageUids = thread.messages.map { it.uid }, - nameFirstMessage = thread.subject, + nameFirstMessage = thread.subject ?: "", ThreadActionsBottomSheetDialog::class.java.name ) } diff --git a/app/src/main/java/com/infomaniak/mail/utils/KDriveUtils.kt b/app/src/main/java/com/infomaniak/mail/utils/SaveOnKDriveUtils.kt similarity index 98% rename from app/src/main/java/com/infomaniak/mail/utils/KDriveUtils.kt rename to app/src/main/java/com/infomaniak/mail/utils/SaveOnKDriveUtils.kt index 98029ef026..095b09e1cb 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/KDriveUtils.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/SaveOnKDriveUtils.kt @@ -21,7 +21,7 @@ import android.content.Context import android.content.pm.PackageManager import io.sentry.Sentry -object KDriveUtils { +object SaveOnKDriveUtils { const val DRIVE_PACKAGE = "com.infomaniak.drive" const val SAVE_EXTERNAL_ACTIVITY_CLASS = "com.infomaniak.drive.ui.SaveExternalFilesActivity" diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt index b699756257..65cf9180a0 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt @@ -37,9 +37,9 @@ import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.ui.main.SnackbarManager import com.infomaniak.mail.ui.main.thread.actions.DownloadAttachmentProgressDialogArgs import com.infomaniak.mail.utils.AccountUtils -import com.infomaniak.mail.utils.KDriveUtils.DRIVE_PACKAGE -import com.infomaniak.mail.utils.KDriveUtils.SAVE_EXTERNAL_ACTIVITY_CLASS -import com.infomaniak.mail.utils.KDriveUtils.canSaveOnKDrive +import com.infomaniak.mail.utils.SaveOnKDriveUtils.DRIVE_PACKAGE +import com.infomaniak.mail.utils.SaveOnKDriveUtils.SAVE_EXTERNAL_ACTIVITY_CLASS +import com.infomaniak.mail.utils.SaveOnKDriveUtils.canSaveOnKDrive import com.infomaniak.mail.utils.SentryDebug import com.infomaniak.mail.utils.WorkerUtils.UploadMissingLocalFileException import com.infomaniak.mail.utils.extensions.AttachmentExtensions.AttachmentIntentType.OPEN_WITH diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt index 95659dd45b..6f84e84da9 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt @@ -93,8 +93,8 @@ fun Fragment.navigateToDownloadProgressDialog( fun Fragment.navigateToDownloadMessagesProgressDialog( messageUids: List, - nameFirstMessage: String?, - currentClassName: String + nameFirstMessage: String, + currentClassName: String, ) { safeNavigate( resId = R.id.downloadMessagesProgressDialog, diff --git a/app/src/main/res/layout/dialog_download_progress.xml b/app/src/main/res/layout/dialog_download_progress.xml index a6e6ebcc20..d496f61e0e 100644 --- a/app/src/main/res/layout/dialog_download_progress.xml +++ b/app/src/main/res/layout/dialog_download_progress.xml @@ -29,6 +29,7 @@ android:layout_marginStart="@dimen/marginStandardMedium" android:importantForAccessibility="no" android:src="@drawable/ic_file_unknown" + android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml index 8a5a730782..869def6863 100644 --- a/app/src/main/res/navigation/main_navigation.xml +++ b/app/src/main/res/navigation/main_navigation.xml @@ -600,8 +600,7 @@ app:argType="string[]" /> + app:argType="string" /> Date: Mon, 16 Dec 2024 08:21:18 +0100 Subject: [PATCH 20/41] refactor: Put EML_CONTENT_TYPE for all --- .../main/java/com/infomaniak/mail/data/api/ApiRepository.kt | 4 +++- .../mail/ui/main/thread/actions/DownloadMessagesViewModel.kt | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt b/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt index 0b87932a3a..0743b4a512 100644 --- a/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt +++ b/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt @@ -452,8 +452,10 @@ object ApiRepository : ApiRepositoryCore() { } fun getDownloadedMessage(mailboxUuid: String, folderId: String, shortUid: Int): Response { + val emlContentType = "message/rfc822" + val request = Request.Builder().url(ApiRoutes.downloadMessage(mailboxUuid, folderId, shortUid)) - .headers(HttpUtils.getHeaders()) + .headers(HttpUtils.getHeaders(emlContentType)) .get() .build() diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt index 9152a93e45..387d69a204 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt @@ -63,7 +63,6 @@ class DownloadMessagesViewModel @Inject constructor( mailboxUuid = mailbox.uuid, folderId = message.folderId, shortUid = message.shortUid, - contentType = EML_CONTENT_TYPE, ) if (!response.isSuccessful || response.body == null) return@runCatching null @@ -116,6 +115,5 @@ class DownloadMessagesViewModel @Inject constructor( companion object { private const val NO_SUBJECT_FILE = "message" - private const val EML_CONTENT_TYPE = "message/rfc822" } } From 77f10760134334112b2e1c06e7573a6db30d10a5 Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Mon, 16 Dec 2024 09:21:05 +0100 Subject: [PATCH 21/41] refactor: Apply suggestion from code review --- .../mail/ui/main/thread/actions/DownloadProgressDialog.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadProgressDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadProgressDialog.kt index 7334080963..176b7d6bcf 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadProgressDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadProgressDialog.kt @@ -49,10 +49,9 @@ abstract class DownloadProgressDialog : DialogFragment() { } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val context = requireContext() isCancelable = false - return MaterialAlertDialogBuilder(context) + return MaterialAlertDialogBuilder(requireContext()) .setTitle(dialogTitle) .setView(binding.root) .setOnKeyListener { _, keyCode, event -> From 7f7df650ea7ab46e78f54e9b9d3f1e9fc29a2613 Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Mon, 16 Dec 2024 15:26:49 +0100 Subject: [PATCH 22/41] refactor: Remove 'EXPOSED_EML_PATH' from 'build.gradle' --- app/build.gradle | 2 -- .../mail/ui/main/thread/actions/DownloadMessagesViewModel.kt | 5 +++-- .../main/java/com/infomaniak/mail/utils/LocalStorageUtils.kt | 3 +++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 872927fe41..3e594c74f9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,8 +50,6 @@ android { resValue 'string', 'EML_AUTHORITY', 'com.infomaniak.mail.eml' resValue 'string', 'FILES_AUTHORITY', 'com.infomaniak.mail.attachments;com.infomaniak.mail.eml' - resValue 'string', 'EXPOSED_EML_PATH', 'eml_export' - resourceConfigurations += ["en", "de", "es", "fr", "it"] } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt index 387d69a204..f1a09e3d72 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt @@ -31,6 +31,7 @@ import com.infomaniak.mail.data.api.ApiRepository import com.infomaniak.mail.data.cache.mailboxContent.MessageController import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.di.IoDispatcher +import com.infomaniak.mail.utils.LocalStorageUtils.getEmlCacheDir import com.infomaniak.mail.utils.coroutineContext import com.infomaniak.mail.utils.extensions.appContext import dagger.hilt.android.lifecycle.HiltViewModel @@ -90,14 +91,14 @@ class DownloadMessagesViewModel @Inject constructor( } private fun getAllFileNameInExportEmlDir(context: Context): List { - val fileDir = File(context.cacheDir, context.getString(R.string.EXPOSED_EML_PATH)) + val fileDir = getEmlCacheDir(context) if (!fileDir.exists()) fileDir.mkdirs() return fileDir.listFiles()?.map { it.name.removeSuffix(".eml") } ?: emptyList() } private fun saveEmlToFile(context: Context, emlByteArray: ByteArray, fileName: String): Uri? { val fileNameWithExtension = "${fileName.removeIllegalFileNameCharacter()}.eml" - val fileDir = File(context.cacheDir, context.getString(R.string.EXPOSED_EML_PATH)) + val fileDir = getEmlCacheDir(context) if (!fileDir.exists()) fileDir.mkdirs() diff --git a/app/src/main/java/com/infomaniak/mail/utils/LocalStorageUtils.kt b/app/src/main/java/com/infomaniak/mail/utils/LocalStorageUtils.kt index d682a8d95f..d47a927040 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/LocalStorageUtils.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/LocalStorageUtils.kt @@ -36,11 +36,14 @@ object LocalStorageUtils { private const val ATTACHMENTS_UPLOAD_DIR = "attachments_upload" private const val HIDDEN_FILE_NAME = "HIDDEN_FILE_NAME" private const val NAME_TOO_LONG_EXCEPTION = "ENAMETOOLONG" + private const val EML_CACHE_DIR = "eml_export" private inline val Context.attachmentsCacheRootDir get() = File(cacheDir, ATTACHMENTS_CACHE_DIR) private inline val Context.attachmentsUploadRootDir get() = File(filesDir, ATTACHMENTS_UPLOAD_DIR) //region Cache + fun getEmlCacheDir(context: Context): File = File(context.cacheDir, EML_CACHE_DIR) + fun getAttachmentsCacheDir( context: Context, attachmentPath: String, From f9305d189cb9554ed1de9d7378bb87bdb5ff5b3d Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Tue, 21 Jan 2025 08:46:16 +0100 Subject: [PATCH 23/41] refactor: Optimize and remove 'while' loop --- .../com/infomaniak/mail/ui/MainViewModel.kt | 4 +- .../actions/DownloadMessagesViewModel.kt | 46 +++++++++++++------ .../MessageActionsBottomSheetDialog.kt | 2 +- .../actions/ThreadActionsBottomSheetDialog.kt | 2 +- .../utils/extensions/NavigationExtensions.kt | 2 +- 5 files changed, 36 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt index 9c618aa84a..fdf14d9d5d 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt @@ -1294,8 +1294,8 @@ class MainViewModel @Inject constructor( } } - fun getMessagesUidsFromThreadUids(selectedThreadsUids: List): List { - val messageUids = mutableListOf() + fun getMessagesUidsFromThreadUids(selectedThreadsUids: List): Set { + val messageUids = mutableSetOf() selectedThreadsUids.forEach { threadUuid -> val thread = threadController.getThread(threadUuid) ?: return@forEach messageUids.addAll(thread.messages.map { it.uid }) diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt index f1a09e3d72..8f30e71e39 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt @@ -56,7 +56,7 @@ class DownloadMessagesViewModel @Inject constructor( val downloadedThreadUris: List? = runCatching { val mailbox = currentMailbox ?: return@runCatching null val listUri = mutableListOf() - val listFileName = getAllFileNameInExportEmlDir(appContext).toMutableSet() + val listFileName = getAllFileNameInExportEmlDir(appContext) messageLocalUids.forEach { messageUid -> val message = messageController.getMessage(messageUid) ?: return@runCatching null @@ -69,10 +69,16 @@ class DownloadMessagesViewModel @Inject constructor( if (!response.isSuccessful || response.body == null) return@runCatching null val messageSubject: String = message.subject?.removeIllegalFileNameCharacter() ?: NO_SUBJECT_FILE - createUniqueFileName(messageSubject, listFileName.toList()).let { fileName -> - listFileName.add(fileName) - saveEmlToFile(appContext, response.body!!.bytes(), fileName)?.let { listUri.add(it) } + + val fileName = if (listFileName[messageSubject] == null) { + listFileName[messageSubject] = 0 + messageSubject + } else { + listFileName[messageSubject] = listFileName[messageSubject]!! + 1 + "$messageSubject (${listFileName[messageSubject]!! + 1})" } + + saveEmlToFile(appContext, response.body!!.bytes(), fileName)?.let { listUri.add(it) } } listUri }.getOrNull() @@ -80,20 +86,30 @@ class DownloadMessagesViewModel @Inject constructor( emit(downloadedThreadUris) } - // TODO Extract this code in core2 - private fun createUniqueFileName(originalFileName: String, listFileName: List): String { - var postfix = 1 - var fileName = originalFileName + private fun getAllFileNameInExportEmlDir(context: Context): HashMap { - while (listFileName.contains(fileName)) fileName = "$originalFileName (${postfix++})" - - return fileName - } - - private fun getAllFileNameInExportEmlDir(context: Context): List { + val fileNameMap = HashMap() val fileDir = getEmlCacheDir(context) if (!fileDir.exists()) fileDir.mkdirs() - return fileDir.listFiles()?.map { it.name.removeSuffix(".eml") } ?: emptyList() + + fileDir.listFiles()?.forEach { file -> + val nameWithoutExtension = file.name.removeSuffix(".eml") + + val regex = Regex("\\((\\d+)\\)$") + val match = regex.find(nameWithoutExtension) + val numberInParentheses = match?.groupValues?.last() + + var fileNumber = numberInParentheses?.toInt() ?: 0 + val fileName = nameWithoutExtension.replace(regex, "").trim() + + fileNameMap[fileName]?.let { + fileNumber = if (it > fileNumber) it else fileNumber + } + + fileNameMap[fileName] = fileNumber + } + + return fileNameMap } private fun saveEmlToFile(context: Context, emlByteArray: ByteArray, fileName: String): Uri? { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt index 4f84927f4b..6cb149467d 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MessageActionsBottomSheetDialog.kt @@ -178,7 +178,7 @@ class MessageActionsBottomSheetDialog : MailActionsBottomSheetDialog() { override fun onSaveKDrive() { trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) navigateToDownloadMessagesProgressDialog( - messageUids = listOf(messageUid), + messageUids = setOf(messageUid), nameFirstMessage = mainViewModel.getSubject(threadUid), currentClassName = MessageActionsBottomSheetDialog::class.java.name, ) diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt index 6241f2c365..a754b08ad4 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ThreadActionsBottomSheetDialog.kt @@ -199,7 +199,7 @@ class ThreadActionsBottomSheetDialog : MailActionsBottomSheetDialog() { override fun onSaveKDrive() { trackBottomSheetThreadActionsEvent(ACTION_SAVE_KDRIVE_NAME) navigateToDownloadMessagesProgressDialog( - messageUids = thread.messages.map { it.uid }, + messageUids = thread.messages.map { it.uid }.toSet(), nameFirstMessage = thread.subject ?: "", ThreadActionsBottomSheetDialog::class.java.name ) diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt index 6f84e84da9..1ad902d488 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt @@ -92,7 +92,7 @@ fun Fragment.navigateToDownloadProgressDialog( } fun Fragment.navigateToDownloadMessagesProgressDialog( - messageUids: List, + messageUids: Set, nameFirstMessage: String, currentClassName: String, ) { From c6c616afbeab94d39af4bfe36e3c5919a7da00f7 Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Tue, 21 Jan 2025 14:10:28 +0100 Subject: [PATCH 24/41] refactor: Replace ArrayList by List for maintainability --- .../main/thread/actions/DownloadMessagesProgressDialog.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesProgressDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesProgressDialog.kt index de5068dd36..e822c4cb4e 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesProgressDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesProgressDialog.kt @@ -41,7 +41,7 @@ class DownloadMessagesProgressDialog : DownloadProgressDialog() { if (threadUris == null) { popBackStackWithError() } else { - ArrayList(threadUris).openKDriveOrPlayStore(requireContext())?.let { openKDriveIntent -> + threadUris.openKDriveOrPlayStore(requireContext())?.let { openKDriveIntent -> setBackNavigationResult(DOWNLOAD_MESSAGES_RESULT, openKDriveIntent) } ?: run { findNavController().popBackStack() } } @@ -58,7 +58,7 @@ class DownloadMessagesProgressDialog : DownloadProgressDialog() { ) } - private fun ArrayList.openKDriveOrPlayStore(context: Context): Intent? { + private fun List.openKDriveOrPlayStore(context: Context): Intent? { return if (canSaveOnKDrive(context)) { saveToDriveIntent() } else { @@ -67,11 +67,11 @@ class DownloadMessagesProgressDialog : DownloadProgressDialog() { } } - private fun ArrayList.saveToDriveIntent(): Intent { + private fun List.saveToDriveIntent(): Intent { return Intent().apply { component = ComponentName(DRIVE_PACKAGE, SAVE_EXTERNAL_ACTIVITY_CLASS) action = Intent.ACTION_SEND_MULTIPLE - putParcelableArrayListExtra(Intent.EXTRA_STREAM, this@saveToDriveIntent) + putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(this@saveToDriveIntent)) } } From a9865c13c3248af4e2e5ff4779f822446ecac276 Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Tue, 21 Jan 2025 16:04:40 +0100 Subject: [PATCH 25/41] feat: Delete all files in cache dir after share them to kDrive --- .../mail/ui/main/folder/TwoPaneFragment.kt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/folder/TwoPaneFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/main/folder/TwoPaneFragment.kt index 2f46055afc..2df92f55f7 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/folder/TwoPaneFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/folder/TwoPaneFragment.kt @@ -17,9 +17,11 @@ */ package com.infomaniak.mail.ui.main.folder +import android.app.Activity import android.content.res.Configuration import android.os.Bundle import android.view.View +import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import androidx.annotation.ColorRes import androidx.core.content.res.ResourcesCompat import androidx.core.view.isGone @@ -42,7 +44,9 @@ import com.infomaniak.mail.ui.MainViewModel import com.infomaniak.mail.ui.main.search.SearchFragment import com.infomaniak.mail.ui.main.thread.ThreadFragment import com.infomaniak.mail.ui.main.thread.actions.DownloadMessagesProgressDialog +import com.infomaniak.mail.utils.LocalStorageUtils.getEmlCacheDir import com.infomaniak.mail.utils.extensions.* +import java.io.File import javax.inject.Inject abstract class TwoPaneFragment : Fragment() { @@ -119,9 +123,21 @@ abstract class TwoPaneFragment : Fragment() { } } + private val resultActivityResultLauncher = registerForActivityResult(StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK) { + val fileDir: File = getEmlCacheDir(requireContext()) + + if (!fileDir.exists()) fileDir.mkdirs() + + if (!fileDir.deleteRecursively()) { + // TODO: Manage error + } + } + } + private fun observeThreadNavigation() = with(twoPaneViewModel) { getBackNavigationResult(AttachmentExtensions.DOWNLOAD_ATTACHMENT_RESULT, ::startActivity) - getBackNavigationResult(DownloadMessagesProgressDialog.DOWNLOAD_MESSAGES_RESULT, ::startActivity) + getBackNavigationResult(DownloadMessagesProgressDialog.DOWNLOAD_MESSAGES_RESULT, resultActivityResultLauncher::launch) newMessageArgs.observe(viewLifecycleOwner) { safeNavigateToNewMessageActivity(args = it.toBundle()) From 166fd1886a9cbd9cab6ff585bae51346c3ee5aac Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Tue, 21 Jan 2025 16:05:16 +0100 Subject: [PATCH 26/41] refactor: Allow to manage error --- .../main/thread/actions/DownloadMessagesViewModel.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt index 8f30e71e39..604b4d68a5 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt @@ -78,7 +78,9 @@ class DownloadMessagesViewModel @Inject constructor( "$messageSubject (${listFileName[messageSubject]!! + 1})" } - saveEmlToFile(appContext, response.body!!.bytes(), fileName)?.let { listUri.add(it) } + saveEmlToFile(appContext, response.body!!.bytes(), fileName)?.let(listUri::add) ?: { + // TODO: Manage error case + } } listUri }.getOrNull() @@ -118,14 +120,11 @@ class DownloadMessagesViewModel @Inject constructor( if (!fileDir.exists()) fileDir.mkdirs() - runCatching { + return runCatching { val file = File(fileDir, fileNameWithExtension) file.outputStream().use { it.write(emlByteArray) } return FileProvider.getUriForFile(context, context.getString(R.string.EML_AUTHORITY), file) - }.onFailure { exception -> - exception.printStackTrace() - } - return null + }.getOrNull() } private fun String.removeIllegalFileNameCharacter(): String = this.replace(DownloadManagerUtils.regexInvalidSystemChar, "") From b50229e3d227c9a5e0204ba30576050956c659e2 Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Tue, 21 Jan 2025 16:11:48 +0100 Subject: [PATCH 27/41] feat: Limit file name length --- .../mail/ui/main/thread/actions/DownloadMessagesViewModel.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt index 604b4d68a5..d7b9d4fc81 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt @@ -69,6 +69,7 @@ class DownloadMessagesViewModel @Inject constructor( if (!response.isSuccessful || response.body == null) return@runCatching null val messageSubject: String = message.subject?.removeIllegalFileNameCharacter() ?: NO_SUBJECT_FILE + .take(MAX_FILE_NAME_LENGTH) val fileName = if (listFileName[messageSubject] == null) { listFileName[messageSubject] = 0 @@ -131,5 +132,6 @@ class DownloadMessagesViewModel @Inject constructor( companion object { private const val NO_SUBJECT_FILE = "message" + private const val MAX_FILE_NAME_LENGTH = 256 } } From e46f5100c18963239be4e39258df3dac01f8136c Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Tue, 21 Jan 2025 16:21:25 +0100 Subject: [PATCH 28/41] refactor: No need to check for existing files because they are deleted before --- .../actions/DownloadMessagesViewModel.kt | 28 +------------------ 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt index d7b9d4fc81..e0e7c4e4d0 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt @@ -56,7 +56,7 @@ class DownloadMessagesViewModel @Inject constructor( val downloadedThreadUris: List? = runCatching { val mailbox = currentMailbox ?: return@runCatching null val listUri = mutableListOf() - val listFileName = getAllFileNameInExportEmlDir(appContext) + val listFileName = hashMapOf() messageLocalUids.forEach { messageUid -> val message = messageController.getMessage(messageUid) ?: return@runCatching null @@ -89,32 +89,6 @@ class DownloadMessagesViewModel @Inject constructor( emit(downloadedThreadUris) } - private fun getAllFileNameInExportEmlDir(context: Context): HashMap { - - val fileNameMap = HashMap() - val fileDir = getEmlCacheDir(context) - if (!fileDir.exists()) fileDir.mkdirs() - - fileDir.listFiles()?.forEach { file -> - val nameWithoutExtension = file.name.removeSuffix(".eml") - - val regex = Regex("\\((\\d+)\\)$") - val match = regex.find(nameWithoutExtension) - val numberInParentheses = match?.groupValues?.last() - - var fileNumber = numberInParentheses?.toInt() ?: 0 - val fileName = nameWithoutExtension.replace(regex, "").trim() - - fileNameMap[fileName]?.let { - fileNumber = if (it > fileNumber) it else fileNumber - } - - fileNameMap[fileName] = fileNumber - } - - return fileNameMap - } - private fun saveEmlToFile(context: Context, emlByteArray: ByteArray, fileName: String): Uri? { val fileNameWithExtension = "${fileName.removeIllegalFileNameCharacter()}.eml" val fileDir = getEmlCacheDir(context) From 398119dcf3c6defad0e65d15776bebb263e95c55 Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Wed, 22 Jan 2025 15:52:18 +0100 Subject: [PATCH 29/41] feat: Optimize message downloads with parallel execution --- .../actions/DownloadMessagesViewModel.kt | 57 +++++++++++-------- .../utils/extensions/AttachmentExtensions.kt | 1 + 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt index e0e7c4e4d0..238e2069b4 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt @@ -35,7 +35,7 @@ import com.infomaniak.mail.utils.LocalStorageUtils.getEmlCacheDir import com.infomaniak.mail.utils.coroutineContext import com.infomaniak.mail.utils.extensions.appContext import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.* import java.io.File import javax.inject.Inject @@ -58,31 +58,42 @@ class DownloadMessagesViewModel @Inject constructor( val listUri = mutableListOf() val listFileName = hashMapOf() - messageLocalUids.forEach { messageUid -> - val message = messageController.getMessage(messageUid) ?: return@runCatching null - val response = ApiRepository.getDownloadedMessage( - mailboxUuid = mailbox.uuid, - folderId = message.folderId, - shortUid = message.shortUid, - ) - - if (!response.isSuccessful || response.body == null) return@runCatching null - - val messageSubject: String = message.subject?.removeIllegalFileNameCharacter() ?: NO_SUBJECT_FILE - .take(MAX_FILE_NAME_LENGTH) - - val fileName = if (listFileName[messageSubject] == null) { - listFileName[messageSubject] = 0 - messageSubject - } else { - listFileName[messageSubject] = listFileName[messageSubject]!! + 1 - "$messageSubject (${listFileName[messageSubject]!! + 1})" + coroutineScope { + val deferredResponses: List> = messageLocalUids.map { messageUid -> + async { + val message = messageController.getMessage(messageUid) ?: return@async null + val response = ApiRepository.getDownloadedMessage( + mailboxUuid = mailbox.uuid, + folderId = message.folderId, + shortUid = message.shortUid, + ) + + val messageSubject: String = message.subject?.removeIllegalFileNameCharacter() + ?: NO_SUBJECT_FILE + val truncatedSubject = messageSubject.take(MAX_FILE_NAME_LENGTH) + + val fileName = if (listFileName[truncatedSubject] == null) { + listFileName[truncatedSubject] = 0 + truncatedSubject + } else { + listFileName[truncatedSubject] = listFileName[truncatedSubject]!! + 1 + "$truncatedSubject (${listFileName[truncatedSubject]!! + 1})" + } + + if (!response.isSuccessful || response.body == null) return@async null + + saveEmlToFile(appContext, response.body!!.bytes(), fileName).also { + if (it == null) { + // TODO: Manage error case + } + } + } } - saveEmlToFile(appContext, response.body!!.bytes(), fileName)?.let(listUri::add) ?: { - // TODO: Manage error case - } + val uris = deferredResponses.awaitAll() + listUri.addAll(uris.filterNotNull()) } + listUri }.getOrNull() diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt index 65cf9180a0..416fdbdd1b 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExtensions.kt @@ -20,6 +20,7 @@ package com.infomaniak.mail.utils.extensions import android.content.ComponentName import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.os.Bundle import android.provider.MediaStore.Files.FileColumns import androidx.core.content.FileProvider From b49ee5340107060e5f2acddee752566a7df4319e Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Mon, 27 Jan 2025 09:44:40 +0100 Subject: [PATCH 30/41] refactor: Send both 'threadUid' and 'messagesUid' to put all logic together after --- .idea/navEditor.xml | 2 +- .../com/infomaniak/mail/ui/MainViewModel.kt | 14 -------- .../actions/DownloadMessagesProgressDialog.kt | 21 ++++++----- .../actions/DownloadMessagesViewModel.kt | 35 ++++++++++++++++--- .../MessageActionsBottomSheetDialog.kt | 3 +- .../actions/MultiSelectBottomSheetDialog.kt | 3 +- .../actions/ThreadActionsBottomSheetDialog.kt | 5 ++- .../utils/extensions/NavigationExtensions.kt | 8 ++--- .../main/res/navigation/main_navigation.xml | 10 +++--- 9 files changed, 58 insertions(+), 43 deletions(-) diff --git a/.idea/navEditor.xml b/.idea/navEditor.xml index 94f86c2952..8960a98ada 100644 --- a/.idea/navEditor.xml +++ b/.idea/navEditor.xml @@ -310,7 +310,7 @@ - + + + + + + + + + + + + + + + @@ -566,6 +590,18 @@ + + + + + + + diff --git a/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt b/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt index 97a3d8ca8a..18d8cf2721 100644 --- a/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt +++ b/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt @@ -452,13 +452,8 @@ object ApiRepository : ApiRepositoryCore() { return callApi(url = ApiRoutes.shareLink(mailboxUuid, folderId, mailId), method = POST) } - fun getDownloadedMessage(mailboxUuid: String, folderId: String, shortUid: Int): Response { - val request = Request.Builder().url(ApiRoutes.downloadMessage(mailboxUuid, folderId, shortUid)) - .headers(HttpUtils.getHeaders(EML_CONTENT_TYPE)) - .get() - .build() - - return HttpClient.okHttpClient.newCall(request).execute() + fun getDownloadedMessage(mailboxUuid: String, folderId: String, shortUid: Int): ApiResponse { + return ApiController.callApi(url = ApiRoutes.downloadMessage(mailboxUuid, folderId, shortUid), method = GET) } fun getMyKSuiteData(okHttpClient: OkHttpClient): ApiResponse { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt index 95df36ab14..39a82deece 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt @@ -48,7 +48,6 @@ class DownloadMessagesViewModel @Inject constructor( application: Application, private val savedStateHandle: SavedStateHandle, private val messageController: MessageController, - private val threadController: ThreadController, @IoDispatcher private val ioDispatcher: CoroutineDispatcher, ) : AndroidViewModel(application) { @@ -57,18 +56,11 @@ class DownloadMessagesViewModel @Inject constructor( private val messageLocalUids: Array? inline get() = savedStateHandle[DownloadMessagesProgressDialogArgs::messageUids.name] - private val threadLocalUids: Array? - inline get() = savedStateHandle[DownloadMessagesProgressDialogArgs::threadUids.name] - val downloadMessagesLiveData = MutableLiveData?>() private fun getAllMessages(): Set { val messages = mutableSetOf() messageLocalUids?.mapNotNull(messageController::getMessage)?.let(messages::addAll) - - threadLocalUids?.forEach { threadUid -> - threadController.getThread(threadUid)?.let { thread -> messages.addAll(thread.messages) } - } return messages } @@ -96,7 +88,7 @@ class DownloadMessagesViewModel @Inject constructor( private fun getFirstMessageSubject(): String? = getAllMessages().firstOrNull()?.subject - private fun numberOfMessagesToDownloads(): Int = (messageLocalUids?.size ?: 0) + (threadLocalUids?.size ?: 0) + private fun numberOfMessagesToDownloads(): Int = messageLocalUids?.size ?: 0 fun downloadMessages(currentMailbox: Mailbox?) { viewModelScope.launch(ioCoroutineContext) { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt index c0714331ba..ada6d7b386 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt @@ -124,7 +124,7 @@ class MultiSelectBottomSheetDialog : ActionsBottomSheetDialog() { binding.saveKDrive.setClosingOnClickListener(shouldCloseMultiSelection = true) { trackMultiSelectActionEvent(ACTION_SAVE_TO_KDRIVE_NAME, threadsCount, isFromBottomSheet = true) navigateToDownloadMessagesProgressDialog( - threadUids = threadsUids, + messageUids = threads.flatMap { it.messages }.map { it.uid }, currentClassName = MultiSelectBottomSheetDialog::class.java.name, ) isMultiSelectOn = false diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt index cf7c8981ad..508f083b74 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/NavigationExtensions.kt @@ -91,17 +91,10 @@ fun Fragment.navigateToDownloadProgressDialog( ) } -fun Fragment.navigateToDownloadMessagesProgressDialog( - messageUids: List? = null, - threadUids: List? = null, - currentClassName: String, -) { +fun Fragment.navigateToDownloadMessagesProgressDialog(messageUids: List? = null, currentClassName: String) { safeNavigate( resId = R.id.downloadMessagesProgressDialog, - args = DownloadMessagesProgressDialogArgs( - messageUids = messageUids?.toTypedArray(), - threadUids = threadUids?.toTypedArray(), - ).toBundle(), + args = DownloadMessagesProgressDialogArgs(messageUids = messageUids?.toTypedArray()).toBundle(), currentClassName = currentClassName, ) } diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml index d4570ce63e..9cde3c4da4 100644 --- a/app/src/main/res/navigation/main_navigation.xml +++ b/app/src/main/res/navigation/main_navigation.xml @@ -595,10 +595,6 @@ android:name="com.infomaniak.mail.ui.main.thread.actions.DownloadMessagesProgressDialog" android:label="DownloadMessagesProgressDialog" tools:layout="@layout/dialog_download_progress"> - Date: Wed, 26 Feb 2025 11:00:09 +0100 Subject: [PATCH 38/41] feat: Allow ByteArray response --- .../main/java/com/infomaniak/mail/data/api/ApiRepository.kt | 4 +--- app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt | 4 ++-- .../mail/ui/main/thread/actions/DownloadMessagesViewModel.kt | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt b/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt index 18d8cf2721..66fcaef0f8 100644 --- a/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt +++ b/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt @@ -58,7 +58,6 @@ import com.infomaniak.mail.data.models.signature.SignaturesResult import com.infomaniak.mail.data.models.thread.ThreadResult import com.infomaniak.mail.ui.newMessage.AiViewModel.Shortcut import com.infomaniak.mail.utils.Utils -import com.infomaniak.mail.utils.Utils.EML_CONTENT_TYPE import io.realm.kotlin.ext.copyFromRealm import kotlinx.serialization.json.Json import okhttp3.MultipartBody @@ -457,11 +456,10 @@ object ApiRepository : ApiRepositoryCore() { } fun getMyKSuiteData(okHttpClient: OkHttpClient): ApiResponse { - return ApiController.callApi( + return callApi( url = MyKSuiteApiRoutes.myKSuiteData(), method = GET, okHttpClient = okHttpClient, - useKotlinxSerialization = true, ) } diff --git a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt index 230d15df8f..d653146a9b 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt @@ -1021,7 +1021,7 @@ class MainViewModel @Inject constructor( val apiResponse = ApiRepository.getDownloadedMessage(mailbox.uuid, message.folderId, message.shortUid) - if (!apiResponse.isSuccessful || apiResponse.body == null) { + if (apiResponse.data == null || !apiResponse.isSuccess()) { reportDisplayProblemTrigger.postValue(Unit) snackbarManager.postValue(appContext.getString(RCore.string.anErrorHasOccurred)) @@ -1029,7 +1029,7 @@ class MainViewModel @Inject constructor( } val filename = UUID.randomUUID().toString() - val emlAttachment = Attachment(apiResponse.body?.bytes(), filename, EML_CONTENT_TYPE) + val emlAttachment = Attachment(apiResponse.data, filename, EML_CONTENT_TYPE) Sentry.captureMessage("Message display problem reported", SentryLevel.ERROR) { scope -> scope.addAttachment(emlAttachment) } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt index 39a82deece..b6c49d99ab 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt @@ -105,13 +105,13 @@ class DownloadMessagesViewModel @Inject constructor( shortUid = message.shortUid, ) - if (!apiResponse.isSuccessful || apiResponse.body == null) throw NetworkErrorException() + if (apiResponse.data == null || !apiResponse.isSuccess()) throw NetworkErrorException() val messageSubject = message.subject ?: NO_SUBJECT_FILE val truncatedSubject = messageSubject.take(MAX_FILE_NAME_LENGTH) val fileName = createUniqueFileName(listFileName, truncatedSubject) - saveEmlToFile(appContext, apiResponse.body!!.bytes(), fileName) + saveEmlToFile(appContext, apiResponse.data!!, fileName) } } From b5e09f44e676690f68298519c299cf0338ce8c3f Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Wed, 26 Feb 2025 14:55:14 +0100 Subject: [PATCH 39/41] feat: Manage error --- .../java/com/infomaniak/mail/data/api/ApiRepository.kt | 6 +----- .../ui/main/thread/actions/DownloadMessagesViewModel.kt | 9 +++++++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt b/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt index 66fcaef0f8..1ae8542884 100644 --- a/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt +++ b/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt @@ -456,11 +456,7 @@ object ApiRepository : ApiRepositoryCore() { } fun getMyKSuiteData(okHttpClient: OkHttpClient): ApiResponse { - return callApi( - url = MyKSuiteApiRoutes.myKSuiteData(), - method = GET, - okHttpClient = okHttpClient, - ) + return callApi(url = MyKSuiteApiRoutes.myKSuiteData(), method = GET, okHttpClient = okHttpClient) } /** diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt index b6c49d99ab..b6bae83907 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt @@ -26,7 +26,9 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope +import com.infomaniak.lib.core.api.ApiController import com.infomaniak.lib.core.utils.DownloadManagerUtils +import com.infomaniak.lib.core.utils.SentryLog import com.infomaniak.mail.R import com.infomaniak.mail.data.api.ApiRepository import com.infomaniak.mail.data.cache.mailboxContent.MessageController @@ -105,7 +107,7 @@ class DownloadMessagesViewModel @Inject constructor( shortUid = message.shortUid, ) - if (apiResponse.data == null || !apiResponse.isSuccess()) throw NetworkErrorException() + if (apiResponse.data == null || !apiResponse.isSuccess()) throw apiResponse.error?.exception!! val messageSubject = message.subject ?: NO_SUBJECT_FILE val truncatedSubject = messageSubject.take(MAX_FILE_NAME_LENGTH) @@ -119,7 +121,9 @@ class DownloadMessagesViewModel @Inject constructor( }.onSuccess { downloadedThreadUris -> downloadMessagesLiveData.postValue(downloadedThreadUris) }.onFailure { - // Maybe log sentry + if (it is ApiController.ByteArrayException){ + SentryLog.e(TAG, "Error while sharing messages to kDrive:", it) + } clearEmlDir() downloadMessagesLiveData.postValue(null) } @@ -145,5 +149,6 @@ class DownloadMessagesViewModel @Inject constructor( companion object { private const val NO_SUBJECT_FILE = "message" private const val MAX_FILE_NAME_LENGTH = 256 + private val TAG = DownloadMessagesViewModel::class.simpleName.toString() } } From b3a260d4b0d276b8ff525299d732c57231e0adb6 Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Wed, 26 Feb 2025 15:38:05 +0100 Subject: [PATCH 40/41] refactor: Revert some useless changes --- .../com/infomaniak/mail/data/api/ApiRepository.kt | 10 ++++++++-- .../java/com/infomaniak/mail/ui/MainViewModel.kt | 4 ++-- .../thread/actions/DownloadMessagesViewModel.kt | 14 ++++++++------ app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 2 +- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt b/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt index 1ae8542884..eb16ba2045 100644 --- a/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt +++ b/app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt @@ -58,6 +58,7 @@ import com.infomaniak.mail.data.models.signature.SignaturesResult import com.infomaniak.mail.data.models.thread.ThreadResult import com.infomaniak.mail.ui.newMessage.AiViewModel.Shortcut import com.infomaniak.mail.utils.Utils +import com.infomaniak.mail.utils.Utils.EML_CONTENT_TYPE import io.realm.kotlin.ext.copyFromRealm import kotlinx.serialization.json.Json import okhttp3.MultipartBody @@ -451,8 +452,13 @@ object ApiRepository : ApiRepositoryCore() { return callApi(url = ApiRoutes.shareLink(mailboxUuid, folderId, mailId), method = POST) } - fun getDownloadedMessage(mailboxUuid: String, folderId: String, shortUid: Int): ApiResponse { - return ApiController.callApi(url = ApiRoutes.downloadMessage(mailboxUuid, folderId, shortUid), method = GET) + fun getDownloadedMessage(mailboxUuid: String, folderId: String, shortUid: Int): Response { + val request = Request.Builder().url(ApiRoutes.downloadMessage(mailboxUuid, folderId, shortUid)) + .headers(HttpUtils.getHeaders(EML_CONTENT_TYPE)) + .get() + .build() + + return HttpClient.okHttpClient.newCall(request).execute() } fun getMyKSuiteData(okHttpClient: OkHttpClient): ApiResponse { diff --git a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt index d653146a9b..eac639afde 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt @@ -1021,7 +1021,7 @@ class MainViewModel @Inject constructor( val apiResponse = ApiRepository.getDownloadedMessage(mailbox.uuid, message.folderId, message.shortUid) - if (apiResponse.data == null || !apiResponse.isSuccess()) { + if (apiResponse.body == null || !apiResponse.isSuccessful) { reportDisplayProblemTrigger.postValue(Unit) snackbarManager.postValue(appContext.getString(RCore.string.anErrorHasOccurred)) @@ -1029,7 +1029,7 @@ class MainViewModel @Inject constructor( } val filename = UUID.randomUUID().toString() - val emlAttachment = Attachment(apiResponse.data, filename, EML_CONTENT_TYPE) + val emlAttachment = Attachment(apiResponse.body?.bytes(), filename, EML_CONTENT_TYPE) Sentry.captureMessage("Message display problem reported", SentryLevel.ERROR) { scope -> scope.addAttachment(emlAttachment) } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt index b6bae83907..3a5bf7b005 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt @@ -17,7 +17,6 @@ */ package com.infomaniak.mail.ui.main.thread.actions -import android.accounts.NetworkErrorException import android.app.Application import android.content.Context import android.net.Uri @@ -26,13 +25,11 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope -import com.infomaniak.lib.core.api.ApiController import com.infomaniak.lib.core.utils.DownloadManagerUtils import com.infomaniak.lib.core.utils.SentryLog import com.infomaniak.mail.R import com.infomaniak.mail.data.api.ApiRepository import com.infomaniak.mail.data.cache.mailboxContent.MessageController -import com.infomaniak.mail.data.cache.mailboxContent.ThreadController import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.di.IoDispatcher @@ -107,13 +104,15 @@ class DownloadMessagesViewModel @Inject constructor( shortUid = message.shortUid, ) - if (apiResponse.data == null || !apiResponse.isSuccess()) throw apiResponse.error?.exception!! + if (apiResponse.body == null || !apiResponse.isSuccessful) { + throw ByteArrayNetworkException(apiResponse.body.toString(), apiResponse.code) + } val messageSubject = message.subject ?: NO_SUBJECT_FILE val truncatedSubject = messageSubject.take(MAX_FILE_NAME_LENGTH) val fileName = createUniqueFileName(listFileName, truncatedSubject) - saveEmlToFile(appContext, apiResponse.data!!, fileName) + saveEmlToFile(appContext, apiResponse.body?.bytes()!!, fileName) } } @@ -121,7 +120,7 @@ class DownloadMessagesViewModel @Inject constructor( }.onSuccess { downloadedThreadUris -> downloadMessagesLiveData.postValue(downloadedThreadUris) }.onFailure { - if (it is ApiController.ByteArrayException){ + if (it is ByteArrayNetworkException) { SentryLog.e(TAG, "Error while sharing messages to kDrive:", it) } clearEmlDir() @@ -150,5 +149,8 @@ class DownloadMessagesViewModel @Inject constructor( private const val NO_SUBJECT_FILE = "message" private const val MAX_FILE_NAME_LENGTH = 256 private val TAG = DownloadMessagesViewModel::class.simpleName.toString() + + private data class ByteArrayNetworkException(val responseBody: String, val responseCode: Int) : + Exception("Failed to get EML $responseCode: $responseBody") } } diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 37fb640d74..1f037b6f52 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -15,7 +15,7 @@ ~ You should have received a copy of the GNU General Public License ~ along with this program. If not, see . --> - + Blau Rosa Farbe des Systems diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index e978f6e294..c2d179b8d1 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -15,7 +15,7 @@ ~ You should have received a copy of the GNU General Public License ~ along with this program. If not, see . --> - + Azul Rosa Color del sistema diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 967664a53f..4d8f6f3ac7 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -15,7 +15,7 @@ ~ You should have received a copy of the GNU General Public License ~ along with this program. If not, see . --> - + Blu Rosa Colore del sistema From e1c66c893adbee7c722193a27b73648ab8ea66d9 Mon Sep 17 00:00:00 2001 From: Nicolas Bourdin Date: Thu, 27 Feb 2025 12:44:24 +0100 Subject: [PATCH 41/41] refactor: Apply suggestion from code review --- .../mail/ui/main/folder/TwoPaneFragment.kt | 4 +- .../actions/DownloadMessagesProgressDialog.kt | 19 ++---- .../actions/DownloadMessagesViewModel.kt | 64 +++++++++---------- .../actions/MultiSelectBottomSheetDialog.kt | 4 -- .../mail/utils/LocalStorageUtils.kt | 4 ++ 5 files changed, 42 insertions(+), 53 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/folder/TwoPaneFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/main/folder/TwoPaneFragment.kt index 22b4e39cc2..5c8ce397c5 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/folder/TwoPaneFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/folder/TwoPaneFragment.kt @@ -43,7 +43,7 @@ import com.infomaniak.mail.ui.MainViewModel import com.infomaniak.mail.ui.main.search.SearchFragment import com.infomaniak.mail.ui.main.thread.ThreadFragment import com.infomaniak.mail.ui.main.thread.actions.DownloadMessagesProgressDialog -import com.infomaniak.mail.utils.LocalStorageUtils.getEmlCacheDir +import com.infomaniak.mail.utils.LocalStorageUtils.clearEmlCacheDir import com.infomaniak.mail.utils.extensions.* import javax.inject.Inject @@ -122,7 +122,7 @@ abstract class TwoPaneFragment : Fragment() { } private val resultActivityResultLauncher = registerForActivityResult(StartActivityForResult()) { _ -> - getEmlCacheDir(requireContext()).deleteRecursively() + clearEmlCacheDir(requireContext()) } private fun observeThreadNavigation() = with(twoPaneViewModel) { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesProgressDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesProgressDialog.kt index 47da384635..b2bc98850e 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesProgressDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesProgressDialog.kt @@ -26,6 +26,7 @@ import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import com.infomaniak.lib.core.utils.goToPlayStore import com.infomaniak.lib.core.utils.setBackNavigationResult +import com.infomaniak.mail.utils.LocalStorageUtils.clearEmlCacheDir import com.infomaniak.mail.utils.SaveOnKDriveUtils.DRIVE_PACKAGE import com.infomaniak.mail.utils.SaveOnKDriveUtils.SAVE_EXTERNAL_ACTIVITY_CLASS import com.infomaniak.mail.utils.SaveOnKDriveUtils.canSaveOnKDrive @@ -33,7 +34,7 @@ import com.infomaniak.mail.utils.SaveOnKDriveUtils.canSaveOnKDrive class DownloadMessagesProgressDialog : DownloadProgressDialog() { private val downloadThreadsViewModel: DownloadMessagesViewModel by viewModels() - override val dialogTitle: String? by lazy { getDialogName() } + override val dialogTitle: String? by lazy { downloadThreadsViewModel.getDialogName() } override fun onCreate(savedInstanceState: Bundle?) { observeDownload() @@ -46,21 +47,15 @@ class DownloadMessagesProgressDialog : DownloadProgressDialog() { private fun observeDownload() { downloadThreadsViewModel.downloadMessagesLiveData.observe(this) { messageUris -> - if (messageUris == null) { - popBackStackWithError() - } else { - messageUris.openKDriveOrPlayStore(requireContext())?.let { openKDriveIntent -> - setBackNavigationResult(DOWNLOAD_MESSAGES_RESULT, openKDriveIntent) - } ?: run { - downloadThreadsViewModel.clearEmlDir() - findNavController().popBackStack() - } + messageUris?.openKDriveOrPlayStore(requireContext())?.let { openKDriveIntent -> + setBackNavigationResult(DOWNLOAD_MESSAGES_RESULT, openKDriveIntent) + } ?: run { + clearEmlCacheDir(requireContext()) + if (messageUris == null) popBackStackWithError() else findNavController().popBackStack() } } } - private fun getDialogName(): String = downloadThreadsViewModel.getDialogName() - private fun List.openKDriveOrPlayStore(context: Context): Intent? { return if (canSaveOnKDrive(context)) { saveToDriveIntent() diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt index 3a5bf7b005..0afeb0dcb2 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadMessagesViewModel.kt @@ -37,7 +37,10 @@ import com.infomaniak.mail.utils.LocalStorageUtils.getEmlCacheDir import com.infomaniak.mail.utils.coroutineContext import com.infomaniak.mail.utils.extensions.appContext import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.launch import java.io.File import javax.inject.Inject import kotlin.collections.set @@ -89,44 +92,39 @@ class DownloadMessagesViewModel @Inject constructor( private fun numberOfMessagesToDownloads(): Int = messageLocalUids?.size ?: 0 - fun downloadMessages(currentMailbox: Mailbox?) { - viewModelScope.launch(ioCoroutineContext) { - val mailbox = currentMailbox ?: return@launch + fun downloadMessages(currentMailbox: Mailbox?) = viewModelScope.launch(ioCoroutineContext) { + val mailbox = currentMailbox ?: return@launch - runCatching { - val listFileName = HashMap() + val downloadedThreadUris = runCatching { + val listFileName = HashMap() - val deferredResponses = getAllMessages().map { message -> - async { - val apiResponse = ApiRepository.getDownloadedMessage( - mailboxUuid = mailbox.uuid, - folderId = message.folderId, - shortUid = message.shortUid, - ) + val deferredResponses = getAllMessages().map { message -> + async { + val apiResponse = ApiRepository.getDownloadedMessage( + mailboxUuid = mailbox.uuid, + folderId = message.folderId, + shortUid = message.shortUid, + ) - if (apiResponse.body == null || !apiResponse.isSuccessful) { - throw ByteArrayNetworkException(apiResponse.body.toString(), apiResponse.code) - } - - val messageSubject = message.subject ?: NO_SUBJECT_FILE - val truncatedSubject = messageSubject.take(MAX_FILE_NAME_LENGTH) - val fileName = createUniqueFileName(listFileName, truncatedSubject) - - saveEmlToFile(appContext, apiResponse.body?.bytes()!!, fileName) + if (apiResponse.body == null || !apiResponse.isSuccessful) { + throw ByteArrayNetworkException(apiResponse.body.toString(), apiResponse.code) } - } - deferredResponses.awaitAll() - }.onSuccess { downloadedThreadUris -> - downloadMessagesLiveData.postValue(downloadedThreadUris) - }.onFailure { - if (it is ByteArrayNetworkException) { - SentryLog.e(TAG, "Error while sharing messages to kDrive:", it) + val messageSubject = message.subject ?: NO_SUBJECT_FILE + val truncatedSubject = messageSubject.take(MAX_FILE_NAME_LENGTH) + val fileName = createUniqueFileName(listFileName, truncatedSubject) + + saveEmlToFile(appContext, apiResponse.body?.bytes()!!, fileName) } - clearEmlDir() - downloadMessagesLiveData.postValue(null) } + + deferredResponses.awaitAll() + }.getOrElse { + if (it is ByteArrayNetworkException) SentryLog.e(TAG, "Error while sharing messages to kDrive:", it) + null } + + downloadMessagesLiveData.postValue(downloadedThreadUris) } fun getDialogName(): String { @@ -139,10 +137,6 @@ class DownloadMessagesViewModel @Inject constructor( } } - fun clearEmlDir() { - getEmlCacheDir(appContext).deleteRecursively() - } - private fun String.removeIllegalFileNameCharacter(): String = replace(DownloadManagerUtils.regexInvalidSystemChar, "") companion object { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt index ada6d7b386..dc2fb41a6d 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MultiSelectBottomSheetDialog.kt @@ -156,8 +156,4 @@ class MultiSelectBottomSheetDialog : ActionsBottomSheetDialog() { private fun getSpamIconAndText(isFromSpam: Boolean): Pair { return if (isFromSpam) R.drawable.ic_non_spam to R.string.actionNonSpam else R.drawable.ic_spam to R.string.actionSpam } - - companion object { - private val TAG = this::class.java.simpleName - } } diff --git a/app/src/main/java/com/infomaniak/mail/utils/LocalStorageUtils.kt b/app/src/main/java/com/infomaniak/mail/utils/LocalStorageUtils.kt index f856d0aa81..74f1ce7648 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/LocalStorageUtils.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/LocalStorageUtils.kt @@ -44,6 +44,10 @@ object LocalStorageUtils { //region Cache fun getEmlCacheDir(context: Context): File = File(context.cacheDir, EML_CACHE_DIR) + fun clearEmlCacheDir(context: Context) { + getEmlCacheDir(context).deleteRecursively() + } + fun getAttachmentsCacheDir( context: Context, attachmentPath: String,