Skip to content

Commit 534a532

Browse files
committed
Merge branch 'master' into app-lock-on-screen-off
2 parents e051eee + 51d5bd8 commit 534a532

File tree

22 files changed

+172
-140
lines changed

22 files changed

+172
-140
lines changed

app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ android {
2222
applicationId 'com.infomaniak.mail'
2323
minSdk 25
2424
targetSdk 34
25-
versionCode 1_06_008_01
26-
versionName '1.6.8'
25+
versionCode 1_06_009_01
26+
versionName '1.6.9'
2727
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
2828

2929
setProperty "archivesBaseName", "infomaniak-mail-$versionName ($versionCode)"

app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,10 @@
9494
android:theme="@style/AppThemeLauncher">
9595
<intent-filter>
9696
<action android:name="android.intent.action.MAIN" />
97+
9798
<category android:name="android.intent.category.LAUNCHER" />
99+
<category android:name="android.intent.category.APP_EMAIL" />
100+
<category android:name="android.intent.category.DEFAULT" />
98101
</intent-filter>
99102

100103
<meta-data

app/src/main/java/com/infomaniak/mail/data/api/FlatteningSubBodiesSerializer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ object FlatteningSubBodiesSerializer : JsonTransformingSerializer<RealmList<SubB
3838
outputList: MutableList<JsonElement> = mutableListOf(),
3939
): List<JsonElement> {
4040

41-
val remoteSubBody = inputList.removeFirst() as? JsonObject
41+
val remoteSubBody = inputList.removeAt(0) as? JsonObject
4242
val remoteBody = remoteSubBody?.get("body") as? JsonObject
4343
val remoteSubBodies = remoteBody?.get("subBody") as? JsonArray
4444

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ class FolderController @Inject constructor(
200200
suspend fun updateFolderAndChildren(id: String, realm: Realm, onUpdate: (Folder) -> Unit) {
201201

202202
tailrec fun updateChildrenRecursively(inputList: MutableList<Folder>) {
203-
val folder = inputList.removeFirst()
203+
val folder = inputList.removeAt(0)
204204
onUpdate(folder)
205205
inputList.addAll(folder.children)
206206

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,7 @@ class RefreshController @Inject constructor(
768768
"3_folderId" to folder.id,
769769
"5_deleted" to activities.deletedShortUids.map { it },
770770
"6_updated" to activities.updatedMessages.map { it.shortUid },
771-
"7_updated" to activities.addedShortUids.map { it },
771+
"7_added" to activities.addedShortUids.map { it },
772772
),
773773
)
774774
}

app/src/main/java/com/infomaniak/mail/data/models/message/Message.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@ class Message : RealmObject {
161161
// TODO: Remove this `runCatching / getOrElse` when the issue is fixed
162162
inline val folder
163163
get() = runCatching {
164-
threads.single { it.folder.id == folderId || it.folder.id == FolderController.SEARCH_FOLDER_ID }.folder
164+
(threads.singleOrNull { it.folder.id == folderId }
165+
?: threads.single { it.folder.id == FolderController.SEARCH_FOLDER_ID }).folder
165166
}.getOrElse { exception ->
166167

167168
val reason = when {
@@ -180,6 +181,7 @@ class Message : RealmObject {
180181
scope.setExtra("threadsCount", "${threads.count()}")
181182
scope.setExtra("threadsFolder", "${threads.map { "role:[${it.folder.role?.name}] (id:[${it.folder.id}])" }}")
182183
scope.setExtra("messageUid", uid)
184+
scope.setExtra("folderId", folderId)
183185
scope.setExtra("email", AccountUtils.currentMailboxEmail.toString())
184186
scope.setExtra("exception", exception.message.toString())
185187
}
@@ -322,7 +324,7 @@ class Message : RealmObject {
322324
fun shouldBeExpanded(index: Int, lastIndex: Int) = !isDraft && (!isSeen || index == lastIndex)
323325

324326
fun toThread() = Thread().apply {
325-
uid = this@Message.uid // TODO: Check if we can use random UUID instead ?
327+
uid = this@Message.uid
326328
folderId = this@Message.folderId
327329
messagesIds += this@Message.messageIds
328330
messages += this@Message

app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListFragment.kt

Lines changed: 50 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import com.infomaniak.mail.ui.MainActivity
6767
import com.infomaniak.mail.ui.alertDialogs.DescriptionAlertDialog
6868
import com.infomaniak.mail.ui.alertDialogs.TitleAlertDialog
6969
import com.infomaniak.mail.ui.main.SnackbarManager
70+
import com.infomaniak.mail.ui.main.folder.ThreadListViewModel.ContentDisplayMode
7071
import com.infomaniak.mail.ui.main.settings.appearance.swipe.SwipeActionsSettingsFragment
7172
import com.infomaniak.mail.ui.main.thread.ThreadFragment
7273
import com.infomaniak.mail.ui.newMessage.NewMessageActivityArgs
@@ -80,8 +81,6 @@ import com.infomaniak.mail.utils.Utils.isPermanentDeleteFolder
8081
import com.infomaniak.mail.utils.Utils.runCatchingRealm
8182
import com.infomaniak.mail.utils.extensions.*
8283
import dagger.hilt.android.AndroidEntryPoint
83-
import io.sentry.Sentry
84-
import io.sentry.SentryLevel
8584
import kotlinx.coroutines.launch
8685
import java.util.Date
8786
import javax.inject.Inject
@@ -164,6 +163,7 @@ class ThreadListFragment : TwoPaneFragment(), SwipeRefreshLayout.OnRefreshListen
164163
observeUpdateInstall()
165164
observeWebViewOutdated()
166165
observeLoadMoreTriggers()
166+
observeContentDisplayMode()
167167
}.getOrDefault(Unit)
168168

169169
@ColorRes
@@ -706,46 +706,60 @@ class ThreadListFragment : TwoPaneFragment(), SwipeRefreshLayout.OnRefreshListen
706706

707707
private fun updateThreadsVisibility() = with(threadListViewModel) {
708708

709-
fun displayThreadsView(
710-
areThereThreads: Boolean,
711-
isFilterEnabled: Boolean,
712-
isBooting: Boolean,
713-
isWaitingFirstThreads: Boolean,
714-
) {
709+
val isNetworkConnected = mainViewModel.hasNetwork
715710

716-
with(binding) {
717-
emptyStateView.isGone = true
718-
threadsList.isVisible = true
719-
}
711+
// The folder's cursor is null, meaning it's the 1st time we are opening this folder.
712+
val isCursorNull = currentFolderCursor == null
720713

721-
if (!areThereThreads && !isFilterEnabled && !isBooting && !isWaitingFirstThreads) {
722-
val currentFolder = mainViewModel.currentFolder.value
723-
Sentry.captureMessage(
724-
"Should display threads is true but there are no threads to display",
725-
SentryLevel.WARNING,
726-
) { scope ->
727-
scope.setExtra("cursor", "$currentFolderCursor")
728-
scope.setExtra("folderRole", currentFolder?.role?.name.toString())
729-
scope.setExtra("folderThreadsCount", "${currentFolder?.threads?.count()}")
730-
}
731-
}
732-
}
714+
// We have a cursor, but don't have any info about threads yet, meaning the app is still booting and loading things.
715+
val isBooting = !isCursorNull && currentThreadsCount == null
733716

734-
val areThereThreads = (currentThreadsCount ?: 0) > 0
717+
// We know that there is existing threads in this folder, so if we wait long enough, they'll be there.
718+
val areThereThreadsSoon = mainViewModel.currentFolderLive.value?.oldMessagesUidsToFetch?.isNotEmpty() == true
719+
720+
// If there is network connectivity, but we either don't have a cursor yet
721+
// or don't have threads yet (but we know that they are coming), it means
722+
// we are opening this folder for the 1st time and we know we'll have a result at the end.
723+
val isWaitingFirstThreads = (isCursorNull || areThereThreadsSoon) && isNetworkConnected
724+
725+
// There is at least 1 thread available to be displayed right now.
726+
val areThereThreadsNow = (currentThreadsCount ?: 0) > 0
727+
728+
// If we filtered on something, it means we have threads, so we want to display the Threads display mode.
735729
val isFilterEnabled = mainViewModel.currentFilter.value != ThreadFilter.ALL
736-
val isCursorNull = currentFolderCursor == null
737-
val isNetworkConnected = mainViewModel.hasNetwork
738-
val isBooting = currentThreadsCount == null && !isCursorNull && isNetworkConnected
739-
val isWaitingFirstThreads = isCursorNull && isNetworkConnected
740-
val shouldDisplayThreadsView = isBooting || isWaitingFirstThreads || areThereThreads || isFilterEnabled
741730

742-
when {
743-
shouldDisplayThreadsView -> displayThreadsView(areThereThreads, isFilterEnabled, isBooting, isWaitingFirstThreads)
744-
isCursorNull || !isNetworkConnected -> setEmptyState(EmptyState.NETWORK)
745-
isCurrentFolderRole(FolderRole.INBOX) -> setEmptyState(EmptyState.INBOX)
746-
isCurrentFolderRole(FolderRole.TRASH) -> setEmptyState(EmptyState.TRASH)
747-
else -> setEmptyState(EmptyState.FOLDER)
731+
// If any of these conditions is true, it means Threads are on their way or the
732+
// app is still loading things, so either way we want to display the Threads mode.
733+
val shouldDisplayThreadsView = isBooting || isWaitingFirstThreads || areThereThreadsNow || isFilterEnabled
734+
735+
contentDisplayMode.value = when {
736+
shouldDisplayThreadsView -> ContentDisplayMode.Threads
737+
!isNetworkConnected -> ContentDisplayMode.NoNetwork
738+
else -> ContentDisplayMode.EmptyFolder
739+
}
740+
}
741+
742+
private fun observeContentDisplayMode() {
743+
744+
fun folderEmptyState() = when {
745+
isCurrentFolderRole(FolderRole.INBOX) -> EmptyState.INBOX
746+
isCurrentFolderRole(FolderRole.TRASH) -> EmptyState.TRASH
747+
else -> EmptyState.FOLDER
748748
}
749+
750+
threadListViewModel.contentDisplayMode
751+
.observe(viewLifecycleOwner) {
752+
when (it) {
753+
ContentDisplayMode.Threads, null -> displayThreadsView()
754+
ContentDisplayMode.NoNetwork -> setEmptyState(EmptyState.NETWORK)
755+
ContentDisplayMode.EmptyFolder -> setEmptyState(folderEmptyState())
756+
}
757+
}
758+
}
759+
760+
private fun displayThreadsView() = with(binding) {
761+
emptyStateView.isGone = true
762+
threadsList.isVisible = true
749763
}
750764

751765
private fun setEmptyState(emptyState: EmptyState): Unit = with(binding) {

app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListViewModel.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ class ThreadListViewModel @Inject constructor(
4646
val updatedAtTrigger = MutableLiveData<Unit>()
4747
val isWebViewOutdated = MutableLiveData(false)
4848

49+
val contentDisplayMode = MutableLiveData(ContentDisplayMode.Threads)
50+
4951
var currentFolderCursor: String? = null
5052
var currentThreadsCount: Int? = null
5153

@@ -84,6 +86,12 @@ class ThreadListViewModel @Inject constructor(
8486
isWebViewOutdated.value = canShowWebViewOutdated && hasOutdatedMajorVersion
8587
}
8688

89+
enum class ContentDisplayMode {
90+
Threads,
91+
NoNetwork,
92+
EmptyFolder,
93+
}
94+
8795
companion object {
8896
private const val WEBVIEW_OFFICIAL_PACKAGE_NAME = "com.google.android.webview"
8997
private const val WEBVIEW_OFFICIAL_MIN_VERSION = 124

app/src/main/java/com/infomaniak/mail/ui/main/search/RecentSearchAdapter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class RecentSearchAdapter(
6868

6969
while (count() > MAX_HISTORY_COUNT) {
7070
notifyItemRemoved(lastIndex)
71-
removeLast()
71+
removeAt(lastIndex)
7272
}
7373

7474
return true

app/src/main/java/com/infomaniak/mail/ui/main/thread/AttachmentAdapter.kt

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ import androidx.recyclerview.widget.RecyclerView.Adapter
2424
import androidx.recyclerview.widget.RecyclerView.ViewHolder
2525
import com.infomaniak.mail.R
2626
import com.infomaniak.mail.data.models.Attachable
27-
import com.infomaniak.mail.data.models.Attachment
28-
import com.infomaniak.mail.data.models.Attachment.AttachmentDisposition
2927
import com.infomaniak.mail.databinding.ItemAttachmentBinding
3028
import com.infomaniak.mail.ui.main.thread.AttachmentAdapter.AttachmentViewHolder
3129
import com.infomaniak.mail.utils.Utils.runCatchingRealm
@@ -78,13 +76,9 @@ class AttachmentAdapter(
7876

7977
override fun getItemCount(): Int = runCatchingRealm { attachments.count() }.getOrDefault(0)
8078

81-
fun setAttachments(newList: List<Attachable>) = runCatchingRealm {
79+
fun submitList(newList: List<Attachable>) = runCatchingRealm {
8280
attachments.clear()
8381
attachments.addAll(newList)
84-
}
85-
86-
fun submitList(newList: List<Attachment>) {
87-
setAttachments(newList.filterNot { it.disposition == AttachmentDisposition.INLINE })
8882
notifyDataSetChanged()
8983
}
9084

app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadAdapter.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ class ThreadAdapter(
203203
bindHeader(message)
204204
bindAlerts(message.uid)
205205
bindCalendarEvent(message)
206-
bindAttachment(message)
206+
bindAttachments(message)
207207
bindContent(message)
208208

209209
onExpandOrCollapseMessage(message, shouldTrack = false)
@@ -462,7 +462,7 @@ class ThreadAdapter(
462462
private fun ItemMessageBinding.areOneOrMoreAlertsVisible() = alerts.children.any { it.isVisible }
463463

464464
@SuppressLint("SetTextI18n")
465-
private fun MessageViewHolder.bindAttachment(message: Message) = with(binding) {
465+
private fun MessageViewHolder.bindAttachments(message: Message) = with(binding) {
466466

467467
if (!message.hasAttachable) {
468468
attachmentLayout.root.isVisible = false
@@ -482,7 +482,7 @@ class ThreadAdapter(
482482
)
483483
}
484484

485-
attachmentAdapter.setAttachments(attachments)
485+
attachmentAdapter.submitList(attachments)
486486

487487
attachmentLayout.attachmentsSizeText.text = totalAttachmentsSize
488488
attachmentLayout.attachmentsInfo.setOnClickListener { threadAdapterCallbacks?.onDownloadAllClicked?.invoke(message) }

app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageFragment.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import com.infomaniak.mail.R
5656
import com.infomaniak.mail.data.LocalSettings
5757
import com.infomaniak.mail.data.LocalSettings.ExternalContent
5858
import com.infomaniak.mail.data.models.Attachment
59+
import com.infomaniak.mail.data.models.Attachment.AttachmentDisposition
5960
import com.infomaniak.mail.data.models.draft.Draft
6061
import com.infomaniak.mail.data.models.draft.Draft.DraftAction
6162
import com.infomaniak.mail.data.models.draft.Draft.DraftMode
@@ -601,7 +602,9 @@ class NewMessageFragment : Fragment() {
601602

602603
// When removing an Attachment, both counts will be the same, because the Adapter is already notified.
603604
// We don't want to notify it again, because it will cancel the nice animation.
604-
if (attachments.count() != attachmentAdapter.itemCount) attachmentAdapter.submitList(attachments)
605+
if (attachments.count() != attachmentAdapter.itemCount) {
606+
attachmentAdapter.submitList(attachments.filterNot { it.disposition == AttachmentDisposition.INLINE })
607+
}
605608

606609
if (attachments.isEmpty()) TransitionManager.beginDelayedTransition(binding.root)
607610
binding.attachmentsRecyclerView.isVisible = attachments.isNotEmpty()

app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageViewModel.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,6 @@ class NewMessageViewModel @Inject constructor(
192192
fun arrivedFromExistingDraft() = arrivedFromExistingDraft
193193
fun draftLocalUuid() = draftLocalUuid
194194
fun draftMode() = draftMode
195-
fun recipient() = recipient
196195
fun shouldLoadDistantResources() = shouldLoadDistantResources
197196

198197
fun initDraftAndViewModel(intent: Intent): LiveData<Draft?> = liveData(ioCoroutineContext) {

app/src/main/java/com/infomaniak/mail/ui/newMessage/RecipientFieldView.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,7 @@ class RecipientFieldView @JvmOverloads constructor(
132132
onContactClicked = { addRecipient(it.email, it.name) },
133133
onAddUnrecognizedContact = {
134134
val input = textInput.text.toString()
135-
if (input.isEmail()) {
136-
addRecipient(email = input, name = input)
137-
} else {
138-
snackbarManager.setValue(context.getString(R.string.addUnknownRecipientInvalidEmail))
139-
}
135+
addRecipient(email = input, name = input)
140136
},
141137
snackbarManager = snackbarManager,
142138
)
@@ -312,6 +308,11 @@ class RecipientFieldView @JvmOverloads constructor(
312308

313309
private fun addRecipient(email: String, name: String) {
314310

311+
if (!email.isEmail()) {
312+
snackbarManager.setValue(context.getString(R.string.addUnknownRecipientInvalidEmail))
313+
return
314+
}
315+
315316
if (contactChipAdapter.itemCount > MAX_ALLOWED_RECIPIENT) {
316317
snackbarManager.setValue(context.getString(R.string.tooManyRecipients))
317318
return

app/src/main/java/com/infomaniak/mail/utils/ErrorCode.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ object ErrorCode {
3737
//region Mailbox
3838
const val MAILBOX_LOCKED = "mailbox_locked"
3939
const val ACCESS_DENIED = "access_denied"
40+
const val NOT_AUTHORIZED = "not_authorized"
4041
const val ERROR_WHILE_LINKING_MAILBOX = "error_while_linking_mailbox"
4142
const val INVALID_MAILBOX_PASSWORD = "invalid_mailbox_password"
4243
//endregion

app/src/main/java/com/infomaniak/mail/utils/FetchMessagesManager.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ class FetchMessagesManager @Inject constructor(
5656
private fun shouldLogToSentry(throwable: Throwable?): Boolean {
5757
return when (throwable) {
5858
is CancellationException, is NetworkException -> false
59-
is ApiErrorException -> throwable.errorCode != ErrorCode.ACCESS_DENIED
59+
is ApiErrorException -> {
60+
throwable.errorCode != ErrorCode.ACCESS_DENIED && throwable.errorCode != ErrorCode.NOT_AUTHORIZED
61+
}
6062
else -> true
6163
}
6264
}

app/src/main/java/com/infomaniak/mail/utils/extensions/Extensions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ fun List<Folder>.flattenFolderChildrenAndRemoveMessages(dismissHiddenChildren: B
337337
outputList: MutableList<Folder> = mutableListOf(),
338338
): List<Folder> {
339339

340-
val folder = inputList.removeFirst()
340+
val folder = inputList.removeAt(0)
341341

342342
val children = if (folder.isManaged()) {
343343
outputList.add(folder.copyFromRealm(depth = 1u))

app/src/main/res/layout/fragment_thread_list.xml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,7 @@
232232
android:clipToPadding="false"
233233
android:paddingBottom="@dimen/recyclerViewPaddingBottom"
234234
app:behind_swiped_item_icon_margin="@dimen/marginStandard"
235-
tools:listitem="@layout/cardview_thread_item"
236-
tools:visibility="visible" />
235+
tools:listitem="@layout/cardview_thread_item" />
237236

238237
</LinearLayout>
239238

0 commit comments

Comments
 (0)