Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Save thread on kDrive #2088

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
49baa79
refactor: Share code of 'reportDisplayProblem'
NicolasBourdin88 Oct 29, 2024
167624b
feat: Add save to kDrive option
NicolasBourdin88 Oct 29, 2024
7056b5d
feat: Save eml to kDrive
NicolasBourdin88 Oct 29, 2024
9c86d70
refactor: Rename & Clean code
NicolasBourdin88 Oct 30, 2024
d0ef8a7
feat: Replace illegal file characters with a blank space
NicolasBourdin88 Oct 31, 2024
be3079d
feat: Allow to download a thread instead of only one message
NicolasBourdin88 Oct 31, 2024
deb20d0
feat: Add option in 'MultiSelectBottomSheet'
NicolasBourdin88 Oct 31, 2024
a50bc80
feat: Allow to add different threads or messages with same name
NicolasBourdin88 Nov 1, 2024
1a6a4b9
feat: Save in cache instead of files
NicolasBourdin88 Nov 1, 2024
9abe5f9
refactor: Put all 'clickListener' from message bottom sheet together
NicolasBourdin88 Nov 18, 2024
830ff4b
refactor: Use a list instead of a set
NicolasBourdin88 Nov 18, 2024
30cdf26
feat: Display progression while loading messages
NicolasBourdin88 Nov 21, 2024
9503ec4
refactor: Send messages uids instead of thread uuids
NicolasBourdin88 Nov 21, 2024
41ec818
refactor: Rename 'messageUuids' to 'messageUids'
NicolasBourdin88 Nov 21, 2024
22f5c47
feat: Import downloading String
NicolasBourdin88 Nov 21, 2024
1eaaeff
feat: Add dialog name
NicolasBourdin88 Nov 22, 2024
83a0cdb
feat: Centralize shared code in abstract class
NicolasBourdin88 Nov 22, 2024
1d999f2
refactor: Rename for better clarity
NicolasBourdin88 Nov 22, 2024
8c0a17f
refactor: Apply suggestion from code review
NicolasBourdin88 Nov 22, 2024
0f3cb8b
refactor: Put EML_CONTENT_TYPE for all
NicolasBourdin88 Dec 16, 2024
23bd66f
refactor: Apply suggestion from code review
NicolasBourdin88 Dec 16, 2024
fdefc17
refactor: Remove 'EXPOSED_EML_PATH' from 'build.gradle'
NicolasBourdin88 Dec 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions .idea/navEditor.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,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', 'FILES_AUTHORITY', 'com.infomaniak.mail.attachments;com.infomaniak.mail.eml'

resourceConfigurations += ["en", "de", "es", "fr", "it"]
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="@string/ATTACHMENTS_AUTHORITY"
android:authorities="@string/FILES_AUTHORITY"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/com/infomaniak/mail/MatomoMail.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,17 @@ object ApiRepository : ApiRepositoryCore() {
return callApi(url = ApiRoutes.shareLink(mailboxUuid, folderId, mailId), method = POST)
}

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(emlContentType))
.get()
.build()

return HttpClient.okHttpClient.newCall(request).execute()
NicolasBourdin88 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Create batches of the given values to perform the given request
* @param values Data to batch
Expand Down
31 changes: 16 additions & 15 deletions app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ package com.infomaniak.mail.ui
import android.app.Application
import androidx.lifecycle.*
import com.infomaniak.lib.core.models.ApiResponse
import com.infomaniak.lib.core.networking.HttpUtils
import com.infomaniak.lib.core.networking.NetworkAvailability
import com.infomaniak.lib.core.utils.ApiErrorCode.Companion.translateError
import com.infomaniak.lib.core.utils.DownloadManagerUtils
Expand All @@ -29,7 +28,6 @@ import com.infomaniak.lib.core.utils.SingleLiveEvent
import com.infomaniak.mail.MatomoMail.trackMultiSelectionEvent
import com.infomaniak.mail.R
import com.infomaniak.mail.data.api.ApiRepository
import com.infomaniak.mail.data.api.ApiRoutes
import com.infomaniak.mail.data.cache.RealmDatabase
import com.infomaniak.mail.data.cache.mailboxContent.FolderController
import com.infomaniak.mail.data.cache.mailboxContent.MessageController
Expand Down Expand Up @@ -75,7 +73,6 @@ import io.sentry.Sentry
import io.sentry.SentryLevel
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import okhttp3.Request
import java.util.Date
import java.util.UUID
import javax.inject.Inject
Expand Down Expand Up @@ -924,19 +921,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.getDownloadedMessage(mailbox.uuid, message.folderId, message.shortUid)

if (!response.isSuccessful || response.body == null) {
reportDisplayProblemTrigger.postValue(Unit)
Expand Down Expand Up @@ -1096,7 +1083,7 @@ class MainViewModel @Inject constructor(
}

fun hasOtherExpeditors(threadUid: String) = liveData(ioCoroutineContext) {
val hasOtherExpeditors = threadController.getThread(threadUid)?.messages?.flatMap { it.from }?.any { !it.isMe() } ?: false
val hasOtherExpeditors = threadController.getThread(threadUid)?.messages?.flatMap { it.from }?.any { !it.isMe() } == true
emit(hasOtherExpeditors)
}

Expand Down Expand Up @@ -1208,6 +1195,20 @@ class MainViewModel @Inject constructor(
}
}

fun getMessagesUidsFromThreadUids(selectedThreadsUids: List<String>): List<String> {
val messageUids = mutableListOf<String>()
selectedThreadsUids.forEach { threadUuid ->
val thread = threadController.getThread(threadUuid) ?: return@forEach
messageUids.addAll(thread.messages.map { it.uid })
}

return messageUids
}

fun getSubject(threadUid: String): String {
return threadController.getThread(threadUid)?.subject ?: ""
}

companion object {
private val TAG: String = MainViewModel::class.java.simpleName
private val DEFAULT_SELECTED_FOLDER = FolderRole.INBOX
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.DownloadMessagesProgressDialog
import com.infomaniak.mail.utils.extensions.*
import javax.inject.Inject

Expand Down Expand Up @@ -120,6 +121,7 @@ abstract class TwoPaneFragment : Fragment() {

private fun observeThreadNavigation() = with(twoPaneViewModel) {
getBackNavigationResult(AttachmentExtensions.DOWNLOAD_ATTACHMENT_RESULT, ::startActivity)
getBackNavigationResult(DownloadMessagesProgressDialog.DOWNLOAD_MESSAGES_RESULT, ::startActivity)

newMessageArgs.observe(viewLifecycleOwner) {
safeNavigateToNewMessageActivity(args = it.toBundle())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,59 +17,33 @@
*/
package com.infomaniak.mail.ui.main.thread.actions

import android.app.Dialog
import android.os.Bundle
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.content.res.AppCompatResources
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import androidx.core.view.isVisible
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 val dialogTitle: String? by lazy { navigationArgs.attachmentName }

override fun onStart() {
super.onStart()
downloadAttachment()
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)
}

private fun downloadAttachment() {
override fun download() {
downloadAttachmentViewModel.downloadAttachment().observe(this) { cachedAttachment ->
if (cachedAttachment == null) {
popBackStackWithError()
Expand All @@ -80,13 +54,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()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
package com.infomaniak.mail.ui.main.thread.actions

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
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.SaveOnKDriveUtils.DRIVE_PACKAGE
import com.infomaniak.mail.utils.SaveOnKDriveUtils.SAVE_EXTERNAL_ACTIVITY_CLASS
import com.infomaniak.mail.utils.SaveOnKDriveUtils.canSaveOnKDrive

class DownloadMessagesProgressDialog : DownloadProgressDialog() {
private val downloadThreadsViewModel: DownloadMessagesViewModel by viewModels()
private val navigationArgs: DownloadMessagesProgressDialogArgs by navArgs()
override val dialogTitle: String? by lazy { getDialogTitleFromArgs() }

override fun download() {
downloadThreadsViewModel.downloadThreads(mainViewModel.currentMailbox.value).observe(this) { threadUris ->
if (threadUris == null) {
popBackStackWithError()
} else {
ArrayList(threadUris).openKDriveOrPlayStore(requireContext())?.let { openKDriveIntent ->
setBackNavigationResult(DOWNLOAD_MESSAGES_RESULT, openKDriveIntent)
} ?: run { findNavController().popBackStack() }
}
}
}

private fun getDialogTitleFromArgs() = if (navigationArgs.messageUids.size == 1) {
navigationArgs.nameFirstMessage
} else {
requireContext().resources.getQuantityString(
R.plurals.downloadingEmailsTitle,
navigationArgs.messageUids.size,
navigationArgs.messageUids.size
)
}

private fun ArrayList<Uri>.openKDriveOrPlayStore(context: Context): Intent? {
return if (canSaveOnKDrive(context)) {
saveToDriveIntent()
} else {
context.goToPlayStore(DRIVE_PACKAGE)
null
}
}

private fun ArrayList<Uri>.saveToDriveIntent(): Intent {
return Intent().apply {
component = ComponentName(DRIVE_PACKAGE, SAVE_EXTERNAL_ACTIVITY_CLASS)
action = Intent.ACTION_SEND_MULTIPLE
putParcelableArrayListExtra(Intent.EXTRA_STREAM, this@saveToDriveIntent)
}
}

companion object {
const val DOWNLOAD_MESSAGES_RESULT = "download_messages_result"
}
}
Loading
Loading