Skip to content

Commit

Permalink
Merge pull request #1989 from Infomaniak/nested-scroll-views
Browse files Browse the repository at this point in the history
Remove NestedScrollViews in Move & MenuDrawer fragments
  • Loading branch information
KevinBoulongne committed Aug 7, 2024
2 parents d9c1761 + acd1034 commit e4c390a
Show file tree
Hide file tree
Showing 104 changed files with 2,299 additions and 1,433 deletions.
1 change: 0 additions & 1 deletion .idea/codeStyles/Project.xml

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

8 changes: 4 additions & 4 deletions app/src/main/java/com/infomaniak/mail/MatomoMail.kt
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ object MatomoMail : MatomoCore {
}

fun Fragment.trackCreateFolderEvent(name: String) {
context?.trackCreateFolderEvent(name)
}

fun Context.trackCreateFolderEvent(name: String) {
trackEvent("createFolder", name)
}

Expand Down Expand Up @@ -209,10 +213,6 @@ object MatomoMail : MatomoCore {
trackEvent("invalidPasswordMailbox", name)
}

fun Fragment.trackExternalEvent(name: String, action: TrackerAction = TrackerAction.CLICK, value: Float? = null) {
context?.trackExternalEvent(name, action, value)
}

fun Context.trackExternalEvent(name: String, action: TrackerAction = TrackerAction.CLICK, value: Float? = null) {
trackEvent("externals", name, action, value)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ package com.infomaniak.mail.data.api
import com.infomaniak.lib.core.InfomaniakCore
import com.infomaniak.lib.core.R
import com.infomaniak.lib.core.api.ApiController
import com.infomaniak.lib.core.api.ApiController.ApiMethod.*
import com.infomaniak.lib.core.api.ApiController.ApiMethod.DELETE
import com.infomaniak.lib.core.api.ApiController.ApiMethod.GET
import com.infomaniak.lib.core.api.ApiController.ApiMethod.PATCH
import com.infomaniak.lib.core.api.ApiController.ApiMethod.POST
import com.infomaniak.lib.core.api.ApiController.ApiMethod.PUT
import com.infomaniak.lib.core.api.ApiRepositoryCore
import com.infomaniak.lib.core.models.ApiResponse
import com.infomaniak.lib.core.models.ApiResponseStatus
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.infomaniak.mail.data.models.Folder.FolderRole
import com.infomaniak.mail.data.models.mailbox.Mailbox
import com.infomaniak.mail.utils.extensions.copyListToRealm
import com.infomaniak.mail.utils.extensions.flattenFolderChildren
import com.infomaniak.mail.utils.extensions.sortFolders
import io.realm.kotlin.MutableRealm
import io.realm.kotlin.Realm
import io.realm.kotlin.TypedRealm
Expand All @@ -34,7 +35,6 @@ import io.realm.kotlin.notifications.ResultsChange
import io.realm.kotlin.query.RealmQuery
import io.realm.kotlin.query.RealmResults
import io.realm.kotlin.query.RealmSingleQuery
import io.realm.kotlin.query.Sort
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.mapNotNull
import javax.inject.Inject
Expand All @@ -45,20 +45,20 @@ class FolderController @Inject constructor(
) {

//region Get data
fun getCustomFolders(): RealmResults<Folder> {
return getCustomFoldersQuery(mailboxContentRealm()).find()
fun getMenuDrawerDefaultFoldersAsync(): Flow<ResultsChange<Folder>> {
return getFoldersQuery(mailboxContentRealm(), withoutType = FoldersType.CUSTOM, withoutChildren = true).asFlow()
}

fun getRootsFoldersAsync(): Flow<ResultsChange<Folder>> {
return getFoldersQuery(mailboxContentRealm(), onlyRoots = true).asFlow()
fun getMenuDrawerCustomFoldersAsync(): Flow<ResultsChange<Folder>> {
return getFoldersQuery(mailboxContentRealm(), withoutType = FoldersType.DEFAULT, withoutChildren = true).asFlow()
}

fun getDefaultFoldersAsync(): Flow<ResultsChange<Folder>> {
return getDefaultFoldersQuery(mailboxContentRealm()).asFlow()
fun getSearchFoldersAsync(): Flow<ResultsChange<Folder>> {
return getFoldersQuery(mailboxContentRealm(), withoutChildren = true).asFlow()
}

fun getCustomFoldersAsync(): Flow<ResultsChange<Folder>> {
return getCustomFoldersQuery(mailboxContentRealm()).asFlow()
fun getMoveFolders(): RealmResults<Folder> {
return getFoldersQuery(mailboxContentRealm(), withoutType = FoldersType.DRAFT, withoutChildren = true).find()
}

fun getFolder(id: String): Folder? {
Expand Down Expand Up @@ -131,31 +131,31 @@ class FolderController @Inject constructor(
}
//endregion

enum class FoldersType {
DEFAULT,
CUSTOM,
DRAFT,
}

companion object {
const val SEARCH_FOLDER_ID = "search_folder_id"
private val isNotSearch = "${Folder::id.name} != '$SEARCH_FOLDER_ID'"
private val isRootFolder = "${Folder.parentsPropertyName}.@count == 0"

//region Queries
private fun getFoldersQuery(realm: TypedRealm, onlyRoots: Boolean = false): RealmQuery<Folder> {
val rootsQuery = if (onlyRoots) " AND $isRootFolder" else ""
return realm
.query<Folder>(isNotSearch + rootsQuery)
.sort(Folder::name.name, Sort.ASCENDING)
.sort(Folder::isFavorite.name, Sort.DESCENDING)
}

private fun getDefaultFoldersQuery(realm: TypedRealm): RealmQuery<Folder> {
val hasRole = "${Folder.rolePropertyName} != nil"
return realm.query("$isNotSearch AND $hasRole")
}

private fun getCustomFoldersQuery(realm: TypedRealm): RealmQuery<Folder> {
val hasNoRole = "${Folder.rolePropertyName} == nil"
return realm
.query<Folder>("$isNotSearch AND $isRootFolder AND $hasNoRole")
.sort(Folder::name.name, Sort.ASCENDING)
.sort(Folder::isFavorite.name, Sort.DESCENDING)
private fun getFoldersQuery(
realm: TypedRealm,
withoutType: FoldersType? = null,
withoutChildren: Boolean = false,
): RealmQuery<Folder> {
val rootsQuery = if (withoutChildren) " AND $isRootFolder" else ""
val typeQuery = when (withoutType) {
FoldersType.DEFAULT -> " AND ${Folder.rolePropertyName} == nil"
FoldersType.CUSTOM -> " AND ${Folder.rolePropertyName} != nil"
FoldersType.DRAFT -> " AND ${Folder.rolePropertyName} != '${FolderRole.DRAFT.name}'"
null -> ""
}
return realm.query<Folder>("$isNotSearch${rootsQuery}${typeQuery}").sortFolders()
}

private fun getFoldersQuery(exceptionsFoldersIds: List<String>, realm: TypedRealm): RealmQuery<Folder> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import com.infomaniak.lib.core.utils.SentryLog
import com.infomaniak.mail.data.LocalSettings
import com.infomaniak.mail.data.LocalSettings.ThreadMode
import com.infomaniak.mail.data.api.ApiRepository
import com.infomaniak.mail.data.cache.mailboxContent.RefreshController.RefreshMode.*
import com.infomaniak.mail.data.cache.mailboxContent.RefreshController.RefreshMode.ONE_PAGE_OF_OLD_MESSAGES
import com.infomaniak.mail.data.cache.mailboxContent.RefreshController.RefreshMode.REFRESH_FOLDER
import com.infomaniak.mail.data.cache.mailboxContent.RefreshController.RefreshMode.REFRESH_FOLDER_WITH_ROLE
import com.infomaniak.mail.data.cache.mailboxContent.RefreshController.RetryStrategy.Iteration
import com.infomaniak.mail.data.cache.mailboxInfo.MailboxController
import com.infomaniak.mail.data.models.Folder
Expand Down
27 changes: 19 additions & 8 deletions app/src/main/java/com/infomaniak/mail/data/models/Folder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import android.content.Context
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.infomaniak.lib.core.utils.Utils.enumValueOfOrNull
import com.infomaniak.lib.core.utils.removeAccents
import com.infomaniak.mail.R
import com.infomaniak.mail.data.models.message.Message
import com.infomaniak.mail.data.models.thread.Thread
Expand All @@ -43,7 +44,7 @@ import kotlinx.serialization.UseSerializers
import kotlin.math.max

@Serializable
class Folder : RealmObject {
class Folder : RealmObject, Cloneable {

//region Remote data
@PrimaryKey
Expand Down Expand Up @@ -79,6 +80,10 @@ class Folder : RealmObject {
var isHidden: Boolean = false // For children only (a children Folder is hidden if its parent is collapsed)
@Transient
var isCollapsed: Boolean = false // For parents only (collapsing a parent Folder will hide its children)
@Transient
var roleOrder: Int = role?.order ?: CUSTOM_FOLDER_ROLE_ORDER
@Transient
var sortedName: String = name
//endregion

private val _parents by backlinks(Folder::children)
Expand All @@ -99,6 +104,9 @@ class Folder : RealmObject {
val isRoot: Boolean
inline get() = !path.contains(separator)

val isRootAndCustom: Boolean
inline get() = role == null && isRoot

fun initLocalValues(
lastUpdatedAt: RealmInstant?,
cursor: String?,
Expand All @@ -120,6 +128,8 @@ class Folder : RealmObject {
this.isHistoryComplete = isHistoryComplete
this.isHidden = isHidden
this.isCollapsed = isCollapsed

this.sortedName = this.name.lowercase().removeAccents()
}

fun resetLocalValues() {
Expand Down Expand Up @@ -153,14 +163,14 @@ class Folder : RealmObject {
val order: Int,
val matomoValue: String,
) {
INBOX(R.string.inboxFolder, R.drawable.ic_drawer_inbox, 0, "inboxFolder"),
INBOX(R.string.inboxFolder, R.drawable.ic_drawer_inbox, 8, "inboxFolder"),
COMMERCIAL(R.string.commercialFolder, R.drawable.ic_promotions, 7, "commercialFolder"),
SOCIALNETWORKS(R.string.socialNetworksFolder, R.drawable.ic_social_media, 6, "socialNetworksFolder"),
SENT(R.string.sentFolder, R.drawable.ic_sent_messages, 5, "sentFolder"),
DRAFT(R.string.draftFolder, R.drawable.ic_draft, 4, "draftFolder"),
SENT(R.string.sentFolder, R.drawable.ic_sent_messages, 3, "sentFolder"),
SPAM(R.string.spamFolder, R.drawable.ic_spam, 5, "spamFolder"),
TRASH(R.string.trashFolder, R.drawable.ic_bin, 6, "trashFolder"),
ARCHIVE(R.string.archiveFolder, R.drawable.ic_archive_folder, 7, "archiveFolder"),
COMMERCIAL(R.string.commercialFolder, R.drawable.ic_promotions, 1, "commercialFolder"),
SOCIALNETWORKS(R.string.socialNetworksFolder, R.drawable.ic_social_media, 2, "socialNetworksFolder"),
SPAM(R.string.spamFolder, R.drawable.ic_spam, 3, "spamFolder"),
TRASH(R.string.trashFolder, R.drawable.ic_bin, 2, "trashFolder"),
ARCHIVE(R.string.archiveFolder, R.drawable.ic_archive_folder, 1, "archiveFolder"),
}

companion object {
Expand All @@ -173,5 +183,6 @@ class Folder : RealmObject {
const val DEFAULT_IS_HISTORY_COMPLETE = false

const val INBOX_FOLDER_ID = "eJzz9HPyjwAABGYBgQ--"
private const val CUSTOM_FOLDER_ROLE_ORDER = 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Quotas : EmbeddedRealmObject {
@SerialName("size")
private var _size: Long = 0L

private val size: Long get() = _size * 1_000L // Convert from KiloOctets to Octets
val size: Long get() = _size * 1_000L // Convert from KiloOctets to Octets

fun getText(context: Context): String {

Expand Down
6 changes: 1 addition & 5 deletions app/src/main/java/com/infomaniak/mail/ui/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,9 @@ import com.infomaniak.mail.data.models.draft.Draft.DraftAction
import com.infomaniak.mail.databinding.ActivityMainBinding
import com.infomaniak.mail.firebase.RegisterFirebaseBroadcastReceiver
import com.infomaniak.mail.ui.alertDialogs.DescriptionAlertDialog
import com.infomaniak.mail.ui.alertDialogs.TitleAlertDialog
import com.infomaniak.mail.ui.main.SnackbarManager
import com.infomaniak.mail.ui.main.folder.TwoPaneFragment
import com.infomaniak.mail.ui.main.menu.MenuDrawerFragment
import com.infomaniak.mail.ui.main.menuDrawer.MenuDrawerFragment
import com.infomaniak.mail.ui.main.onboarding.PermissionsOnboardingPagerFragment
import com.infomaniak.mail.ui.main.search.SearchFragmentArgs
import com.infomaniak.mail.ui.newMessage.NewMessageActivity
Expand Down Expand Up @@ -144,9 +143,6 @@ class MainActivity : BaseActivity() {
@Inject
lateinit var descriptionDialog: DescriptionAlertDialog

@Inject
lateinit var titleDialog: TitleAlertDialog

@Inject
lateinit var permissionUtils: PermissionUtils

Expand Down
15 changes: 6 additions & 9 deletions app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,7 @@ import com.infomaniak.mail.utils.Utils.runCatchingRealm
import com.infomaniak.mail.utils.coroutineContext
import com.infomaniak.mail.utils.extensions.MergedContactDictionary
import com.infomaniak.mail.utils.extensions.appContext
import com.infomaniak.mail.utils.extensions.getCustomMenuFolders
import com.infomaniak.mail.utils.extensions.getDefaultMenuFolders
import com.infomaniak.mail.utils.extensions.flattenFolderChildren
import com.infomaniak.mail.utils.extensions.getFoldersIds
import com.infomaniak.mail.utils.extensions.getUids
import com.infomaniak.mail.utils.extensions.launchNoValidMailboxesActivity
Expand All @@ -85,6 +84,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
Expand Down Expand Up @@ -119,7 +119,6 @@ class MainViewModel @Inject constructor(
private val ioCoroutineContext = viewModelScope.coroutineContext(ioDispatcher)
private var refreshEverythingJob: Job? = null

// First boolean is the download status, second boolean is if the LoadMore button should be displayed
val isDownloadingChanges: MutableLiveData<Boolean> = MutableLiveData(false)
val isInternetAvailable = MutableLiveData<Boolean>()
val isMovedToNewFolder = SingleLiveEvent<Boolean>()
Expand Down Expand Up @@ -158,14 +157,12 @@ class MainViewModel @Inject constructor(
it?.let(mailboxController::getMailbox)
}.asLiveData(ioCoroutineContext)

val currentDefaultFoldersLive = _currentMailboxObjectId.flatMapLatest { objectId ->
objectId?.let { folderController.getDefaultFoldersAsync().map { it.list.getDefaultMenuFolders() } } ?: emptyFlow()
val defaultFoldersLive = _currentMailboxObjectId.filterNotNull().flatMapLatest {
folderController.getMenuDrawerDefaultFoldersAsync().map { it.list.flattenFolderChildren(dismissHiddenChildren = true) }
}.asLiveData(ioCoroutineContext)

val currentCustomFoldersLive = _currentMailboxObjectId.flatMapLatest { objectId ->
objectId
?.let { folderController.getCustomFoldersAsync().map { it.list.getCustomMenuFolders(dismissHiddenChildren = true) } }
?: emptyFlow()
val customFoldersLive = _currentMailboxObjectId.filterNotNull().flatMapLatest {
folderController.getMenuDrawerCustomFoldersAsync().map { it.list.flattenFolderChildren(dismissHiddenChildren = true) }
}.asLiveData(ioCoroutineContext)

val currentQuotasLive = _currentMailboxObjectId.flatMapLatest {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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.alertDialogs

import android.content.Context
import androidx.annotation.StringRes
import com.infomaniak.mail.MatomoMail.trackCreateFolderEvent
import com.infomaniak.mail.R
import com.infomaniak.mail.data.cache.mailboxContent.FolderController
import com.infomaniak.mail.di.IoDispatcher
import com.infomaniak.mail.utils.extensions.getFolderCreationError
import dagger.hilt.android.qualifiers.ActivityContext
import dagger.hilt.android.scopes.ActivityScoped
import kotlinx.coroutines.CoroutineDispatcher
import javax.inject.Inject

@ActivityScoped
class CreateFolderDialog @Inject constructor(
@ActivityContext private val activityContext: Context,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
private val folderController: FolderController,
) : InputAlertDialog(activityContext, ioDispatcher) {

fun show(@StringRes confirmButtonText: Int = R.string.buttonCreate) = show(
title = R.string.newFolderDialogTitle,
hint = R.string.newFolderDialogHint,
confirmButtonText = confirmButtonText,
)

fun setCallbacks(onPositiveButtonClicked: (String) -> Unit) = setCallbacks(
onPositiveButtonClicked = { folderName ->
activityContext.trackCreateFolderEvent("confirm")
onPositiveButtonClicked(folderName)
},
onErrorCheck = { folderName ->
activityContext.getFolderCreationError(folderName, folderController)
},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import javax.inject.Inject
import com.infomaniak.lib.core.R as RCore

@ActivityScoped
class InputAlertDialog @Inject constructor(
open class InputAlertDialog @Inject constructor(
@ActivityContext private val activityContext: Context,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
) : BaseAlertDialog(activityContext) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import com.infomaniak.lib.core.utils.safeNavigate
import com.infomaniak.mail.R
import com.infomaniak.mail.data.models.mailbox.Mailbox
import com.infomaniak.mail.ui.bottomSheetDialogs.LockedMailboxBottomSheetDialogArgs
import com.infomaniak.mail.ui.main.menu.MailboxesAdapter
import com.infomaniak.mail.ui.main.menuDrawer.MailboxesAdapter
import com.infomaniak.mail.utils.AccountUtils
import kotlinx.coroutines.launch

Expand All @@ -49,7 +49,7 @@ interface MailboxListFragment {
)
}

fun Fragment.onValidMailboxClicked(mailboxId: Int) {
lifecycleScope.launch { AccountUtils.switchToMailbox(mailboxId) }
fun Fragment.onValidMailboxClicked(mailboxId: Int) = lifecycleScope.launch {
AccountUtils.switchToMailbox(mailboxId)
}
}
Loading

0 comments on commit e4c390a

Please sign in to comment.