Skip to content

Commit a7a72b6

Browse files
Merge pull request #1994 from Infomaniak/menuDrawer-actions-as-items
Menu drawer actions as items
2 parents a8f9317 + edcfc18 commit a7a72b6

19 files changed

+337
-155
lines changed

app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/FolderController.kt

+24-5
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,20 @@ class FolderController @Inject constructor(
4545
) {
4646

4747
//region Get data
48-
fun getRootFoldersAsync(): Flow<ResultsChange<Folder>> {
48+
fun getMenuDrawerDefaultFoldersAsync(): Flow<ResultsChange<Folder>> {
49+
return getFoldersQuery(mailboxContentRealm(), withoutType = FoldersType.CUSTOM, withoutChildren = true).asFlow()
50+
}
51+
52+
fun getMenuDrawerCustomFoldersAsync(): Flow<ResultsChange<Folder>> {
53+
return getFoldersQuery(mailboxContentRealm(), withoutType = FoldersType.DEFAULT, withoutChildren = true).asFlow()
54+
}
55+
56+
fun getSearchFoldersAsync(): Flow<ResultsChange<Folder>> {
4957
return getFoldersQuery(mailboxContentRealm(), withoutChildren = true).asFlow()
5058
}
5159

5260
fun getMoveFolders(): RealmResults<Folder> {
53-
return getFoldersQuery(mailboxContentRealm(), withoutChildren = true, withoutDrafts = true).find()
61+
return getFoldersQuery(mailboxContentRealm(), withoutType = FoldersType.DRAFT, withoutChildren = true).find()
5462
}
5563

5664
fun getFolder(id: String): Folder? {
@@ -123,6 +131,12 @@ class FolderController @Inject constructor(
123131
}
124132
//endregion
125133

134+
enum class FoldersType {
135+
DEFAULT,
136+
CUSTOM,
137+
DRAFT,
138+
}
139+
126140
companion object {
127141
const val SEARCH_FOLDER_ID = "search_folder_id"
128142
private val isNotSearch = "${Folder::id.name} != '$SEARCH_FOLDER_ID'"
@@ -131,12 +145,17 @@ class FolderController @Inject constructor(
131145
//region Queries
132146
private fun getFoldersQuery(
133147
realm: TypedRealm,
148+
withoutType: FoldersType? = null,
134149
withoutChildren: Boolean = false,
135-
withoutDrafts: Boolean = false,
136150
): RealmQuery<Folder> {
137151
val rootsQuery = if (withoutChildren) " AND $isRootFolder" else ""
138-
val draftsQuery = if (withoutDrafts) " AND ${Folder.rolePropertyName} != '${FolderRole.DRAFT.name}'" else ""
139-
return realm.query<Folder>("$isNotSearch${rootsQuery}${draftsQuery}").sortFolders()
152+
val typeQuery = when (withoutType) {
153+
FoldersType.DEFAULT -> " AND ${Folder.rolePropertyName} == nil"
154+
FoldersType.CUSTOM -> " AND ${Folder.rolePropertyName} != nil"
155+
FoldersType.DRAFT -> " AND ${Folder.rolePropertyName} != '${FolderRole.DRAFT.name}'"
156+
null -> ""
157+
}
158+
return realm.query<Folder>("$isNotSearch${rootsQuery}${typeQuery}").sortFolders()
140159
}
141160

142161
private fun getFoldersQuery(exceptionsFoldersIds: List<String>, realm: TypedRealm): RealmQuery<Folder> {

app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt

+7-4
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ import kotlinx.coroutines.Job
8484
import kotlinx.coroutines.delay
8585
import kotlinx.coroutines.flow.MutableStateFlow
8686
import kotlinx.coroutines.flow.emptyFlow
87+
import kotlinx.coroutines.flow.filterNotNull
8788
import kotlinx.coroutines.flow.flatMapLatest
8889
import kotlinx.coroutines.flow.map
8990
import kotlinx.coroutines.flow.mapLatest
@@ -156,10 +157,12 @@ class MainViewModel @Inject constructor(
156157
it?.let(mailboxController::getMailbox)
157158
}.asLiveData(ioCoroutineContext)
158159

159-
val currentFoldersLive = _currentMailboxObjectId.flatMapLatest { objectId ->
160-
objectId
161-
?.let { folderController.getRootFoldersAsync().map { it.list.flattenFolderChildren(dismissHiddenChildren = true) } }
162-
?: emptyFlow()
160+
val defaultFoldersLive = _currentMailboxObjectId.filterNotNull().flatMapLatest {
161+
folderController.getMenuDrawerDefaultFoldersAsync().map { it.list.flattenFolderChildren(dismissHiddenChildren = true) }
162+
}.asLiveData(ioCoroutineContext)
163+
164+
val customFoldersLive = _currentMailboxObjectId.filterNotNull().flatMapLatest {
165+
folderController.getMenuDrawerCustomFoldersAsync().map { it.list.flattenFolderChildren(dismissHiddenChildren = true) }
163166
}.asLiveData(ioCoroutineContext)
164167

165168
val currentQuotasLive = _currentMailboxObjectId.flatMapLatest {

app/src/main/java/com/infomaniak/mail/ui/main/menuDrawer/MenuDrawerAdapter.kt

+107-59
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,16 @@ import androidx.recyclerview.widget.DiffUtil
2323
import androidx.recyclerview.widget.ListAdapter
2424
import androidx.recyclerview.widget.RecyclerView.ViewHolder
2525
import androidx.viewbinding.ViewBinding
26+
import com.infomaniak.mail.R
2627
import com.infomaniak.mail.data.models.Folder
2728
import com.infomaniak.mail.data.models.mailbox.Mailbox
28-
import com.infomaniak.mail.databinding.ItemInvalidMailboxBinding
29-
import com.infomaniak.mail.databinding.ItemMenuDrawerCustomFoldersHeaderBinding
30-
import com.infomaniak.mail.databinding.ItemMenuDrawerFolderBinding
31-
import com.infomaniak.mail.databinding.ItemMenuDrawerFooterBinding
32-
import com.infomaniak.mail.databinding.ItemMenuDrawerMailboxBinding
33-
import com.infomaniak.mail.databinding.ItemMenuDrawerMailboxesHeaderBinding
29+
import com.infomaniak.mail.data.models.mailbox.MailboxPermissions
3430
import com.infomaniak.mail.ui.main.menuDrawer.MenuDrawerAdapter.MenuDrawerViewHolder
3531
import com.infomaniak.mail.ui.main.menuDrawer.MenuDrawerFragment.MediatorContainer
32+
import com.infomaniak.mail.ui.main.menuDrawer.items.ActionViewHolder
33+
import com.infomaniak.mail.ui.main.menuDrawer.items.ActionViewHolder.MenuDrawerAction
34+
import com.infomaniak.mail.ui.main.menuDrawer.items.ActionViewHolder.MenuDrawerAction.ActionType
35+
import com.infomaniak.mail.ui.main.menuDrawer.items.ActionsHeaderViewHolder
3636
import com.infomaniak.mail.ui.main.menuDrawer.items.DividerItemViewHolder
3737
import com.infomaniak.mail.ui.main.menuDrawer.items.EmptyFoldersViewHolder
3838
import com.infomaniak.mail.ui.main.menuDrawer.items.FolderViewHolder
@@ -66,53 +66,75 @@ class MenuDrawerAdapter @Inject constructor() : ListAdapter<Any, MenuDrawerViewH
6666
val (
6767
mailboxes,
6868
areMailboxesExpanded,
69-
allFolders,
69+
defaultFolders,
70+
customFolders,
7071
areCustomFoldersExpanded,
72+
areActionsExpanded,
7173
permissions,
7274
quotas,
7375
) = mediatorContainer
7476

75-
var count = 0
76-
var temporaryHasCollapsableDefaultFolder = false
77-
var temporaryHasCollapsableCustomFolder = false
77+
addMailboxes(mailboxes, areMailboxesExpanded)
7878

79-
// Mailboxes
80-
val currentMailboxIndex = mailboxes.indexOfFirst { it.mailboxId == AccountUtils.currentMailboxId }
81-
val otherMailboxes = mailboxes.toMutableList()
82-
val currentMailbox = otherMailboxes.removeAt(currentMailboxIndex)
83-
add(MailboxesHeader(currentMailbox, otherMailboxes.isNotEmpty(), areMailboxesExpanded))
84-
if (areMailboxesExpanded) addAll(otherMailboxes)
79+
add(ItemType.DIVIDER)
80+
hasCollapsableDefaultFolder = addDefaultFolders(defaultFolders)
8581

86-
// Default Folders
8782
add(ItemType.DIVIDER)
88-
while (count < allFolders.count() && (allFolders[count].role != null || !allFolders[count].isRoot)) {
89-
val defaultFolder = allFolders[count]
90-
if (defaultFolder.canBeCollapsed) temporaryHasCollapsableDefaultFolder = true
91-
add(defaultFolder)
92-
count++
93-
}
83+
hasCollapsableCustomFolder = addCustomFolders(customFolders, areCustomFoldersExpanded)
9484

95-
// Custom Folders
9685
add(ItemType.DIVIDER)
97-
add(ItemType.FOLDERS_HEADER)
98-
if (areCustomFoldersExpanded) {
99-
if (count == allFolders.count()) {
100-
add(ItemType.EMPTY_FOLDERS)
101-
} else {
102-
while (count < allFolders.count()) {
103-
val customFolder = allFolders[count]
104-
if (customFolder.canBeCollapsed) temporaryHasCollapsableCustomFolder = true
105-
add(customFolder)
106-
count++
107-
}
108-
}
86+
addAdvancedActions(areActionsExpanded, permissions)
87+
88+
add(MenuDrawerFooter(quotas))
89+
}
90+
}
91+
92+
private fun MutableList<Any>.addMailboxes(mailboxes: List<Mailbox>, areMailboxesExpanded: Boolean) {
93+
val currentMailboxIndex = mailboxes.indexOfFirst { it.mailboxId == AccountUtils.currentMailboxId }
94+
val otherMailboxes = mailboxes.toMutableList()
95+
val currentMailbox = otherMailboxes.removeAt(currentMailboxIndex)
96+
97+
add(MailboxesHeader(currentMailbox, otherMailboxes.isNotEmpty(), areMailboxesExpanded))
98+
if (areMailboxesExpanded) addAll(otherMailboxes)
99+
}
100+
101+
private fun MutableList<Any>.addDefaultFolders(defaultFolders: List<Folder>): Boolean {
102+
var atLeastOneFolderIsIndented = false
103+
104+
defaultFolders.forEach { defaultFolder ->
105+
if (defaultFolder.canBeCollapsed) atLeastOneFolderIsIndented = true
106+
add(defaultFolder)
107+
}
108+
109+
return atLeastOneFolderIsIndented
110+
}
111+
112+
private fun MutableList<Any>.addCustomFolders(customFolders: List<Folder>, areCustomFoldersExpanded: Boolean): Boolean {
113+
var atLeastOneFolderIsIndented = false
114+
115+
add(ItemType.FOLDERS_HEADER)
116+
if (!areCustomFoldersExpanded) return false
117+
118+
if (customFolders.isEmpty()) {
119+
add(ItemType.EMPTY_FOLDERS)
120+
} else {
121+
customFolders.forEach { customFolder ->
122+
if (customFolder.canBeCollapsed) atLeastOneFolderIsIndented = true
123+
add(customFolder)
109124
}
110-
hasCollapsableDefaultFolder = temporaryHasCollapsableDefaultFolder
111-
hasCollapsableCustomFolder = temporaryHasCollapsableCustomFolder
125+
}
112126

113-
// Footer
114-
add(ItemType.DIVIDER)
115-
add(MenuDrawerFooter(permissions, quotas))
127+
return atLeastOneFolderIsIndented
128+
}
129+
130+
private fun MutableList<Any>.addAdvancedActions(areActionsExpanded: Boolean, permissions: MailboxPermissions?) {
131+
132+
add(ItemType.ACTIONS_HEADER)
133+
134+
if (areActionsExpanded) {
135+
add(SYNC_AUTO_CONFIG_ACTION)
136+
add(IMPORT_MAILS_ACTION)
137+
if (permissions?.canRestoreEmails == true) add(RESTORE_MAILS_ACTION)
116138
}
117139
}
118140

@@ -154,6 +176,8 @@ class MenuDrawerAdapter @Inject constructor() : ListAdapter<Any, MenuDrawerViewH
154176
ItemType.FOLDERS_HEADER -> ItemType.FOLDERS_HEADER.ordinal
155177
is Folder -> ItemType.FOLDER.ordinal
156178
ItemType.EMPTY_FOLDERS -> ItemType.EMPTY_FOLDERS.ordinal
179+
ItemType.ACTIONS_HEADER -> ItemType.ACTIONS_HEADER.ordinal
180+
is MenuDrawerAction -> ItemType.ACTION.ordinal
157181
is MenuDrawerFooter -> ItemType.FOOTER.ordinal
158182
else -> error("Failed to find a viewType for MenuDrawer item")
159183
}
@@ -170,69 +194,66 @@ class MenuDrawerAdapter @Inject constructor() : ListAdapter<Any, MenuDrawerViewH
170194
ItemType.FOLDERS_HEADER.ordinal -> FoldersHeaderViewHolder(inflater, parent)
171195
ItemType.FOLDER.ordinal -> FolderViewHolder(inflater, parent)
172196
ItemType.EMPTY_FOLDERS.ordinal -> EmptyFoldersViewHolder(inflater, parent)
197+
ItemType.ACTIONS_HEADER.ordinal -> ActionsHeaderViewHolder(inflater, parent)
198+
ItemType.ACTION.ordinal -> ActionViewHolder(inflater, parent)
173199
ItemType.FOOTER.ordinal -> FooterViewHolder(inflater, parent)
174200
else -> error("Failed to find a binding for MenuDrawer viewType")
175201
}
176202
}
177203

178204
override fun onBindViewHolder(holder: MenuDrawerViewHolder, position: Int, payloads: MutableList<Any>) {
179205
if (payloads.firstOrNull() == NotifyType.MAILBOXES_HEADER_CLICKED) {
180-
(holder as MailboxesHeaderViewHolder).updateCollapseState(
181-
header = currentList[position] as MailboxesHeader,
182-
binding = holder.binding as ItemMenuDrawerMailboxesHeaderBinding,
183-
)
206+
(holder as MailboxesHeaderViewHolder).updateCollapseState(header = currentList[position] as MailboxesHeader)
184207
} else {
185208
super.onBindViewHolder(holder, position, payloads)
186209
}
187210
}
188211

189-
override fun onBindViewHolder(holder: MenuDrawerViewHolder, position: Int): Unit = with(holder.binding) {
212+
override fun onBindViewHolder(holder: MenuDrawerViewHolder, position: Int) {
190213
val item = currentList[position]
191214

192215
when (holder) {
193216
is MailboxesHeaderViewHolder -> holder.displayMailboxesHeader(
194217
header = item as MailboxesHeader,
195-
binding = this as ItemMenuDrawerMailboxesHeaderBinding,
196218
onMailboxesHeaderClicked = callbacks.onMailboxesHeaderClicked,
197219
)
198220
is MailboxViewHolder -> holder.displayMailbox(
199221
mailbox = item as Mailbox,
200-
binding = this as ItemMenuDrawerMailboxBinding,
201222
onValidMailboxClicked = callbacks.onValidMailboxClicked,
202223
)
203224
is InvalidMailboxViewHolder -> holder.displayInvalidMailbox(
204225
mailbox = item as Mailbox,
205-
binding = this as ItemInvalidMailboxBinding,
206226
onLockedMailboxClicked = callbacks.onLockedMailboxClicked,
207227
onInvalidPasswordMailboxClicked = callbacks.onInvalidPasswordMailboxClicked,
208228
)
209229
is FoldersHeaderViewHolder -> holder.displayFoldersHeader(
210-
binding = this as ItemMenuDrawerCustomFoldersHeaderBinding,
211230
onFoldersHeaderClicked = callbacks.onFoldersHeaderClicked,
212231
onCreateFolderClicked = callbacks.onCreateFolderClicked,
213232
)
214233
is FolderViewHolder -> holder.displayFolder(
215234
folder = item as Folder,
216-
binding = this as ItemMenuDrawerFolderBinding,
217235
currentFolderId = currentFolderId,
218236
hasCollapsableFolder = if (item.role == null) hasCollapsableCustomFolder else hasCollapsableDefaultFolder,
219237
onFolderClicked = callbacks.onFolderClicked,
220238
onCollapseChildrenClicked = callbacks.onCollapseChildrenClicked,
221239
)
240+
is ActionsHeaderViewHolder -> holder.displayActionsHeader(
241+
onActionsHeaderClicked = callbacks.onActionsHeaderClicked,
242+
)
243+
is ActionViewHolder -> holder.displayAction(
244+
action = item as MenuDrawerAction,
245+
onActionClicked = callbacks.onActionClicked,
246+
)
222247
is FooterViewHolder -> holder.displayFooter(
223248
footer = item as MenuDrawerFooter,
224-
binding = this as ItemMenuDrawerFooterBinding,
225-
onSyncAutoConfigClicked = callbacks.onSyncAutoConfigClicked,
226-
onImportMailsClicked = callbacks.onImportMailsClicked,
227-
onRestoreMailsClicked = callbacks.onRestoreMailsClicked,
228249
onFeedbackClicked = callbacks.onFeedbackClicked,
229250
onHelpClicked = callbacks.onHelpClicked,
230251
onAppVersionClicked = callbacks.onAppVersionClicked,
231252
)
232253
}
233254
}
234255

235-
abstract class MenuDrawerViewHolder(val binding: ViewBinding) : ViewHolder(binding.root)
256+
abstract class MenuDrawerViewHolder(open val binding: ViewBinding) : ViewHolder(binding.root)
236257

237258
enum class ItemType {
238259
DIVIDER,
@@ -242,6 +263,8 @@ class MenuDrawerAdapter @Inject constructor() : ListAdapter<Any, MenuDrawerViewH
242263
FOLDERS_HEADER,
243264
FOLDER,
244265
EMPTY_FOLDERS,
266+
ACTIONS_HEADER,
267+
ACTION,
245268
FOOTER,
246269
}
247270

@@ -259,29 +282,33 @@ class MenuDrawerAdapter @Inject constructor() : ListAdapter<Any, MenuDrawerViewH
259282
ItemType.FOLDERS_HEADER -> newItem == ItemType.FOLDERS_HEADER
260283
is Folder -> newItem is Folder && newItem.id == oldItem.id
261284
ItemType.EMPTY_FOLDERS -> newItem == ItemType.EMPTY_FOLDERS
285+
ItemType.ACTIONS_HEADER -> newItem == ItemType.ACTIONS_HEADER
286+
is MenuDrawerAction -> newItem is MenuDrawerAction && newItem.type == oldItem.type
262287
is MenuDrawerFooter -> newItem is MenuDrawerFooter
263288
else -> error("oldItem wasn't any known item type (in MenuDrawer `areItemsTheSame`)")
264289
}
265290
}.getOrDefault(false)
266291

267292
override fun areContentsTheSame(oldItem: Any, newItem: Any) = runCatchingRealm {
268293
when (oldItem) {
269-
ItemType.DIVIDER -> true
270294
is MailboxesHeader -> newItem is MailboxesHeader
271295
&& newItem.hasMoreThanOneMailbox == oldItem.hasMoreThanOneMailbox
272296
&& newItem.isExpanded == oldItem.isExpanded
273297
&& newItem.mailbox?.unreadCountDisplay?.count == oldItem.mailbox?.unreadCountDisplay?.count
274298
is Mailbox -> newItem is Mailbox && newItem.unreadCountDisplay.count == oldItem.unreadCountDisplay.count
275-
ItemType.FOLDERS_HEADER -> true
276299
is Folder -> newItem is Folder &&
277300
newItem.name == oldItem.name &&
278301
newItem.isFavorite == oldItem.isFavorite &&
279302
newItem.path == oldItem.path &&
280303
newItem.unreadCountDisplay == oldItem.unreadCountDisplay &&
281304
newItem.threads.count() == oldItem.threads.count() &&
282305
newItem.canBeCollapsed == oldItem.canBeCollapsed
283-
ItemType.EMPTY_FOLDERS -> true
284306
is MenuDrawerFooter -> newItem is MenuDrawerFooter && newItem.quotas?.size == oldItem.quotas?.size
307+
ItemType.DIVIDER,
308+
ItemType.FOLDERS_HEADER,
309+
ItemType.EMPTY_FOLDERS,
310+
ItemType.ACTIONS_HEADER,
311+
is MenuDrawerAction -> true
285312
else -> error("oldItem wasn't any known item type (in MenuDrawer `areContentsTheSame`)")
286313
}
287314
}.getOrDefault(false)
@@ -294,4 +321,25 @@ class MenuDrawerAdapter @Inject constructor() : ListAdapter<Any, MenuDrawerViewH
294321
}
295322
}
296323
}
324+
325+
companion object {
326+
private val SYNC_AUTO_CONFIG_ACTION = MenuDrawerAction(
327+
type = ActionType.SYNC_AUTO_CONFIG,
328+
icon = R.drawable.ic_synchronize,
329+
text = R.string.syncCalendarsAndContactsTitle,
330+
maxLines = 2,
331+
)
332+
private val IMPORT_MAILS_ACTION = MenuDrawerAction(
333+
type = ActionType.IMPORT_MAILS,
334+
icon = R.drawable.ic_drawer_download,
335+
text = R.string.buttonImportEmails,
336+
maxLines = 1,
337+
)
338+
private val RESTORE_MAILS_ACTION = MenuDrawerAction(
339+
type = ActionType.RESTORE_MAILS,
340+
icon = R.drawable.ic_restore_arrow,
341+
text = R.string.buttonRestoreEmails,
342+
maxLines = 1,
343+
)
344+
}
297345
}

app/src/main/java/com/infomaniak/mail/ui/main/menuDrawer/MenuDrawerAdapterCallbacks.kt

+4-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package com.infomaniak.mail.ui.main.menuDrawer
1919

2020
import com.infomaniak.mail.data.models.mailbox.Mailbox
21+
import com.infomaniak.mail.ui.main.menuDrawer.items.ActionViewHolder.MenuDrawerAction.ActionType
2122

2223
interface MenuDrawerAdapterCallbacks {
2324

@@ -33,9 +34,9 @@ interface MenuDrawerAdapterCallbacks {
3334
var onFolderClicked: (folderId: String) -> Unit
3435
var onCollapseChildrenClicked: (folderId: String, shouldCollapse: Boolean) -> Unit
3536

36-
var onSyncAutoConfigClicked: () -> Unit
37-
var onImportMailsClicked: () -> Unit
38-
var onRestoreMailsClicked: () -> Unit
37+
var onActionsHeaderClicked: () -> Unit
38+
var onActionClicked: (ActionType) -> Unit
39+
3940
var onFeedbackClicked: () -> Unit
4041
var onHelpClicked: () -> Unit
4142
var onAppVersionClicked: () -> Unit

0 commit comments

Comments
 (0)