From 81b16209a665054e647d212f9ebd309702b38280 Mon Sep 17 00:00:00 2001 From: Louis CAD Date: Wed, 3 Jun 2026 14:32:12 +0200 Subject: [PATCH 01/13] refactor: Move Realm models to an included build This allows using a newer Kotlin version in the project, while having Realm models compiled with an older Kotlin compiler, so it works with the no-longer maintained compiler plugin. --- Core | 2 +- EmojiComponents/build.gradle.kts | 9 +- HtmlCleaner/build.gradle.kts | 8 +- OldKotlin/build.gradle.kts | 6 + .../emoji-reaction-models/build.gradle.kts | 30 ++ .../emojicomponents/data/Reaction.kt | 2 +- OldKotlin/gradle.properties | 23 + OldKotlin/gradle/libs.versions.toml | 22 + OldKotlin/realm-models/build.gradle.kts | 42 ++ .../core/common/extensions/Enums.kt | 26 ++ .../core/common/extensions/Parcel.kt | 26 ++ .../core/common/utils/MailApiEnum.kt | 37 ++ .../api/CalendarRealmInstantSerializer.kt | 1 - .../mail/data/api/DateSerializer.kt | 1 - .../com/infomaniak/mail/data/api/DateUtils.kt | 20 + .../data/api/FlatteningSubBodiesSerializer.kt | 0 .../data/api/HeadersSpamBooleanSerializer.kt | 0 .../mail/data/api/RealmInstantSerializer.kt | 1 - .../data/api/UnwrappingJsonListSerializer.kt | 0 .../mail/data/api/ZeroAsNullLongSerializer.kt | 0 .../mail/data/models/AppSettings.kt | 2 +- .../infomaniak/mail/data/models/Attachable.kt | 27 ++ .../infomaniak/mail/data/models/Attachment.kt | 56 +-- .../mail/data/models/AttachmentDisposition.kt | 2 +- .../data/models/AttachmentUploadStatus.kt | 2 +- .../com/infomaniak/mail/data/models/Bimi.kt | 2 +- .../mail/data/models/FeatureFlag.kt | 2 +- .../com/infomaniak/mail/data/models/Folder.kt | 93 +--- .../infomaniak/mail/data/models/FolderRole.kt | 43 ++ .../data/models/InternalModelProperties.kt | 21 + .../com/infomaniak/mail/data/models/Quotas.kt | 18 +- .../infomaniak/mail/data/models/Snoozable.kt | 5 +- .../mail/data/models/SnoozeState.kt | 4 +- .../data/models/SwissTransferContainer.kt | 2 +- .../mail/data/models/SwissTransferFile.kt | 10 +- .../mail/data/models/TreeStructure.kt | 0 .../data/models/addressBook/AddressBook.kt | 2 +- .../models/addressBook/AddressBooksResult.kt | 0 .../data/models/addressBook/ContactGroup.kt | 2 +- .../mail/data/models/calendar/Attendee.kt | 33 +- .../data/models/calendar/CalendarEvent.kt | 4 +- .../models/calendar/CalendarEventResponse.kt | 17 +- .../mail/data/models/correspondent/Contact.kt | 2 +- .../correspondent/ContactAutocompletable.kt | 2 +- .../models/correspondent/Correspondent.kt | 31 ++ .../models/correspondent/MergedContact.kt | 13 +- .../data/models/correspondent/Recipient.kt | 30 +- .../mail/data/models/draft/Draft.kt | 36 +- .../mail/data/models/mailbox/Mailbox.kt | 34 +- .../data/models/mailbox/MailboxPermissions.kt | 2 +- .../mail/data/models/mailbox/SenderDetails.kt | 2 +- .../models/mailbox/SendersRestrictions.kt | 2 +- .../mail/data/models/message/Body.kt | 2 +- .../models/message/EmojiReactionAuthor.kt | 2 +- .../message/EmojiReactionNotAllowedReason.kt | 34 ++ .../data/models/message/EmojiReactionState.kt | 7 +- .../mail/data/models/message/Headers.kt | 2 +- .../mail/data/models/message/Message.kt | 166 +------- .../mail/data/models/message/SplitBody.kt | 29 ++ .../mail/data/models/message/SubBody.kt | 3 +- .../mail/data/models/signature/Signature.kt | 21 +- .../mail/data/models/thread/Thread.kt | 129 ++++++ .../mail/internal/RealmExtensions.kt | 26 ++ .../extensions/RealmInstantConversion.kt | 29 ++ OldKotlin/settings.gradle.kts | 24 ++ app/build.gradle.kts | 9 +- .../com/infomaniak/mail/MainApplication.kt | 2 + .../java/com/infomaniak/mail/MatomoMail.kt | 4 +- .../infomaniak/mail/data/api/ApiRepository.kt | 4 +- .../com/infomaniak/mail/data/api/ApiRoutes.kt | 7 + .../cache/mailboxContent/FolderController.kt | 11 +- .../cache/mailboxContent/ImpactedFolders.kt | 4 +- .../cache/mailboxContent/MessageController.kt | 4 +- .../cache/mailboxContent/RefreshController.kt | 10 +- .../cache/mailboxContent/ThreadController.kt | 4 +- .../DefaultRefreshStrategy.kt | 5 +- .../refreshStrategies/InboxRefreshStrategy.kt | 4 +- .../refreshStrategies/RefreshStrategy.kt | 4 +- .../SnoozeRefreshStrategy.kt | 4 +- .../infomaniak/mail/data/models/Attachable.kt | 74 ---- .../mail/data/models/AttachmentType.kt | 37 ++ .../mail/data/models/SwipeAction.kt | 3 +- .../mail/data/models/SwipeDisplayBehavior.kt | 3 +- .../data/models/calendar/AttendanceState.kt | 35 ++ .../models/correspondent/Correspondent.kt | 82 ---- .../mail/data/models/draft/DraftAction.kt | 27 ++ .../extensions/CorrespondentExtensions.kt | 82 ++++ .../models/extensions/MessageExtensions.kt | 176 ++++++++ .../models/extensions/ModelsExtensions.kt | 399 ++++++++++++++++++ .../models/extensions/ThreadExtensions.kt | 170 ++++++++ .../message/EmojiReactionNotAllowedReason.kt | 48 --- .../mail/data/models/thread/Thread.kt | 292 ------------- .../thread/ThreadEmojiReactionsComputation.kt | 3 +- .../mail/data/models/thread/ThreadFilter.kt | 29 ++ .../mail/data/models/thread/ThreadResult.kt | 1 + .../receivers/NotificationActionsReceiver.kt | 5 +- .../com/infomaniak/mail/ui/MainActivity.kt | 3 +- .../com/infomaniak/mail/ui/MainViewModel.kt | 17 +- .../AccountBottomSheetDialog.kt | 5 +- .../main/folder/PerformSwipeActionManager.kt | 7 +- .../mail/ui/main/folder/ThreadItem.kt | 6 +- .../mail/ui/main/folder/ThreadListAdapter.kt | 13 +- .../mail/ui/main/folder/ThreadListFragment.kt | 8 +- .../main/folder/ThreadListMultiSelection.kt | 4 +- .../mail/ui/main/folder/TwoPaneFragment.kt | 4 +- .../main/folderPicker/FolderPickerAdapter.kt | 5 +- .../folderPicker/FolderPickerViewModel.kt | 5 +- .../ui/main/menuDrawer/MenuDrawerAdapter.kt | 3 +- .../main/menuDrawer/items/FolderViewHolder.kt | 8 +- .../main/menuDrawer/items/FooterViewHolder.kt | 3 +- .../menuDrawer/items/MailboxViewHolder.kt | 3 +- .../mail/ui/main/search/NamedFolder.kt | 7 +- .../mail/ui/main/search/SearchFragment.kt | 6 +- .../mail/ui/main/search/SearchViewModel.kt | 4 +- .../mailbox/SignatureSettingAdapter.kt | 3 +- .../mailbox/SignatureSettingFragment.kt | 3 +- .../mail/ui/main/thread/ThreadAdapter.kt | 7 +- .../mail/ui/main/thread/ThreadFragment.kt | 6 +- .../mail/ui/main/thread/ThreadState.kt | 4 +- .../mail/ui/main/thread/ThreadViewModel.kt | 10 +- .../AttachmentActionsBottomSheetDialog.kt | 3 +- .../actions/DownloadAttachmentViewModel.kt | 3 + .../actions/MailActionsBottomSheetDialog.kt | 3 +- .../MessageActionsBottomSheetDialog.kt | 3 +- .../actions/MultiSelectBottomSheetDialog.kt | 2 +- .../actions/ThreadActionsBottomSheetDialog.kt | 2 +- .../thread/calendar/AttendanceAvatarView.kt | 3 +- .../calendar/CalendarEventBannerView.kt | 5 +- .../thread/models/EmojiReactionAuthorUi.kt | 3 +- .../webViewClient/MessageWebViewClient.kt | 2 + .../mail/ui/newMessage/ContactAdapter.kt | 2 +- .../mail/ui/newMessage/NewMessageActivity.kt | 2 +- .../mail/ui/newMessage/NewMessageAiManager.kt | 1 + .../mail/ui/newMessage/NewMessageFragment.kt | 3 +- .../mail/ui/newMessage/NewMessageViewModel.kt | 8 +- .../mail/ui/newMessage/RecipientFieldView.kt | 4 +- .../encryption/EncryptionPasswordFragment.kt | 3 +- .../mail/utils/AttachableMimeTypeUtils.kt | 4 +- .../infomaniak/mail/utils/AvatarTypeUtils.kt | 4 +- .../com/infomaniak/mail/utils/ContactUtils.kt | 9 +- .../infomaniak/mail/utils/DraftInitManager.kt | 5 +- .../com/infomaniak/mail/utils/ErrorCode.kt | 21 +- .../infomaniak/mail/utils/ExternalUtils.kt | 3 +- .../mail/utils/FetchMessagesManager.kt | 10 +- .../infomaniak/mail/utils/FolderRoleUtils.kt | 4 +- .../infomaniak/mail/utils/FolderUiUtils.kt | 5 +- .../mail/utils/LocalStorageUtils.kt | 1 + .../infomaniak/mail/utils/MessageBodyUtils.kt | 12 +- .../com/infomaniak/mail/utils/MessageUtils.kt | 5 +- .../mail/utils/NotificationUtils.kt | 6 +- .../com/infomaniak/mail/utils/SearchUtils.kt | 7 +- .../com/infomaniak/mail/utils/SentryDebug.kt | 5 +- .../com/infomaniak/mail/utils/SharedUtils.kt | 3 +- .../java/com/infomaniak/mail/utils/UiUtils.kt | 3 +- .../java/com/infomaniak/mail/utils/Utils.kt | 5 +- .../mail/utils/extensions/AttachmentExt.kt | 7 + .../utils/extensions/ConfirmationPopupExt.kt | 4 +- .../mail/utils/extensions/Extensions.kt | 14 +- .../mail/utils/extensions/RealmExt.kt | 2 +- .../mail/views/AttachmentDetailsView.kt | 1 + .../com/infomaniak/mail/views/AvatarView.kt | 4 +- .../views/itemViews/SelectableItemView.kt | 3 +- .../mail/workers/MailActionsManager.kt | 4 +- .../main/res/navigation/main_navigation.xml | 4 +- .../res/navigation/new_message_navigation.xml | 2 +- .../mail/StandardMainApplication.kt | 2 +- .../firebase/KMailFirebaseMessagingService.kt | 5 +- .../mail/CalendarEventResponseTest.kt | 4 +- .../infomaniak/mail/FolderRoleUtilsTest.kt | 4 +- .../infomaniak/mail/dataset/DummyFolders.kt | 7 +- .../infomaniak/mail/dataset/DummyThreads.kt | 3 +- .../mail/workers/MailActionsManagerTest.kt | 6 +- build.gradle.kts | 1 - gradle/libs.versions.toml | 5 +- settings.gradle.kts | 5 + 175 files changed, 1994 insertions(+), 1174 deletions(-) create mode 100644 OldKotlin/build.gradle.kts create mode 100644 OldKotlin/emoji-reaction-models/build.gradle.kts rename {EmojiComponents/src/main/java => OldKotlin/emoji-reaction-models/src/main/kotlin}/com/infomaniak/emojicomponents/data/Reaction.kt (94%) create mode 100644 OldKotlin/gradle.properties create mode 100644 OldKotlin/gradle/libs.versions.toml create mode 100644 OldKotlin/realm-models/build.gradle.kts create mode 100644 OldKotlin/realm-models/src/main/kotlin/com/infomaniak/core/common/extensions/Enums.kt create mode 100644 OldKotlin/realm-models/src/main/kotlin/com/infomaniak/core/common/extensions/Parcel.kt create mode 100644 OldKotlin/realm-models/src/main/kotlin/com/infomaniak/core/common/utils/MailApiEnum.kt rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/api/CalendarRealmInstantSerializer.kt (97%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/api/DateSerializer.kt (96%) create mode 100644 OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/DateUtils.kt rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/api/FlatteningSubBodiesSerializer.kt (100%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/api/HeadersSpamBooleanSerializer.kt (100%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/api/RealmInstantSerializer.kt (96%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/api/UnwrappingJsonListSerializer.kt (100%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/api/ZeroAsNullLongSerializer.kt (100%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/AppSettings.kt (95%) create mode 100644 OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/Attachable.kt rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/Attachment.kt (51%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/AttachmentDisposition.kt (94%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/AttachmentUploadStatus.kt (94%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/Bimi.kt (97%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/FeatureFlag.kt (95%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/Folder.kt (55%) create mode 100644 OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/FolderRole.kt create mode 100644 OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/InternalModelProperties.kt rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/Quotas.kt (78%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/Snoozable.kt (90%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/SnoozeState.kt (89%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/SwissTransferContainer.kt (96%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/SwissTransferFile.kt (81%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/TreeStructure.kt (100%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/addressBook/AddressBook.kt (97%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/addressBook/AddressBooksResult.kt (100%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/addressBook/ContactGroup.kt (96%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/calendar/Attendee.kt (73%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/calendar/CalendarEvent.kt (96%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/calendar/CalendarEventResponse.kt (87%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/correspondent/Contact.kt (96%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/correspondent/ContactAutocompletable.kt (94%) create mode 100644 OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/correspondent/Correspondent.kt rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/correspondent/MergedContact.kt (94%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/correspondent/Recipient.kt (74%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/draft/Draft.kt (80%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/mailbox/Mailbox.kt (79%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/mailbox/MailboxPermissions.kt (96%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/mailbox/SenderDetails.kt (95%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/mailbox/SendersRestrictions.kt (96%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/message/Body.kt (96%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/message/EmojiReactionAuthor.kt (96%) create mode 100644 OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/EmojiReactionNotAllowedReason.kt rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/message/EmojiReactionState.kt (86%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/message/Headers.kt (96%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/message/Message.kt (64%) create mode 100644 OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/SplitBody.kt rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/message/SubBody.kt (97%) rename {app/src/main/java => OldKotlin/realm-models/src/main/kotlin}/com/infomaniak/mail/data/models/signature/Signature.kt (74%) create mode 100644 OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/thread/Thread.kt create mode 100644 OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/internal/RealmExtensions.kt create mode 100644 OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/utils/extensions/RealmInstantConversion.kt create mode 100644 OldKotlin/settings.gradle.kts delete mode 100644 app/src/main/java/com/infomaniak/mail/data/models/Attachable.kt create mode 100644 app/src/main/java/com/infomaniak/mail/data/models/AttachmentType.kt create mode 100644 app/src/main/java/com/infomaniak/mail/data/models/calendar/AttendanceState.kt delete mode 100644 app/src/main/java/com/infomaniak/mail/data/models/correspondent/Correspondent.kt create mode 100644 app/src/main/java/com/infomaniak/mail/data/models/draft/DraftAction.kt create mode 100644 app/src/main/java/com/infomaniak/mail/data/models/extensions/CorrespondentExtensions.kt create mode 100644 app/src/main/java/com/infomaniak/mail/data/models/extensions/MessageExtensions.kt create mode 100644 app/src/main/java/com/infomaniak/mail/data/models/extensions/ModelsExtensions.kt create mode 100644 app/src/main/java/com/infomaniak/mail/data/models/extensions/ThreadExtensions.kt delete mode 100644 app/src/main/java/com/infomaniak/mail/data/models/message/EmojiReactionNotAllowedReason.kt delete mode 100644 app/src/main/java/com/infomaniak/mail/data/models/thread/Thread.kt create mode 100644 app/src/main/java/com/infomaniak/mail/data/models/thread/ThreadFilter.kt diff --git a/Core b/Core index 7e6b4adf45b..ffae3767eda 160000 --- a/Core +++ b/Core @@ -1 +1 @@ -Subproject commit 7e6b4adf45b89a23401b06f40595db632aab703d +Subproject commit ffae3767edac9d583e26463a3453a213c8bc1944 diff --git a/EmojiComponents/build.gradle.kts b/EmojiComponents/build.gradle.kts index 3ca09411d80..e4ec7a762e2 100644 --- a/EmojiComponents/build.gradle.kts +++ b/EmojiComponents/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { id("com.android.library") alias(core.plugins.kotlin.android) @@ -25,10 +27,6 @@ android { compose = true } - kotlinOptions { - jvmTarget = javaVersion.toString() - } - flavorDimensions += "distribution" productFlavors { @@ -37,6 +35,8 @@ android { } } +kotlin.compilerOptions.jvmTarget = JvmTarget.valueOf("JVM_${javaVersion.name.substringAfter("VERSION_")}") + dependencies { implementation(core.infomaniak.core.avatar) implementation(core.infomaniak.core.ui.compose.margin) @@ -45,6 +45,7 @@ dependencies { implementation(libs.compose.ui.android) implementation(core.androidx.core.ktx) + api(libs.emoji.reaction.models) implementation(platform(core.compose.bom)) implementation(core.compose.runtime) diff --git a/HtmlCleaner/build.gradle.kts b/HtmlCleaner/build.gradle.kts index 71c53fcfab8..7cbc45ec650 100644 --- a/HtmlCleaner/build.gradle.kts +++ b/HtmlCleaner/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { id("com.android.library") id("kotlin-android") @@ -20,10 +22,6 @@ android { targetCompatibility = javaVersion } - kotlinOptions { - jvmTarget = javaVersion.toString() - } - flavorDimensions += "distribution" productFlavors { create("standard") @@ -31,6 +29,8 @@ android { } } +kotlin.compilerOptions.jvmTarget = JvmTarget.valueOf("JVM_${javaVersion.name.substringAfter("VERSION_")}") + dependencies { api(libs.jsoup) } diff --git a/OldKotlin/build.gradle.kts b/OldKotlin/build.gradle.kts new file mode 100644 index 00000000000..1be2cb1cf14 --- /dev/null +++ b/OldKotlin/build.gradle.kts @@ -0,0 +1,6 @@ +plugins { + alias(libs.plugins.android.library) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.serialization) apply false + alias(libs.plugins.realm.kotlin) apply false +} diff --git a/OldKotlin/emoji-reaction-models/build.gradle.kts b/OldKotlin/emoji-reaction-models/build.gradle.kts new file mode 100644 index 00000000000..e65b43cfcab --- /dev/null +++ b/OldKotlin/emoji-reaction-models/build.gradle.kts @@ -0,0 +1,30 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +/** + * Don't change the order in this `plugins` block, it will mess things up. + */ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) +} + +group = "com.infomaniak.mail" +version = "1.0.0" + +val javaVersion: JavaVersion = JavaVersion.VERSION_17 + +android { + namespace = "com.infomaniak.mail.emoji.models" + compileSdk = 36 + + defaultConfig { + minSdk = 27 + } + + compileOptions { + sourceCompatibility = javaVersion + targetCompatibility = javaVersion + } +} + +kotlin.compilerOptions.jvmTarget = JvmTarget.valueOf("JVM_${javaVersion.name.substringAfter("VERSION_")}") diff --git a/EmojiComponents/src/main/java/com/infomaniak/emojicomponents/data/Reaction.kt b/OldKotlin/emoji-reaction-models/src/main/kotlin/com/infomaniak/emojicomponents/data/Reaction.kt similarity index 94% rename from EmojiComponents/src/main/java/com/infomaniak/emojicomponents/data/Reaction.kt rename to OldKotlin/emoji-reaction-models/src/main/kotlin/com/infomaniak/emojicomponents/data/Reaction.kt index c21694c9083..a63726248e8 100644 --- a/EmojiComponents/src/main/java/com/infomaniak/emojicomponents/data/Reaction.kt +++ b/OldKotlin/emoji-reaction-models/src/main/kotlin/com/infomaniak/emojicomponents/data/Reaction.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2026 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 diff --git a/OldKotlin/gradle.properties b/OldKotlin/gradle.properties new file mode 100644 index 00000000000..132244e5b8f --- /dev/null +++ b/OldKotlin/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true diff --git a/OldKotlin/gradle/libs.versions.toml b/OldKotlin/gradle/libs.versions.toml new file mode 100644 index 00000000000..6dc0d8cabe0 --- /dev/null +++ b/OldKotlin/gradle/libs.versions.toml @@ -0,0 +1,22 @@ +[versions] +#noinspection AndroidGradlePluginVersion,NewerVersionAvailable +agp = "8.13.2" +#noinspection NewerVersionAvailable +kotlin = "2.2.10" # Update only with compatible Realm-kotlin releases +#noinspection NewerVersionAvailable +kotlinxSerialization = "1.9.0" # Update only with Kotlin +realmKotlin = "3.2.9" +sentry = "8.27.1" + +[libraries] +androidx-annotation = { module = "androidx.annotation:annotation", version = "1.10.0" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" } +realm-kotlin-base = { module = "com.infomaniak.realm.kotlin:library-base", version.ref = "realmKotlin" } +sentry-android = { module = "io.sentry:sentry-android", version.ref = "sentry" } + +[plugins] +android-library = { id = "com.android.library", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-parcelize = { id = "kotlin-parcelize" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +realm-kotlin = { id = "com.infomaniak.realm.kotlin", version.ref = "realmKotlin" } diff --git a/OldKotlin/realm-models/build.gradle.kts b/OldKotlin/realm-models/build.gradle.kts new file mode 100644 index 00000000000..1c2ec4b7a2c --- /dev/null +++ b/OldKotlin/realm-models/build.gradle.kts @@ -0,0 +1,42 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +/** + * Don't change the order in this `plugins` block, it will mess things up. + */ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.realm.kotlin) + alias(libs.plugins.kotlin.parcelize) +} + +group = "com.infomaniak.mail" +version = "1.0.0" + +val javaVersion: JavaVersion = JavaVersion.VERSION_17 + +android { + namespace = "com.infomaniak.mail.realm" + compileSdk = 36 + + defaultConfig { + minSdk = 27 + } + + compileOptions { + sourceCompatibility = javaVersion + targetCompatibility = javaVersion + } +} + +kotlin.compilerOptions.jvmTarget = JvmTarget.valueOf("JVM_${javaVersion.name.substringAfter("VERSION_")}") + +dependencies { + + api(libs.realm.kotlin.base) + api(libs.kotlinx.serialization.json) + api(libs.androidx.annotation) + implementation(libs.sentry.android) + api(project(":emoji-reaction-models")) +} diff --git a/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/core/common/extensions/Enums.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/core/common/extensions/Enums.kt new file mode 100644 index 00000000000..020aaccaa9f --- /dev/null +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/core/common/extensions/Enums.kt @@ -0,0 +1,26 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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.core.common.extensions + +internal inline fun > enumValueOfOrNull(value: String?): T? { + return value?.let { runCatching { enumValueOf(it.uppercase()) }.getOrNull() } +} + +internal inline fun > String.toEnumOrThrow(): T { + return enumValueOf(uppercase()) +} diff --git a/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/core/common/extensions/Parcel.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/core/common/extensions/Parcel.kt new file mode 100644 index 00000000000..52c1665578b --- /dev/null +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/core/common/extensions/Parcel.kt @@ -0,0 +1,26 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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.core.common.extensions + +import android.os.Parcel + +internal fun Parcel.customWriteBoolean(value: Boolean) { + writeInt(if (value) 1 else 0) +} + +internal fun Parcel.customReadBoolean(): Boolean = readInt() != 0 diff --git a/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/core/common/utils/MailApiEnum.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/core/common/utils/MailApiEnum.kt new file mode 100644 index 00000000000..81169f28d8b --- /dev/null +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/core/common/utils/MailApiEnum.kt @@ -0,0 +1,37 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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.core.common.utils + +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KMutableProperty0 +import kotlin.reflect.KProperty + +inline fun mailEpiEnumValueOfOrNull(value: String?): T? where T : Enum, T : MailApiEnum { + return value?.let { runCatching { enumValues().firstOrNull { it.apiValue == value } }.getOrNull() } +} + +inline fun mailApiEnum(backingFieldProperty: KMutableProperty0): ReadWriteProperty where T : Enum, T : MailApiEnum { + return object : ReadWriteProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): T? = mailEpiEnumValueOfOrNull(backingFieldProperty.get()) + override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) = backingFieldProperty.set(value?.apiValue) + } +} + +interface MailApiEnum { + val apiValue: String +} diff --git a/app/src/main/java/com/infomaniak/mail/data/api/CalendarRealmInstantSerializer.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/CalendarRealmInstantSerializer.kt similarity index 97% rename from app/src/main/java/com/infomaniak/mail/data/api/CalendarRealmInstantSerializer.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/CalendarRealmInstantSerializer.kt index b622c8cccf4..073d78ce049 100644 --- a/app/src/main/java/com/infomaniak/mail/data/api/CalendarRealmInstantSerializer.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/CalendarRealmInstantSerializer.kt @@ -17,7 +17,6 @@ */ package com.infomaniak.mail.data.api -import com.infomaniak.core.common.utils.FORMAT_DATE_WITH_TIMEZONE import com.infomaniak.mail.utils.extensions.toDate import com.infomaniak.mail.utils.extensions.toRealmInstant import io.realm.kotlin.types.RealmInstant diff --git a/app/src/main/java/com/infomaniak/mail/data/api/DateSerializer.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/DateSerializer.kt similarity index 96% rename from app/src/main/java/com/infomaniak/mail/data/api/DateSerializer.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/DateSerializer.kt index 575d4f8b189..7c73d7300ed 100644 --- a/app/src/main/java/com/infomaniak/mail/data/api/DateSerializer.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/DateSerializer.kt @@ -17,7 +17,6 @@ */ package com.infomaniak.mail.data.api -import com.infomaniak.core.common.utils.FORMAT_DATE_WITH_TIMEZONE import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor diff --git a/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/DateUtils.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/DateUtils.kt new file mode 100644 index 00000000000..ebefc97f15d --- /dev/null +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/DateUtils.kt @@ -0,0 +1,20 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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.data.api + +internal const val FORMAT_DATE_WITH_TIMEZONE = "yyyy-MM-dd'T'HH:mm:ssZ" // Example: 2025-12-31T23:59:59+0100 diff --git a/app/src/main/java/com/infomaniak/mail/data/api/FlatteningSubBodiesSerializer.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/FlatteningSubBodiesSerializer.kt similarity index 100% rename from app/src/main/java/com/infomaniak/mail/data/api/FlatteningSubBodiesSerializer.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/FlatteningSubBodiesSerializer.kt diff --git a/app/src/main/java/com/infomaniak/mail/data/api/HeadersSpamBooleanSerializer.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/HeadersSpamBooleanSerializer.kt similarity index 100% rename from app/src/main/java/com/infomaniak/mail/data/api/HeadersSpamBooleanSerializer.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/HeadersSpamBooleanSerializer.kt diff --git a/app/src/main/java/com/infomaniak/mail/data/api/RealmInstantSerializer.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/RealmInstantSerializer.kt similarity index 96% rename from app/src/main/java/com/infomaniak/mail/data/api/RealmInstantSerializer.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/RealmInstantSerializer.kt index 4a4f0bf25d9..1eb4db0f5c0 100644 --- a/app/src/main/java/com/infomaniak/mail/data/api/RealmInstantSerializer.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/RealmInstantSerializer.kt @@ -17,7 +17,6 @@ */ package com.infomaniak.mail.data.api -import com.infomaniak.core.common.utils.FORMAT_DATE_WITH_TIMEZONE import com.infomaniak.mail.utils.extensions.toDate import com.infomaniak.mail.utils.extensions.toRealmInstant import io.realm.kotlin.types.RealmInstant diff --git a/app/src/main/java/com/infomaniak/mail/data/api/UnwrappingJsonListSerializer.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/UnwrappingJsonListSerializer.kt similarity index 100% rename from app/src/main/java/com/infomaniak/mail/data/api/UnwrappingJsonListSerializer.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/UnwrappingJsonListSerializer.kt diff --git a/app/src/main/java/com/infomaniak/mail/data/api/ZeroAsNullLongSerializer.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/ZeroAsNullLongSerializer.kt similarity index 100% rename from app/src/main/java/com/infomaniak/mail/data/api/ZeroAsNullLongSerializer.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/api/ZeroAsNullLongSerializer.kt diff --git a/app/src/main/java/com/infomaniak/mail/data/models/AppSettings.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/AppSettings.kt similarity index 95% rename from app/src/main/java/com/infomaniak/mail/data/models/AppSettings.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/AppSettings.kt index 4d5cd0fd5bd..5c4bf9db4a1 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/AppSettings.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/AppSettings.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2024 Infomaniak Network SA + * Copyright (C) 2022-2026 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 diff --git a/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/Attachable.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/Attachable.kt new file mode 100644 index 00000000000..13f76534a25 --- /dev/null +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/Attachable.kt @@ -0,0 +1,27 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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.data.models + +sealed interface Attachable { + + var size: Long + var name: String + var mimeType: String + var resource: String? + var localUuid: String +} diff --git a/app/src/main/java/com/infomaniak/mail/data/models/Attachment.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/Attachment.kt similarity index 51% rename from app/src/main/java/com/infomaniak/mail/data/models/Attachment.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/Attachment.kt index 890f7572b4b..2c737a7d219 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/Attachment.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/Attachment.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2024 Infomaniak Network SA + * Copyright (C) 2022-2026 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 @@ -15,21 +15,15 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +@file:OptIn(InternalModelProperties::class) + package com.infomaniak.mail.data.models -import android.content.Context -import androidx.core.net.toFile -import androidx.core.net.toUri -import com.infomaniak.core.legacy.utils.Utils.enumValueOfOrNull -import com.infomaniak.mail.data.models.draft.Draft -import com.infomaniak.mail.utils.AttachableMimeTypeUtils -import com.infomaniak.mail.utils.LocalStorageUtils -import com.infomaniak.mail.utils.SentryDebug +import com.infomaniak.core.common.extensions.enumValueOfOrNull import io.realm.kotlin.types.EmbeddedRealmObject import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient -import java.io.File import java.util.UUID @Serializable @@ -61,7 +55,8 @@ class Attachment : EmbeddedRealmObject, Attachable { @Transient override var localUuid: String = UUID.randomUUID().toString() @Transient - private var _uploadStatus: String = AttachmentUploadStatus.NOT_UPLOADED.name + @InternalModelProperties + var _uploadStatus: String = AttachmentUploadStatus.NOT_UPLOADED.name @Transient var uploadLocalUri: String? = null //endregion @@ -69,8 +64,6 @@ class Attachment : EmbeddedRealmObject, Attachable { val attachmentUploadStatus: AttachmentUploadStatus? get() = enumValueOfOrNull(_uploadStatus) - val isCalendarEvent: Boolean get() = AttachableMimeTypeUtils.calendarMatches.contains(mimeType) - val disposition: AttachmentDisposition? get() = enumValueOfOrNull(_disposition) @@ -83,42 +76,5 @@ class Attachment : EmbeddedRealmObject, Attachable { return this } - /** - * After uploading an Attachment, we replace the local version with the remote one. - * The remote one doesn't know about local data, so we have to backup them. - */ - fun backupLocalData(oldAttachment: Attachment, draft: Draft) { - localUuid = oldAttachment.localUuid - uploadLocalUri = oldAttachment.uploadLocalUri - setUploadStatus(AttachmentUploadStatus.UPLOADED, draft, "backupLocalData -> setUploadStatus") - } - - fun setUploadStatus(attachmentUploadStatus: AttachmentUploadStatus, draft: Draft? = null, step: String = "") { - draft?.let { SentryDebug.addDraftBreadcrumbs(it, step) } - _uploadStatus = attachmentUploadStatus.name - } - - override fun getFileTypeFromMimeType() = AttachableMimeTypeUtils.getFileTypeFromMimeType(safeMimeType) - - override fun hasUsableCache(context: Context, file: File?, userId: Int, mailboxId: Int): Boolean { - val cachedFile = file ?: getCacheFile(context, userId, mailboxId) - return cachedFile.length() > 0 && cachedFile.canRead() - } - - override fun isInlineCachedFile(context: Context): Boolean { - return getCacheFile(context).exists() && disposition == AttachmentDisposition.INLINE - } - - override fun getCacheFile(context: Context, userId: Int, mailboxId: Int): File { - val cacheFolder = LocalStorageUtils.getAttachmentsCacheDir(context, extractPathFromResource(), userId, mailboxId) - return File(cacheFolder, name) - } - - private fun extractPathFromResource(): String { - return resource?.substringAfter("folder/")?.replace(Regex("(message|attachment)/"), "") ?: "" - } - - fun getUploadLocalFile() = uploadLocalUri?.toUri()?.toFile() - companion object } diff --git a/app/src/main/java/com/infomaniak/mail/data/models/AttachmentDisposition.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/AttachmentDisposition.kt similarity index 94% rename from app/src/main/java/com/infomaniak/mail/data/models/AttachmentDisposition.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/AttachmentDisposition.kt index 46840effa42..5ac1fe46259 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/AttachmentDisposition.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/AttachmentDisposition.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2024 Infomaniak Network SA + * Copyright (C) 2024-2026 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 diff --git a/app/src/main/java/com/infomaniak/mail/data/models/AttachmentUploadStatus.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/AttachmentUploadStatus.kt similarity index 94% rename from app/src/main/java/com/infomaniak/mail/data/models/AttachmentUploadStatus.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/AttachmentUploadStatus.kt index 05771415ebc..ede761a5ae3 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/AttachmentUploadStatus.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/AttachmentUploadStatus.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2024 Infomaniak Network SA + * Copyright (C) 2026 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 diff --git a/app/src/main/java/com/infomaniak/mail/data/models/Bimi.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/Bimi.kt similarity index 97% rename from app/src/main/java/com/infomaniak/mail/data/models/Bimi.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/Bimi.kt index 72fc7c09357..1f009c8a4ba 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/Bimi.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/Bimi.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2024-2026 Infomaniak Network SA + * Copyright (C) 2026 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 diff --git a/app/src/main/java/com/infomaniak/mail/data/models/FeatureFlag.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/FeatureFlag.kt similarity index 95% rename from app/src/main/java/com/infomaniak/mail/data/models/FeatureFlag.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/FeatureFlag.kt index b4d3d1d3465..99ec2f73969 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/FeatureFlag.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/FeatureFlag.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2023-2025 Infomaniak Network SA + * Copyright (C) 2026 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 diff --git a/app/src/main/java/com/infomaniak/mail/data/models/Folder.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/Folder.kt similarity index 55% rename from app/src/main/java/com/infomaniak/mail/data/models/Folder.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/Folder.kt index c5caa90f396..8834e1e3544 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/Folder.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/Folder.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2025 Infomaniak Network SA + * Copyright (C) 2022-2026 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 @@ -16,30 +16,12 @@ * along with this program. If not, see . */ @file:UseSerializers(RealmListKSerializer::class) -@file:OptIn(TestOnly::class) +@file:OptIn(InternalModelProperties::class) package com.infomaniak.mail.data.models -import android.content.Context -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import com.infomaniak.core.legacy.utils.Utils.enumValueOfOrNull -import com.infomaniak.core.legacy.utils.removeAccents -import com.infomaniak.mail.MatomoMail.MatomoName -import com.infomaniak.mail.R -import com.infomaniak.mail.annotations.TestOnly -import com.infomaniak.mail.data.cache.mailboxContent.MessageController -import com.infomaniak.mail.data.cache.mailboxContent.refreshStrategies.RefreshStrategy -import com.infomaniak.mail.data.cache.mailboxContent.refreshStrategies.defaultRefreshStrategy -import com.infomaniak.mail.data.cache.mailboxContent.refreshStrategies.inboxRefreshStrategy -import com.infomaniak.mail.data.cache.mailboxContent.refreshStrategies.scheduledDraftRefreshStrategy -import com.infomaniak.mail.data.cache.mailboxContent.refreshStrategies.snoozeRefreshStrategy -import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.data.models.thread.Thread -import com.infomaniak.mail.utils.SentryDebug -import com.infomaniak.mail.utils.UnreadDisplay -import com.infomaniak.mail.utils.Utils -import io.realm.kotlin.TypedRealm +import com.infomaniak.core.common.extensions.enumValueOfOrNull import io.realm.kotlin.ext.backlinks import io.realm.kotlin.ext.realmListOf import io.realm.kotlin.query.Sort @@ -62,7 +44,7 @@ class Folder : RealmObject, Cloneable, TreeStructure { var id: String = "" var path: String = "" var name: String = "" - @TestOnly + @InternalModelProperties @SerialName("role") var _role: String? = null @SerialName("is_favorite") @@ -98,7 +80,7 @@ class Folder : RealmObject, Cloneable, TreeStructure { @Transient var newMessagesUidsToFetch = realmListOf() @Transient - var remainingOldMessagesToFetch: Int = Utils.NUMBER_OF_OLD_MESSAGES_TO_FETCH + var remainingOldMessagesToFetch: Int = NUMBER_OF_OLD_MESSAGES_TO_FETCH @Transient var isCollapsed: Boolean = false // For parents only (collapsing a parent Folder will hide its children) @@ -116,17 +98,9 @@ class Folder : RealmObject, Cloneable, TreeStructure { val role: FolderRole? get() = enumValueOfOrNull(_role) - val unreadCountDisplay: UnreadDisplay - inline get() = UnreadDisplay( - count = unreadCountLocal, - shouldDisplayPastille = unreadCountLocal == 0 && unreadCountRemote > 0, - ) - val isRoot: Boolean inline get() = !path.contains(separator) - val refreshStrategy: RefreshStrategy get() = role?.refreshStrategy ?: defaultRefreshStrategy - val folderSort get() = role?.folderSort ?: FolderSort.Default fun initLocalValues( @@ -139,9 +113,9 @@ class Folder : RealmObject, Cloneable, TreeStructure { remainingOldMessagesToFetch: Int, isDisplayed: Boolean, isCollapsed: Boolean, + sortedName: String ) { this.lastUpdatedAt = lastUpdatedAt - SentryDebug.addCursorBreadcrumb("initLocalValues", folder = this, cursor) this.cursor = cursor this.unreadCountLocal = unreadCount this.threads.addAll(threads) @@ -151,61 +125,13 @@ class Folder : RealmObject, Cloneable, TreeStructure { this.isDisplayed = isDisplayed this.isCollapsed = isCollapsed - this.sortedName = this.name.lowercase().removeAccents() - } - - fun messagesBlocking(realm: TypedRealm): List = MessageController.getMessagesByFolderIdBlocking(id, realm) - - fun getLocalizedName(context: Context): String { - return role?.folderNameRes?.let(context::getString) ?: name - } - - @DrawableRes - fun getIcon(): Int { - return role?.folderIconRes ?: if (isFavorite) R.drawable.ic_folder_star else R.drawable.ic_folder + this.sortedName = sortedName } override fun equals(other: Any?) = other === this || (other is Folder && other.id == id) override fun hashCode(): Int = id.hashCode() - enum class FolderRole( - @StringRes val folderNameRes: Int, - @DrawableRes val folderIconRes: Int, - val order: Int, - val matomoName: MatomoName, - val refreshStrategy: RefreshStrategy = defaultRefreshStrategy, - val folderSort: FolderSort = FolderSort.Default, - val groupMessagesBySection: Boolean = true, - ) { - INBOX(R.string.inboxFolder, R.drawable.ic_drawer_inbox, 10, MatomoName.InboxFolder, inboxRefreshStrategy), - COMMERCIAL(R.string.commercialFolder, R.drawable.ic_promotions, 9, MatomoName.CommercialFolder), - SOCIALNETWORKS(R.string.socialNetworksFolder, R.drawable.ic_social_media, 8, MatomoName.SocialNetworksFolder), - SENT(R.string.sentFolder, R.drawable.ic_send, 7, MatomoName.SentFolder), - SNOOZED( - folderNameRes = R.string.snoozedFolder, - folderIconRes = R.drawable.ic_alarm_clock, - order = 6, - matomoName = MatomoName.SnoozedFolder, - refreshStrategy = snoozeRefreshStrategy, - folderSort = FolderSort.Snooze, - groupMessagesBySection = false, - ), - SCHEDULED_DRAFTS( - folderNameRes = R.string.scheduledMessagesFolder, - folderIconRes = R.drawable.ic_schedule_send, - order = 5, - matomoName = MatomoName.ScheduledDraftsFolder, - refreshStrategy = scheduledDraftRefreshStrategy, - folderSort = FolderSort.Scheduled, - groupMessagesBySection = false, - ), - DRAFT(R.string.draftFolder, R.drawable.ic_draft, 4, MatomoName.DraftFolder), - SPAM(R.string.spamFolder, R.drawable.ic_spam, 3, MatomoName.SpamFolder), - TRASH(R.string.trashFolder, R.drawable.ic_bin, 2, MatomoName.TrashFolder), - ARCHIVE(R.string.archiveFolder, R.drawable.ic_archive_folder, 1, MatomoName.ArchiveFolder), - } - class FolderSort private constructor( val sortOrder: Sort = Sort.DESCENDING, sortBy: KProperty1<*, *> = Thread::internalDate, @@ -223,7 +149,10 @@ class Folder : RealmObject, Cloneable, TreeStructure { val rolePropertyName = Folder::_role.name val parentsPropertyName = Folder::_parents.name + const val NUMBER_OF_OLD_MESSAGES_TO_FETCH = 500 // Number of Messages we want to fetch when 1st opening a Folder + const val DUMMY_FOLDER_ID = "eJzz9HPyjwAABGYBgQ--" // Fun fact: It's actually the INBOX folder id. But nobody cares. - private const val CUSTOM_FOLDER_ROLE_ORDER = 0 + const val CUSTOM_FOLDER_ROLE_ORDER = 0 + } } diff --git a/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/FolderRole.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/FolderRole.kt new file mode 100644 index 00000000000..96b6752cf47 --- /dev/null +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/FolderRole.kt @@ -0,0 +1,43 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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.data.models + +enum class FolderRole( + val order: Int, + val folderSort: Folder.FolderSort = Folder.FolderSort.Default, + val groupMessagesBySection: Boolean = true, +) { + INBOX(10), + COMMERCIAL(9), + SOCIALNETWORKS(8), + SENT(7), + SNOOZED( + order = 6, + folderSort = Folder.FolderSort.Snooze, + groupMessagesBySection = false, + ), + SCHEDULED_DRAFTS( + order = 5, + folderSort = Folder.FolderSort.Scheduled, + groupMessagesBySection = false, + ), + DRAFT(4), + SPAM(3), + TRASH(2), + ARCHIVE(1), +} diff --git a/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/InternalModelProperties.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/InternalModelProperties.kt new file mode 100644 index 00000000000..5b8b548b683 --- /dev/null +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/InternalModelProperties.kt @@ -0,0 +1,21 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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.data.models + +@RequiresOptIn("Formerly private fields that had to become public so members that became extensions can use them.") +annotation class InternalModelProperties diff --git a/app/src/main/java/com/infomaniak/mail/data/models/Quotas.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/Quotas.kt similarity index 78% rename from app/src/main/java/com/infomaniak/mail/data/models/Quotas.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/Quotas.kt index ca3df02c926..bf88ae7e734 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/Quotas.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/Quotas.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2026 Infomaniak Network SA + * Copyright (C) 2026 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 @@ -15,11 +15,10 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +@file:OptIn(InternalModelProperties::class) + package com.infomaniak.mail.data.models -import android.content.Context -import com.infomaniak.core.common.FormatterFileSize.formatShortFileSize -import com.infomaniak.mail.R import io.realm.kotlin.types.EmbeddedRealmObject import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -36,7 +35,8 @@ class Quotas : EmbeddedRealmObject { //region Local data (Transient) @Transient - private var maxStorage: Long? = null + @InternalModelProperties + var maxStorage: Long? = null //endregion val isFull: Boolean @@ -53,14 +53,6 @@ class Quotas : EmbeddedRealmObject { this.maxStorage = maxStorage } - fun getText(context: Context): String { - - val usedSize = context.formatShortFileSize(size) - val maxSize = maxStorage?.let { context.formatShortFileSize(it) } - - return context.getString(R.string.menuDrawerMailboxStorage, usedSize, maxSize) - } - fun getProgress(): Int? = maxStorage?.let { ceil(100.0f * size.toFloat() / it.toFloat()).toInt() } fun hasLimitedStorage(): Boolean = maxStorage?.takeIf { it > 0L } != null diff --git a/app/src/main/java/com/infomaniak/mail/data/models/Snoozable.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/Snoozable.kt similarity index 90% rename from app/src/main/java/com/infomaniak/mail/data/models/Snoozable.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/Snoozable.kt index f027948edf3..349ae444944 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/Snoozable.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/Snoozable.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -18,7 +18,6 @@ package com.infomaniak.mail.data.models import androidx.annotation.CallSuper -import com.infomaniak.mail.data.cache.mailboxContent.ThreadController import io.realm.kotlin.types.RealmInstant interface Snoozable { @@ -39,7 +38,7 @@ interface Snoozable { /** * Keep the snooze state condition of [isSnoozed] the same as - * the condition used in [ThreadController.Companion.isSnoozedState]. + * the condition used in [com.infomaniak.mail.data.cache.mailboxContent.ThreadController.Companion.isSnoozedState]. */ fun Snoozable.isSnoozed() = snoozeState == SnoozeState.Snoozed && snoozeEndDate != null && snoozeUuid != null diff --git a/app/src/main/java/com/infomaniak/mail/data/models/SnoozeState.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/SnoozeState.kt similarity index 89% rename from app/src/main/java/com/infomaniak/mail/data/models/SnoozeState.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/SnoozeState.kt index 16024193e99..e8b39c004c2 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/SnoozeState.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/SnoozeState.kt @@ -17,9 +17,9 @@ */ package com.infomaniak.mail.data.models -import com.infomaniak.core.common.utils.ApiEnum +import com.infomaniak.core.common.utils.MailApiEnum -enum class SnoozeState(override val apiValue: String) : ApiEnum { +enum class SnoozeState(override val apiValue: String) : MailApiEnum { Snoozed(apiValue = "snoozed"), // Has been snoozed and the snooze end time has not been reached yet Unsnoozed(apiValue = "unsnoozed"), // Used to be snoozed but the snooze end time has been reached and the message came back } diff --git a/app/src/main/java/com/infomaniak/mail/data/models/SwissTransferContainer.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/SwissTransferContainer.kt similarity index 96% rename from app/src/main/java/com/infomaniak/mail/data/models/SwissTransferContainer.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/SwissTransferContainer.kt index 4b5845f2c82..3fadbbd561d 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/SwissTransferContainer.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/SwissTransferContainer.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2024 Infomaniak Network SA + * Copyright (C) 2024-2026 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 diff --git a/app/src/main/java/com/infomaniak/mail/data/models/SwissTransferFile.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/SwissTransferFile.kt similarity index 81% rename from app/src/main/java/com/infomaniak/mail/data/models/SwissTransferFile.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/SwissTransferFile.kt index a93ac465fd2..7b349bb0123 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/SwissTransferFile.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/SwissTransferFile.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2024 Infomaniak Network SA + * Copyright (C) 2026 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 @@ -17,12 +17,10 @@ */ package com.infomaniak.mail.data.models -import android.content.Context import io.realm.kotlin.types.EmbeddedRealmObject import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient -import java.io.File import java.util.UUID @Serializable @@ -43,12 +41,6 @@ class SwissTransferFile : EmbeddedRealmObject, Attachable { override var localUuid: String = UUID.randomUUID().toString() //endregion - override fun hasUsableCache(context: Context, file: File?, userId: Int, mailboxId: Int) = false - - override fun isInlineCachedFile(context: Context) = false - - override fun getCacheFile(context: Context, userId: Int, mailboxId: Int) = File("") - fun initLocalValues(containerUuid: String) { resource = "/api/swisstransfer/containers/$containerUuid/files/$uuid" } diff --git a/app/src/main/java/com/infomaniak/mail/data/models/TreeStructure.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/TreeStructure.kt similarity index 100% rename from app/src/main/java/com/infomaniak/mail/data/models/TreeStructure.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/TreeStructure.kt diff --git a/app/src/main/java/com/infomaniak/mail/data/models/addressBook/AddressBook.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/addressBook/AddressBook.kt similarity index 97% rename from app/src/main/java/com/infomaniak/mail/data/models/addressBook/AddressBook.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/addressBook/AddressBook.kt index 7e80e398bbe..ae87c622a07 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/addressBook/AddressBook.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/addressBook/AddressBook.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2025 Infomaniak Network SA + * Copyright (C) 2026 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 diff --git a/app/src/main/java/com/infomaniak/mail/data/models/addressBook/AddressBooksResult.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/addressBook/AddressBooksResult.kt similarity index 100% rename from app/src/main/java/com/infomaniak/mail/data/models/addressBook/AddressBooksResult.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/addressBook/AddressBooksResult.kt diff --git a/app/src/main/java/com/infomaniak/mail/data/models/addressBook/ContactGroup.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/addressBook/ContactGroup.kt similarity index 96% rename from app/src/main/java/com/infomaniak/mail/data/models/addressBook/ContactGroup.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/addressBook/ContactGroup.kt index 8a0818bc9e1..b4a2225d9d1 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/addressBook/ContactGroup.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/addressBook/ContactGroup.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 diff --git a/app/src/main/java/com/infomaniak/mail/data/models/calendar/Attendee.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/calendar/Attendee.kt similarity index 73% rename from app/src/main/java/com/infomaniak/mail/data/models/calendar/Attendee.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/calendar/Attendee.kt index 20378b08015..bd15c9eca4f 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/calendar/Attendee.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/calendar/Attendee.kt @@ -15,15 +15,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +@file:OptIn(InternalModelProperties::class) + package com.infomaniak.mail.data.models.calendar import android.os.Parcel -import androidx.annotation.ColorRes -import androidx.annotation.DrawableRes import com.infomaniak.core.common.extensions.customReadBoolean import com.infomaniak.core.common.extensions.customWriteBoolean -import com.infomaniak.mail.MatomoMail.MatomoName -import com.infomaniak.mail.R +import com.infomaniak.mail.data.models.InternalModelProperties import com.infomaniak.mail.data.models.correspondent.Correspondent import io.realm.kotlin.types.EmbeddedRealmObject import io.realm.kotlin.types.annotations.Ignore @@ -43,13 +42,13 @@ class Attendee() : EmbeddedRealmObject, Correspondent { @SerialName("organizer") var isOrganizer: Boolean = false @SerialName("state") - private var _state: String = "" + @InternalModelProperties + var _state: String = "" //endregion - val state get() = AttendanceState.entries.firstOrNull { it.apiValue == _state } ?: AttendanceState.NEEDS_ACTION - - @delegate:Ignore - override val initials by lazy { computeInitials() } + @Ignore + @InternalModelProperties + override var cachedInitials: String? = null constructor(email: String, name: String, isOrganizer: Boolean, state: String) : this() { this.email = email @@ -58,22 +57,6 @@ class Attendee() : EmbeddedRealmObject, Correspondent { _state = state } - fun manuallyOverrideAttendanceState(newAttendanceState: AttendanceState) { - _state = newAttendanceState.apiValue - } - - enum class AttendanceState( - val apiValue: String, - @DrawableRes val icon: Int?, - @ColorRes val iconColor: Int?, - val matomoName: MatomoName?, - ) { - ACCEPTED("ACCEPTED", R.drawable.ic_check_rounded, R.color.greenSuccess, MatomoName.ReplyYes), - NEEDS_ACTION("NEEDS-ACTION", null, null, null), - TENTATIVE("TENTATIVE", R.drawable.ic_calendar_maybe, R.color.iconColorSecondaryText, MatomoName.ReplyMaybe), - DECLINED("DECLINED", R.drawable.ic_calendar_no, R.color.redDestructiveAction, MatomoName.ReplyNo), - } - override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/app/src/main/java/com/infomaniak/mail/data/models/calendar/CalendarEvent.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/calendar/CalendarEvent.kt similarity index 96% rename from app/src/main/java/com/infomaniak/mail/data/models/calendar/CalendarEvent.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/calendar/CalendarEvent.kt index f70bbdb9c40..ff1fb8d8a05 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/calendar/CalendarEvent.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/calendar/CalendarEvent.kt @@ -19,7 +19,7 @@ package com.infomaniak.mail.data.models.calendar -import com.infomaniak.core.legacy.utils.Utils +import com.infomaniak.core.common.extensions.enumValueOfOrNull import com.infomaniak.mail.data.api.CalendarRealmInstantSerializer import com.infomaniak.mail.utils.extensions.toRealmInstant import io.realm.kotlin.ext.realmListOf @@ -51,7 +51,7 @@ class CalendarEvent() : EmbeddedRealmObject { private var _status: String? = null //endregion - val status: CalendarEventStatus? get() = Utils.enumValueOfOrNull(_status) + val status: CalendarEventStatus? get() = enumValueOfOrNull(_status) constructor( id: Int, diff --git a/app/src/main/java/com/infomaniak/mail/data/models/calendar/CalendarEventResponse.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/calendar/CalendarEventResponse.kt similarity index 87% rename from app/src/main/java/com/infomaniak/mail/data/models/calendar/CalendarEventResponse.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/calendar/CalendarEventResponse.kt index e245ff15b3c..fe21b262384 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/calendar/CalendarEventResponse.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/calendar/CalendarEventResponse.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2024-2025 Infomaniak Network SA + * Copyright (C) 2024-2026 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 @@ -17,9 +17,9 @@ */ package com.infomaniak.mail.data.models.calendar -import com.infomaniak.core.legacy.utils.Utils +import com.infomaniak.core.common.extensions.enumValueOfOrNull +import com.infomaniak.mail.data.models.InternalModelProperties import com.infomaniak.mail.data.models.calendar.CalendarEvent.CalendarEventStatus -import com.infomaniak.mail.utils.extensions.isUserIn import io.realm.kotlin.types.EmbeddedRealmObject import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -48,19 +48,14 @@ class CalendarEventResponse() : EmbeddedRealmObject { this._attachmentEventMethod = attachmentEventMethod } - private val attachmentEventMethod: AttachmentEventMethod? - get() = Utils.enumValueOfOrNull(_attachmentEventMethod) + @InternalModelProperties + val attachmentEventMethod: AttachmentEventMethod? + get() = enumValueOfOrNull(_attachmentEventMethod) val calendarEvent get() = userStoredEvent ?: attachmentEvent val isCanceled get() = calendarEvent?.status == CalendarEventStatus.CANCELLED - fun isReplyAuthorized(): Boolean { - return (attachmentEventMethod == null || attachmentEventMethod == AttachmentEventMethod.REQUEST) - && !isCanceled - && calendarEvent?.attendees?.isUserIn() == true - } - fun hasAssociatedInfomaniakCalendarEvent(): Boolean = userStoredEvent != null fun hasAttachmentEvent() = attachmentEvent != null diff --git a/app/src/main/java/com/infomaniak/mail/data/models/correspondent/Contact.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/correspondent/Contact.kt similarity index 96% rename from app/src/main/java/com/infomaniak/mail/data/models/correspondent/Contact.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/correspondent/Contact.kt index cd9a7643f93..c84d3f6ee8f 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/correspondent/Contact.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/correspondent/Contact.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2024 Infomaniak Network SA + * Copyright (C) 2026 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 diff --git a/app/src/main/java/com/infomaniak/mail/data/models/correspondent/ContactAutocompletable.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/correspondent/ContactAutocompletable.kt similarity index 94% rename from app/src/main/java/com/infomaniak/mail/data/models/correspondent/ContactAutocompletable.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/correspondent/ContactAutocompletable.kt index b1241a1f915..da52c5cf8e6 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/correspondent/ContactAutocompletable.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/correspondent/ContactAutocompletable.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2026 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 diff --git a/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/correspondent/Correspondent.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/correspondent/Correspondent.kt new file mode 100644 index 00000000000..49b60a60932 --- /dev/null +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/correspondent/Correspondent.kt @@ -0,0 +1,31 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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.data.models.correspondent + +import android.os.Parcelable +import com.infomaniak.mail.data.models.InternalModelProperties + +interface Correspondent : Parcelable { + var email: String + var name: String + + @InternalModelProperties + var cachedInitials: String? + + fun getNameOrEmail(): String = name.ifBlank { email } +} diff --git a/app/src/main/java/com/infomaniak/mail/data/models/correspondent/MergedContact.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/correspondent/MergedContact.kt similarity index 94% rename from app/src/main/java/com/infomaniak/mail/data/models/correspondent/MergedContact.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/correspondent/MergedContact.kt index 204420f4f33..7a8ad83f69a 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/correspondent/MergedContact.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/correspondent/MergedContact.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2025 Infomaniak Network SA + * Copyright (C) 2026 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 @@ -15,11 +15,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + @file:UseSerializers(RealmListKSerializer::class) package com.infomaniak.mail.data.models.correspondent -import com.infomaniak.mail.data.api.ApiRoutes +import com.infomaniak.mail.data.models.InternalModelProperties import io.realm.kotlin.ext.realmListOf import io.realm.kotlin.serializers.RealmListKSerializer import io.realm.kotlin.types.RealmList @@ -50,8 +51,9 @@ class MergedContact() : RealmObject, Correspondent, ContactAutocompletable { var addressbookIds: RealmList = realmListOf() - @delegate:Ignore - override val initials by lazy { computeInitials() } + @Ignore + @InternalModelProperties + override var cachedInitials: String? = null var contactedTimes: Int? = null @@ -68,11 +70,12 @@ class MergedContact() : RealmObject, Correspondent, ContactAutocompletable { email: String, apiContact: Contact, comesFromApi: Boolean, + avatarUrl: String?, ) : this() { this.email = email this.name = apiContact.name - this.avatar = apiContact.avatar?.let { avatar -> ApiRoutes.resource(avatar) } + this.avatar = avatarUrl this.contactedTimes = apiContact.contactedTimes?.get(email) this.other = apiContact.other diff --git a/app/src/main/java/com/infomaniak/mail/data/models/correspondent/Recipient.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/correspondent/Recipient.kt similarity index 74% rename from app/src/main/java/com/infomaniak/mail/data/models/correspondent/Recipient.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/correspondent/Recipient.kt index af35d5f5bec..e9b293fa555 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/correspondent/Recipient.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/correspondent/Recipient.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2026 Infomaniak Network SA + * Copyright (C) 2026 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 @@ -20,8 +20,7 @@ package com.infomaniak.mail.data.models.correspondent import android.os.Parcel import com.infomaniak.core.common.extensions.customReadBoolean import com.infomaniak.core.common.extensions.customWriteBoolean -import com.infomaniak.mail.utils.ExternalUtils.ExternalData -import com.infomaniak.mail.utils.extensions.isEmail +import com.infomaniak.mail.data.models.InternalModelProperties import io.realm.kotlin.types.EmbeddedRealmObject import io.realm.kotlin.types.annotations.Ignore import kotlinx.parcelize.Parceler @@ -51,8 +50,9 @@ open class Recipient : EmbeddedRealmObject, Correspondent, ContactAutocompletabl var isManuallyEntered: Boolean = true //endregion - @delegate:Ignore - override val initials by lazy { computeInitials() } + @Ignore + @InternalModelProperties + override var cachedInitials: String? = null override var contactId = name + email @@ -68,17 +68,6 @@ open class Recipient : EmbeddedRealmObject, Correspondent, ContactAutocompletabl isDisplayedAsExternal = shouldDisplayAsExternal } - // Computes if the Recipient is external, according to the required conditions. - // Does not tell anything about how to display the Recipient chip when composing a new Message. - fun isExternal(externalData: ExternalData): Boolean = with(externalData) { - val isUnknownContact = email !in emailDictionary - val isAlias = email in aliases - val isUntrustedDomain = email.isEmail() && trustedDomains.none(email::endsWith) - val isMailerDaemon = """mailer-daemon@(?:.+\.)?infomaniak\.ch""".toRegex(RegexOption.IGNORE_CASE).matches(email) - - return@with isUnknownContact && !isAlias && isUntrustedDomain && !isMailerDaemon - } - fun quotedDisplayName(): String = "${("$name ").ifBlank { "" }}<$email>" fun quotedEmail(): String = "<$email>" @@ -103,13 +92,12 @@ open class Recipient : EmbeddedRealmObject, Correspondent, ContactAutocompletabl parcel.customWriteBoolean(hasExternalProvider) } - fun createValidRecipientOrNull( - email: String, + fun createValidRecipient( + syntacticallyValidEmail: String, name: String? = null, hasExternalProvider: Boolean? = null - ): Recipient? { - if (!email.isEmail()) return null - return Recipient().initLocalValues(email, name, hasExternalProvider) + ): Recipient { + return Recipient().initLocalValues(syntacticallyValidEmail, name, hasExternalProvider) } } } diff --git a/app/src/main/java/com/infomaniak/mail/data/models/draft/Draft.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/draft/Draft.kt similarity index 80% rename from app/src/main/java/com/infomaniak/mail/data/models/draft/Draft.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/draft/Draft.kt index 7352343a5dc..0eb1afa6c57 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/draft/Draft.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/draft/Draft.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2025 Infomaniak Network SA + * Copyright (C) 2022-2026 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 @@ -16,13 +16,12 @@ * along with this program. If not, see . */ @file:UseSerializers(RealmListKSerializer::class) +@file:OptIn(InternalModelProperties::class) package com.infomaniak.mail.data.models.draft -import com.infomaniak.core.legacy.utils.Utils.enumValueOfOrNull -import com.infomaniak.core.network.api.ApiController -import com.infomaniak.mail.MatomoMail.MatomoName import com.infomaniak.mail.data.models.Attachment +import com.infomaniak.mail.data.models.InternalModelProperties import com.infomaniak.mail.data.models.correspondent.Recipient import io.realm.kotlin.ext.realmListOf import io.realm.kotlin.serializers.RealmListKSerializer @@ -40,12 +39,6 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.encodeToJsonElement -import kotlinx.serialization.json.jsonObject import java.util.UUID @OptIn(ExperimentalSerializationApi::class) @@ -70,7 +63,8 @@ class Draft : RealmObject { var identityId: String? = null @SerialName("action") - private var _action: String? = null + @InternalModelProperties + var _action: String? = null @SerialName("in_reply_to") var inReplyTo: String? = null @@ -135,30 +129,11 @@ class Draft : RealmObject { var messageUid: String? = null //endregion - var action - get() = enumValueOfOrNull(_action) - set(value) { - _action = value?.apiCallValue - } - fun initLocalValues(messageUid: String? = null, mimeType: String? = null) { messageUid?.let { this.messageUid = it } mimeType?.let { this.mimeType = it } } - fun getJsonRequestBody(): MutableMap { - return draftJson.encodeToJsonElement(this).jsonObject.toMutableMap().apply { - this[Draft::attachments.name] = JsonArray(attachments.map { JsonPrimitive(it.uuid) }) - } - } - - enum class DraftAction(val apiCallValue: String, val matomoName: MatomoName) { - SAVE("save", MatomoName.SaveDraft), - SEND("send", MatomoName.SendMail), - SEND_REACTION("send_reaction", MatomoName.SendReaction), - SCHEDULE("schedule", MatomoName.ScheduleDraft), - } - enum class DraftMode { REPLY, REPLY_ALL, @@ -170,7 +145,6 @@ class Draft : RealmObject { const val NO_IDENTITY = -1 val actionPropertyName get() = Draft::_action.name - private val draftJson = Json(ApiController.json) { encodeDefaults = true } } } diff --git a/app/src/main/java/com/infomaniak/mail/data/models/mailbox/Mailbox.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/mailbox/Mailbox.kt similarity index 79% rename from app/src/main/java/com/infomaniak/mail/data/models/mailbox/Mailbox.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/mailbox/Mailbox.kt index ea70ad5a5ba..edf4ec64e49 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/mailbox/Mailbox.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/mailbox/Mailbox.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2026 Infomaniak Network SA + * Copyright (C) 2026 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 @@ -15,19 +15,16 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + @file:UseSerializers(RealmListKSerializer::class) package com.infomaniak.mail.data.models.mailbox -import androidx.core.app.NotificationManagerCompat -import com.infomaniak.core.ksuite.data.KSuite import com.infomaniak.mail.data.api.ZeroAsNullLongSerializer import com.infomaniak.mail.data.models.AppSettings import com.infomaniak.mail.data.models.FeatureFlag import com.infomaniak.mail.data.models.Quotas import com.infomaniak.mail.data.models.signature.Signature -import com.infomaniak.mail.utils.UnreadDisplay -import com.infomaniak.mail.utils.extensions.getDefault import io.realm.kotlin.ext.realmListOf import io.realm.kotlin.ext.realmSetOf import io.realm.kotlin.serializers.RealmListKSerializer @@ -121,28 +118,11 @@ class Mailbox : RealmObject { val featureFlags = FeatureFlagSet(::_featureFlags) //endregion - inline val kSuite: KSuite? - get() = when { - // For KSuite Pro tiers, only Free & Standard are relevant in kMail, all Pro paid tiers got the same functionalities - isKSuitePro && isKSuiteProFree -> KSuite.Pro.Free - isKSuitePro && !isKSuiteProFree -> KSuite.Pro.Standard - isKSuitePerso && isLimited -> KSuite.Perso.Free - isKSuitePerso && !isLimited -> KSuite.Perso.Plus - isPartOfStarterPack -> KSuite.StarterPack - else -> null // It's an older offer, but it checks out. - } - inline val channelGroupId get() = "$mailboxId" inline val channelId get() = "${mailboxId}_channel_id" inline val notificationGroupId get() = uuid.hashCode() inline val notificationGroupKey get() = uuid - val unreadCountDisplay: UnreadDisplay - get() = UnreadDisplay( - count = unreadCountLocal, - shouldDisplayPastille = unreadCountLocal == 0 && unreadCountRemote > 0, - ) - private fun createObjectId(userId: Int): String = "${userId}_${this.mailboxId}" fun initLocalValues( @@ -168,16 +148,6 @@ class Mailbox : RealmObject { sendersRestrictions?.let { this.sendersRestrictions = it } } - fun getDefaultSignatureWithFallback(): Signature { - return signatures.getDefault() ?: signatures.first() - } - - fun notificationsIsDisabled(notificationManagerCompat: NotificationManagerCompat): Boolean = with(notificationManagerCompat) { - val isGroupBlocked = getNotificationChannelGroupCompat(channelGroupId)?.isBlocked == true - val isChannelBlocked = getNotificationChannelCompat(channelId)?.importance == NotificationManagerCompat.IMPORTANCE_NONE - return@with !areNotificationsEnabled() || isGroupBlocked || isChannelBlocked - } - class FeatureFlagSet(val featureFlagsBacking: KMutableProperty0>) { fun contains(featureFlag: FeatureFlag): Boolean { return featureFlagsBacking.get().contains(featureFlag.apiName) diff --git a/app/src/main/java/com/infomaniak/mail/data/models/mailbox/MailboxPermissions.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/mailbox/MailboxPermissions.kt similarity index 96% rename from app/src/main/java/com/infomaniak/mail/data/models/mailbox/MailboxPermissions.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/mailbox/MailboxPermissions.kt index 311142000b6..82a47050184 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/mailbox/MailboxPermissions.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/mailbox/MailboxPermissions.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2023-2024 Infomaniak Network SA + * Copyright (C) 2026 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 diff --git a/app/src/main/java/com/infomaniak/mail/data/models/mailbox/SenderDetails.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/mailbox/SenderDetails.kt similarity index 95% rename from app/src/main/java/com/infomaniak/mail/data/models/mailbox/SenderDetails.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/mailbox/SenderDetails.kt index 439cf853f05..846eb4ed692 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/mailbox/SenderDetails.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/mailbox/SenderDetails.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2026 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 diff --git a/app/src/main/java/com/infomaniak/mail/data/models/mailbox/SendersRestrictions.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/mailbox/SendersRestrictions.kt similarity index 96% rename from app/src/main/java/com/infomaniak/mail/data/models/mailbox/SendersRestrictions.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/mailbox/SendersRestrictions.kt index 836ca641c26..1e030f397c7 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/mailbox/SendersRestrictions.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/mailbox/SendersRestrictions.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2026 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 diff --git a/app/src/main/java/com/infomaniak/mail/data/models/message/Body.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/Body.kt similarity index 96% rename from app/src/main/java/com/infomaniak/mail/data/models/message/Body.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/Body.kt index 4fabd8b98dc..09ffb872afd 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/message/Body.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/Body.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2024 Infomaniak Network SA + * Copyright (C) 2026 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 diff --git a/app/src/main/java/com/infomaniak/mail/data/models/message/EmojiReactionAuthor.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/EmojiReactionAuthor.kt similarity index 96% rename from app/src/main/java/com/infomaniak/mail/data/models/message/EmojiReactionAuthor.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/EmojiReactionAuthor.kt index 1bebc97cafe..9b7f17c3f62 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/message/EmojiReactionAuthor.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/EmojiReactionAuthor.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2026 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 diff --git a/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/EmojiReactionNotAllowedReason.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/EmojiReactionNotAllowedReason.kt new file mode 100644 index 00000000000..66a7e00935a --- /dev/null +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/EmojiReactionNotAllowedReason.kt @@ -0,0 +1,34 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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.data.models.message + +import com.infomaniak.core.common.utils.MailApiEnum + +enum class EmojiReactionNotAllowedReason( + override val apiValue: String, +) : MailApiEnum { + EmojiReactionFolderNotAllowedDraft("folder_not_allowed_draft"), + EmojiReactionFolderNotAllowedScheduledDraft("folder_not_allowed_scheduled_draft"), + EmojiReactionFolderNotAllowedSpam("folder_not_allowed_spam"), + EmojiReactionFolderNotAllowedTrash("folder_not_allowed_trash"), + EmojiReactionMessageInReplyToNotAllowed("message_in_reply_to_not_allowed"), + EmojiReactionMessageInReplyToNotValid("message_in_reply_to_not_valid"), + EmojiReactionMessageInReplyToEncrypted("message_in_reply_to_encrypted"), + EmojiReactionMaxRecipient("max_recipient"), + EmojiReactionRecipientNotAllowed("recipient_not_allowed"); +} diff --git a/app/src/main/java/com/infomaniak/mail/data/models/message/EmojiReactionState.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/EmojiReactionState.kt similarity index 86% rename from app/src/main/java/com/infomaniak/mail/data/models/message/EmojiReactionState.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/EmojiReactionState.kt index 607522fe11c..d4f1c47b92d 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/message/EmojiReactionState.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/EmojiReactionState.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2026 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 @@ -35,9 +35,4 @@ class EmojiReactionState constructor() : Reaction, EmbeddedRealmObject { constructor(emoji: String) : this() { this.emoji = emoji } - - fun addAuthor(newAuthor: EmojiReactionAuthor) { - authors.add(newAuthor) - if (hasReacted.not()) hasReacted = newAuthor.recipient?.isMe() == true - } } diff --git a/app/src/main/java/com/infomaniak/mail/data/models/message/Headers.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/Headers.kt similarity index 96% rename from app/src/main/java/com/infomaniak/mail/data/models/message/Headers.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/Headers.kt index b10f5554f6d..ad021a564a3 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/message/Headers.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/Headers.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2026 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 diff --git a/app/src/main/java/com/infomaniak/mail/data/models/message/Message.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/Message.kt similarity index 64% rename from app/src/main/java/com/infomaniak/mail/data/models/message/Message.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/Message.kt index 407175e14a6..e52cd193a5a 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/message/Message.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/Message.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2026 Infomaniak Network SA + * Copyright (C) 2026 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 @@ -15,36 +15,28 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + @file:UseSerializers(RealmListKSerializer::class, RealmInstantSerializer::class) package com.infomaniak.mail.data.models.message -import android.content.Context -import com.infomaniak.core.legacy.utils.Utils.enumValueOfOrNull -import com.infomaniak.core.common.utils.apiEnum -import com.infomaniak.mail.R +import com.infomaniak.core.common.extensions.enumValueOfOrNull +import com.infomaniak.core.common.utils.mailApiEnum import com.infomaniak.mail.data.api.RealmInstantSerializer import com.infomaniak.mail.data.api.UnwrappingJsonListSerializer -import com.infomaniak.mail.data.cache.mailboxContent.FolderController import com.infomaniak.mail.data.models.Attachment import com.infomaniak.mail.data.models.Bimi -import com.infomaniak.mail.data.models.Folder -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.InternalModelProperties import com.infomaniak.mail.data.models.Snoozable import com.infomaniak.mail.data.models.SnoozeState import com.infomaniak.mail.data.models.SwissTransferFile +import com.infomaniak.mail.data.models.thread.Thread import com.infomaniak.mail.data.models.calendar.CalendarEventResponse import com.infomaniak.mail.data.models.correspondent.Recipient -import com.infomaniak.mail.data.models.getMessages.DefaultMessageFlags -import com.infomaniak.mail.data.models.getMessages.SnoozeMessageFlags -import com.infomaniak.mail.data.models.thread.Thread -import com.infomaniak.mail.utils.AccountUtils -import com.infomaniak.mail.utils.MessageBodyUtils.SplitBody -import com.infomaniak.mail.utils.extensions.replaceContent +import com.infomaniak.mail.internal.replaceContent import com.infomaniak.mail.utils.extensions.toRealmInstant import io.realm.kotlin.ext.backlinks import io.realm.kotlin.ext.copyFromRealm -import io.realm.kotlin.ext.isManaged import io.realm.kotlin.ext.realmListOf import io.realm.kotlin.ext.realmSetOf import io.realm.kotlin.serializers.RealmListKSerializer @@ -52,18 +44,14 @@ import io.realm.kotlin.types.RealmInstant import io.realm.kotlin.types.RealmList import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.RealmSet -import io.realm.kotlin.types.TypedRealmObject import io.realm.kotlin.types.annotations.Ignore import io.realm.kotlin.types.annotations.PersistedName import io.realm.kotlin.types.annotations.PrimaryKey -import io.sentry.Sentry -import io.sentry.SentryLevel import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlinx.serialization.UseSerializers import java.util.Date -import kotlin.random.Random @Serializable class Message : RealmObject, Snoozable { @@ -147,7 +135,8 @@ class Message : RealmObject, Snoozable { var emojiReaction: String? = null @SerialName("emoji_reaction_not_allowed_reason") var _emojiReactionNotAllowedReason: String? = null - private set + @InternalModelProperties + set //endregion //region Local data (Transient) @@ -201,10 +190,10 @@ class Message : RealmObject, Snoozable { //endregion @Ignore - override var snoozeState: SnoozeState? by apiEnum(::_snoozeState) + override var snoozeState: SnoozeState? by mailApiEnum(::_snoozeState) @Ignore - var emojiReactionNotAllowedReason: EmojiReactionNotAllowedReason? by apiEnum(::_emojiReactionNotAllowedReason) + var emojiReactionNotAllowedReason: EmojiReactionNotAllowedReason? by mailApiEnum(::_emojiReactionNotAllowedReason) val isValidReactionTarget get() = _emojiReactionNotAllowedReason == null @@ -216,79 +205,8 @@ class Message : RealmObject, Snoozable { val allRecipients inline get() = listOf(*to.toTypedArray(), *cc.toTypedArray(), *bcc.toTypedArray()) - inline val folder: Folder - get() = run { - - // TODO: Remove the whole content of this `run` and replace it with this commented code when the parental issue is fixed - // val sameFolderThread = threads.singleOrNull { it.folderId == folderId } - // val searchFolderThread = threads.first { it.folderId == FolderController.SEARCH_FOLDER_ID } - // return@run (sameFolderThread ?: searchFolderThread).folder - - var correctFolder: Folder? - var reason: String? - - computeFolderAndReason(folderId).let { - correctFolder = it.first - reason = it.second - } - - if (correctFolder == null) { - computeFolderAndReason(FolderController.SEARCH_FOLDER_ID).let { - correctFolder = it.first - reason = it.second - } - } - - if (correctFolder == null) { - // TODO: As of 20/05/2025, this event is taking the Sentry server down because it's emitting way too often. - // So we temporarily set it to emit in only 2% of cases, while we are working on a fix. - if (Random.nextInt(0, 100) < 2) { - Sentry.captureMessage( - "Message doesn't have a parent Thread from its own Folder, it should not be possible", - SentryLevel.ERROR, - ) { scope -> - scope.setTag("issueType", reason ?: "null") // The `null` value is supposedly impossible - scope.setExtra("threadsUid", threads.joinToString { it.uid }) - scope.setExtra("threadsCount", "${threads.count()}") - scope.setExtra( - "threadsFolder", - "${threads.map { "role:[${it.folder.role?.name}] (folderId:[${it.folderId}] | folder.id:[${it.folder.id}])" }}", - ) - scope.setExtra("messageUid", uid) - scope.setExtra("folderId", folderId) - scope.setExtra("email", AccountUtils.currentMailboxEmail.toString()) - } - } - - correctFolder = (threads.firstOrNull { it.folderId == folderId } ?: threads.first()).folder - } - - return@run correctFolder - } - - fun isInSpamFolder() = folder.role == FolderRole.SPAM - - fun computeFolderAndReason(folderId: String): Pair { - - var correctFolder: Folder? = null - var reason: String? = null - - val list = threads.filter { it.folderId == folderId } - val count = list.count() - - when { - count < 1 -> reason = "no parent Thread from correct Folder" - count > 1 -> reason = "multiple same parent Threads" - else -> correctFolder = list.first().folder - } - - return correctFolder to reason - } - inline val sender get() = from.firstOrNull() - val calendarAttachment: Attachment? get() = if (isDraft) null else attachments.firstOrNull(Attachment::isCalendarEvent) - val dkimStatus: MessageDKIM get() = enumValueOfOrNull(_dkimStatus) ?: MessageDKIM.VALID enum class MessageDKIM { @@ -355,37 +273,6 @@ class Message : RealmObject, Snoozable { } } - private inline fun RealmList.detachedFromRealm(depth: UInt = UInt.MIN_VALUE): List { - return if (isManaged()) copyFromRealm(depth) else this - } - - fun getRecipientsForReplyTo(replyAll: Boolean = false): Pair, List> { - - fun cleanedFrom() = from.detachedFromRealm().filterNot { it.isMe() } - - val cleanedTo = to.detachedFromRealm().filterNot { it.isMe() } - val cleanedCc = cc.detachedFromRealm().filterNot { it.isMe() } - - var to = replyTo.detachedFromRealm().filterNot { it.isMe() }.ifEmpty { cleanedFrom() } - var cc = emptyList() - - if (to.isEmpty()) { - to = cleanedTo - } else if (replyAll) { - cc = cleanedTo - } - - if (to.isEmpty()) { - to = cleanedCc - } else if (replyAll) { - cc = cc + cleanedCc - } - - if (to.isEmpty()) to = from.detachedFromRealm() - - return to to cc - } - fun computeMessageIds(): RealmSet { fun String.ifNotBlank(completion: (String) -> Unit) { @@ -399,37 +286,6 @@ class Message : RealmObject, Snoozable { } } - fun updateFlags(flags: DefaultMessageFlags) { - isSeen = flags.isSeen - isFavorite = flags.isFavorite - isAnswered = flags.isAnswered - isForwarded = flags.isForwarded - isScheduledMessage = flags.isScheduledMessage - - if (flags.isSeen && snoozeState == SnoozeState.Unsnoozed) { - snoozeState = null - snoozeEndDate = null - snoozeUuid = null - } - } - - fun updateSnoozeFlags(flags: SnoozeMessageFlags) { - snoozeEndDate = flags.snoozeEndDate.toRealmInstant() - } - - fun getFormattedPreview(context: Context, totalUnseenReactionOnLastEmoji: Int = 0 ): FormatedPreview = when { - isEncrypted -> FormatedPreview.Encryption(context.getString(R.string.encryptedMessageHeader)) - isReaction && totalUnseenReactionOnLastEmoji > 1 -> FormatedPreview.Reaction(context.resources.getQuantityString( - R.plurals.previewMultiReaction, - (totalUnseenReactionOnLastEmoji - 1), - emojiReaction, - from.firstOrNull()?.name.orEmpty(), - (totalUnseenReactionOnLastEmoji - 1))) - isReaction -> FormatedPreview.Reaction(context.getString(R.string.previewReaction, from.first().name, emojiReaction)) - preview.isBlank() -> FormatedPreview.Empty(context.getString(R.string.noBodyDescription)) - else -> FormatedPreview.Body(preview.trim()) - } - fun hasUnreadContent() = !isSeen || emojiReactions.any { !it.isSeen } fun shouldBeExpanded(index: Int, lastIndex: Int) = !isDraft && (hasUnreadContent() || index == lastIndex) diff --git a/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/SplitBody.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/SplitBody.kt new file mode 100644 index 00000000000..8642c2543e7 --- /dev/null +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/SplitBody.kt @@ -0,0 +1,29 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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.data.models.message + +data class SplitBody( + val content: String, + val quote: String? = null, +) { + override fun equals(other: Any?): Boolean { + return other === this || (other is SplitBody && other.content == content && other.quote == quote) + } + + override fun hashCode(): Int = 31 * content.hashCode() + quote.hashCode() +} diff --git a/app/src/main/java/com/infomaniak/mail/data/models/message/SubBody.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/SubBody.kt similarity index 97% rename from app/src/main/java/com/infomaniak/mail/data/models/message/SubBody.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/SubBody.kt index cfaa094f067..697ff41359e 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/message/SubBody.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/message/SubBody.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2024 Infomaniak Network SA + * Copyright (C) 2026 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 @@ -15,6 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + @file:UseSerializers(RealmListKSerializer::class, RealmInstantSerializer::class) package com.infomaniak.mail.data.models.message diff --git a/app/src/main/java/com/infomaniak/mail/data/models/signature/Signature.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/signature/Signature.kt similarity index 74% rename from app/src/main/java/com/infomaniak/mail/data/models/signature/Signature.kt rename to OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/signature/Signature.kt index 78f37671768..74a9aaf95cc 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/signature/Signature.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/signature/Signature.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2024 Infomaniak Network SA + * Copyright (C) 2026 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 @@ -17,10 +17,6 @@ */ package com.infomaniak.mail.data.models.signature -import android.content.Context -import com.infomaniak.mail.R -import com.infomaniak.mail.data.models.draft.Draft -import com.infomaniak.mail.utils.AccountUtils import io.realm.kotlin.types.EmbeddedRealmObject import io.realm.kotlin.types.annotations.Ignore import kotlinx.serialization.SerialName @@ -59,18 +55,5 @@ class Signature : EmbeddedRealmObject { var isDummy: Boolean = false // The empty Signature to allow the User to not choose any Signature. //endregion - companion object { - - fun getDummySignature( - context: Context, - email: String = AccountUtils.currentMailboxEmail!!, - isDefault: Boolean = false, - ) = Signature().apply { - id = Draft.NO_IDENTITY - isDummy = true - name = context.getString(R.string.selectSignatureNone) - senderEmailIdn = email - this.isDefault = isDefault - } - } + companion object; } diff --git a/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/thread/Thread.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/thread/Thread.kt new file mode 100644 index 00000000000..9ae7c4c92d4 --- /dev/null +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/thread/Thread.kt @@ -0,0 +1,129 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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.data.models.thread + +import com.infomaniak.core.common.utils.mailApiEnum +import com.infomaniak.mail.data.models.Folder +import com.infomaniak.mail.data.models.Snoozable +import com.infomaniak.mail.data.models.SnoozeState +import com.infomaniak.mail.data.models.correspondent.Recipient +import com.infomaniak.mail.data.models.message.Message +import com.infomaniak.mail.utils.extensions.toRealmInstant +import io.realm.kotlin.ext.backlinks +import io.realm.kotlin.ext.realmListOf +import io.realm.kotlin.ext.realmSetOf +import io.realm.kotlin.types.RealmInstant +import io.realm.kotlin.types.RealmObject +import io.realm.kotlin.types.annotations.Ignore +import io.realm.kotlin.types.annotations.PrimaryKey +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import java.util.Date + +@Serializable +class Thread : RealmObject, Snoozable { + + //region Remote data + @PrimaryKey + var uid: String = "" + var messages = realmListOf() + // This value should always be provided because messages always have at least an internalDate. Because of this, the initial value is meaningless + @SerialName("internal_date") + var internalDate: RealmInstant = Date().toRealmInstant() + @SerialName("date") + var displayDate: RealmInstant = internalDate + @SerialName("unseen_messages") + var unseenMessagesCount: Int = 0 + var from = realmListOf() + var to = realmListOf() + var subject: String? = null + @SerialName("has_drafts") + var hasDrafts: Boolean = false + @SerialName("flagged") + var isFavorite: Boolean = false + @SerialName("answered") + var isAnswered: Boolean = false + @SerialName("forwarded") + var isForwarded: Boolean = false + @SerialName("snooze_state") + private var _snoozeState: String? = null + @SerialName("snooze_end_date") + override var snoozeEndDate: RealmInstant? = null + @SerialName("snooze_uuid") + override var snoozeUuid: String? = null + //endregion + + //region Local data (Transient) + @Transient + var folderId: String = "" + @Transient + var folderName: String = "" + @Transient + var duplicates = realmListOf() + @Transient + var messagesIds = realmSetOf() + @Transient + var isFromSearch: Boolean = false + @Transient + var hasAttachable: Boolean = false + // Has been moved (archived, spammed, deleted, moved) but API call hasn't been done yet. + // It's only used to locally filter the Threads' list. + @Transient + var isLocallyMovedOut: Boolean = false + // When deserializing threads from the api, this way of initializing the value will compute the correct + // numberOfScheduledDrafts right after deserialization + @Transient + var numberOfScheduledDrafts: Int = messages.count { it.isScheduledDraft } + @Transient + var isLastInboxMessageSnoozed: Boolean = false + + /** + * The list messages where messages that are emoji reactions have been filtered out + */ + @Transient + var messagesWithContent = realmListOf() + //endregion + + val isSeen get() = unseenMessagesCount == 0 + + @Ignore + override var snoozeState: SnoozeState? by mailApiEnum(::_snoozeState) + + // TODO: Put this back in `private` when the Threads parental issues are fixed + val _folders by backlinks(Folder::threads) + + val isOnlyOneDraft get() = messages.count() == 1 && hasDrafts + + /** + * Only used for when the api tells us we're trying to automatically unsnooze a thread that's not snoozed + */ + override fun manuallyUnsnooze() { + super.manuallyUnsnooze() + messages.forEach(Message::manuallyUnsnooze) + duplicates.forEach(Message::manuallyUnsnooze) + } + + override fun equals(other: Any?) = other === this || (other is Thread && other.uid == uid) + + override fun hashCode(): Int = uid.hashCode() + + companion object { + const val FORMAT_DAY_OF_THE_WEEK = "EEE" + } +} diff --git a/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/internal/RealmExtensions.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/internal/RealmExtensions.kt new file mode 100644 index 00000000000..cb9efc14ebf --- /dev/null +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/internal/RealmExtensions.kt @@ -0,0 +1,26 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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.internal + +import io.realm.kotlin.ext.toRealmList +import io.realm.kotlin.types.RealmList + +internal inline fun RealmList.replaceContent(list: List) { + clear() + addAll(list.toRealmList()) +} diff --git a/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/utils/extensions/RealmInstantConversion.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/utils/extensions/RealmInstantConversion.kt new file mode 100644 index 00000000000..982f7cee1db --- /dev/null +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/utils/extensions/RealmInstantConversion.kt @@ -0,0 +1,29 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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.extensions + +import io.realm.kotlin.types.RealmInstant +import java.util.Date + +fun RealmInstant.toDate(): Date = Date(epochSeconds * 1_000L + nanosecondsOfSecond / 1_000L) + +fun Date.toRealmInstant(): RealmInstant { + val seconds = time / 1_000L + val nanoseconds = (time - seconds * 1_000L).toInt() + return RealmInstant.from(seconds, nanoseconds) +} diff --git a/OldKotlin/settings.gradle.kts b/OldKotlin/settings.gradle.kts new file mode 100644 index 00000000000..9989c6e11f8 --- /dev/null +++ b/OldKotlin/settings.gradle.kts @@ -0,0 +1,24 @@ +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() +// mavenLocal() // Only used when we want to use a local version of a library (./gradlew publishToMavenLocal) + } +} + +include(":realm-models") +include(":emoji-reaction-models") diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 99fc8572d60..5d72634cad1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,4 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import java.util.Properties /** @@ -11,7 +12,6 @@ plugins { alias(core.plugins.kotlin.serialization) alias(core.plugins.ksp) alias(core.plugins.navigation.safeargs) - alias(libs.plugins.realm.kotlin) alias(core.plugins.compose.compiler) alias(core.plugins.kotlin.parcelize) alias(core.plugins.sentry.plugin) @@ -89,10 +89,6 @@ android { targetCompatibility = javaVersion } - kotlinOptions { - jvmTarget = javaVersion.majorVersion - } - buildFeatures { buildConfig = true viewBinding = true @@ -115,6 +111,7 @@ android { kotlin { compilerOptions { freeCompilerArgs.add("-Xjspecify-annotations=strict") + jvmTarget = JvmTarget.valueOf("JVM_${javaVersion.name.substringAfter("VERSION_")}") } } @@ -191,6 +188,7 @@ dependencies { implementation(core.infomaniak.core.ui.compose.materialthemefromxml) implementation(core.infomaniak.core.ui.compose.preview) implementation(core.infomaniak.core.ui.view) + implementation(libs.realm.models) implementation(project(":Core:Legacy")) implementation(project(":Core:Legacy:Confetti")) @@ -199,7 +197,6 @@ dependencies { implementation(libs.rich.html.editor) - implementation(libs.realm.kotlin.base) implementation(libs.junit.ktx) "standardImplementation"(core.infomaniak.core.notifications.registration) diff --git a/app/src/main/java/com/infomaniak/mail/MainApplication.kt b/app/src/main/java/com/infomaniak/mail/MainApplication.kt index a006b1fce27..cb59ad7cb72 100644 --- a/app/src/main/java/com/infomaniak/mail/MainApplication.kt +++ b/app/src/main/java/com/infomaniak/mail/MainApplication.kt @@ -45,6 +45,7 @@ import com.infomaniak.core.crossapplogin.back.internal.deviceinfo.DeviceInfoUpda import com.infomaniak.core.inappupdate.AppUpdateScheduler import com.infomaniak.core.legacy.utils.clearStack import com.infomaniak.core.legacy.utils.hasPermissions +import com.infomaniak.core.network.ApiEnvironment import com.infomaniak.core.network.NetworkConfiguration import com.infomaniak.core.network.networking.HttpClientConfig import com.infomaniak.core.sentry.SentryConfig.configureSentry @@ -235,6 +236,7 @@ open class MainApplication : Application(), SingletonImageLoader.Factory, Defaul appVersionCode = BuildConfig.VERSION_CODE, appVersionName = BuildConfig.VERSION_NAME, apiErrorCodes = ErrorCode.apiErrorCodes, + apiEnvironment = ApiEnvironment.Prod ) AuthConfiguration.init( diff --git a/app/src/main/java/com/infomaniak/mail/MatomoMail.kt b/app/src/main/java/com/infomaniak/mail/MatomoMail.kt index 76941753bad..120715257e2 100644 --- a/app/src/main/java/com/infomaniak/mail/MatomoMail.kt +++ b/app/src/main/java/com/infomaniak/mail/MatomoMail.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2025 Infomaniak Network SA + * Copyright (C) 2022-2026 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 @@ -26,7 +26,7 @@ import com.infomaniak.core.legacy.utils.capitalizeFirstChar import com.infomaniak.core.matomo.Matomo import com.infomaniak.core.matomo.Matomo.TrackerAction import com.infomaniak.mail.data.models.correspondent.Recipient -import com.infomaniak.mail.data.models.draft.Draft.DraftAction +import com.infomaniak.mail.data.models.draft.DraftAction import com.infomaniak.mail.ui.newMessage.NewMessageEditorManager.EditorAction import org.matomo.sdk.Tracker 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 c6889c47c0c..b6a65f8db82 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 @@ -53,7 +53,7 @@ import com.infomaniak.mail.data.models.ai.AiMessage import com.infomaniak.mail.data.models.ai.AiResult import com.infomaniak.mail.data.models.ai.ContextMessage import com.infomaniak.mail.data.models.ai.UserMessage -import com.infomaniak.mail.data.models.calendar.Attendee.AttendanceState +import com.infomaniak.mail.data.models.calendar.AttendanceState import com.infomaniak.mail.data.models.calendar.CalendarEvent import com.infomaniak.mail.data.models.calendar.CalendarEventResponse import com.infomaniak.mail.data.models.correspondent.Contact @@ -62,6 +62,8 @@ import com.infomaniak.mail.data.models.draft.Draft import com.infomaniak.mail.data.models.draft.SaveDraftResult import com.infomaniak.mail.data.models.draft.ScheduleDraftResult import com.infomaniak.mail.data.models.draft.SendDraftResult +import com.infomaniak.mail.data.models.extensions.computeFirstAndLastName +import com.infomaniak.mail.data.models.extensions.getJsonRequestBody import com.infomaniak.mail.data.models.getMessages.ActivitiesResult import com.infomaniak.mail.data.models.getMessages.GetMessagesByUidsResult import com.infomaniak.mail.data.models.getMessages.MessageFlags diff --git a/app/src/main/java/com/infomaniak/mail/data/api/ApiRoutes.kt b/app/src/main/java/com/infomaniak/mail/data/api/ApiRoutes.kt index 4546f494d5d..d2b21925250 100644 --- a/app/src/main/java/com/infomaniak/mail/data/api/ApiRoutes.kt +++ b/app/src/main/java/com/infomaniak/mail/data/api/ApiRoutes.kt @@ -22,6 +22,8 @@ import com.infomaniak.core.common.utils.format import com.infomaniak.core.network.INFOMANIAK_API_V1 import com.infomaniak.mail.MAIL_API import com.infomaniak.mail.utils.Utils +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf import java.net.URLEncoder import java.util.Date @@ -32,6 +34,11 @@ object ApiRoutes { return "$INFOMANIAK_API_V1/profile/password" } + suspend fun a () { + flowOf(0).first() + flowOf(0).first { true } + } + private fun mailboxV1(mailboxHostingId: Int, mailboxName: String): String { return "$INFOMANIAK_API_V1/mail_hostings/$mailboxHostingId/mailboxes/$mailboxName" } diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/FolderController.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/FolderController.kt index 48ebb639090..b57a7f77dad 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/FolderController.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/FolderController.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2025 Infomaniak Network SA + * Copyright (C) 2022-2026 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 @@ -18,12 +18,15 @@ package com.infomaniak.mail.data.cache.mailboxContent import android.content.Context +import com.infomaniak.core.legacy.utils.removeAccents import com.infomaniak.core.sentry.SentryLog import com.infomaniak.mail.data.cache.RealmDatabase import com.infomaniak.mail.data.models.Folder -import com.infomaniak.mail.data.models.Folder.FolderRole -import com.infomaniak.mail.data.models.mailbox.Mailbox +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.thread.Thread +import com.infomaniak.mail.data.models.extensions.messagesBlocking +import com.infomaniak.mail.data.models.mailbox.Mailbox +import com.infomaniak.mail.utils.SentryDebug import com.infomaniak.mail.utils.extensions.copyListToRealm import com.infomaniak.mail.utils.extensions.findSuspend import com.infomaniak.mail.utils.extensions.sortFolders @@ -119,6 +122,7 @@ class FolderController @Inject constructor( if (remoteFolder.role == FolderRole.SCHEDULED_DRAFTS && localFolder == null) remoteFolder.isDisplayed = false localFolder?.let { + SentryDebug.addCursorBreadcrumb("initLocalValues", folder = remoteFolder, it.cursor) remoteFolder.initLocalValues( it.lastUpdatedAt, it.cursor, @@ -129,6 +133,7 @@ class FolderController @Inject constructor( it.remainingOldMessagesToFetch, it.isDisplayed, it.isCollapsed, + sortedName = remoteFolder.name.lowercase().removeAccents(), ) } } diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ImpactedFolders.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ImpactedFolders.kt index c1972c37d4f..7ac27f50d71 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ImpactedFolders.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ImpactedFolders.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -17,7 +17,7 @@ */ package com.infomaniak.mail.data.cache.mailboxContent -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import io.realm.kotlin.Realm import io.realm.kotlin.TypedRealm diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/MessageController.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/MessageController.kt index 35a0751cf93..593209e6aa8 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/MessageController.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/MessageController.kt @@ -21,11 +21,13 @@ import android.content.Context import com.infomaniak.mail.data.LocalSettings import com.infomaniak.mail.data.cache.RealmDatabase import com.infomaniak.mail.data.models.correspondent.Recipient +import com.infomaniak.mail.data.models.extensions.action +import com.infomaniak.mail.data.models.extensions.getDisplayedMessages import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.data.models.message.Body import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.data.models.thread.Thread -import com.infomaniak.mail.data.models.thread.Thread.ThreadFilter +import com.infomaniak.mail.data.models.thread.ThreadFilter import com.infomaniak.mail.utils.AccountUtils import com.infomaniak.mail.utils.FeatureAvailability import com.infomaniak.mail.utils.LocalStorageUtils.deleteDraftUploadDir diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt index c898e78b517..eec9d72c409 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt @@ -32,7 +32,12 @@ import com.infomaniak.mail.data.cache.mailboxContent.refreshStrategies.RefreshSt import com.infomaniak.mail.data.cache.mailboxContent.refreshStrategies.ThreadRecomputations.recomputeThread import com.infomaniak.mail.data.cache.mailboxInfo.MailboxController import com.infomaniak.mail.data.models.Folder -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole +import com.infomaniak.mail.data.models.thread.Thread +import com.infomaniak.mail.data.models.extensions.messagesBlocking +import com.infomaniak.mail.data.models.extensions.refreshStrategy +import com.infomaniak.mail.data.models.extensions.updateFlags +import com.infomaniak.mail.data.models.extensions.updateSnoozeFlags import com.infomaniak.mail.data.models.getMessages.ActivitiesResult import com.infomaniak.mail.data.models.getMessages.DefaultMessageFlags import com.infomaniak.mail.data.models.getMessages.MessageFlags @@ -41,7 +46,6 @@ import com.infomaniak.mail.data.models.getMessages.SnoozeMessageFlags import com.infomaniak.mail.data.models.isSnoozeMalformed import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.data.models.message.Message -import com.infomaniak.mail.data.models.thread.Thread import com.infomaniak.mail.utils.ApiErrorException import com.infomaniak.mail.utils.ErrorCode import com.infomaniak.mail.utils.SentryDebug @@ -244,7 +248,7 @@ class RefreshController @Inject constructor( folder.unreadCountLocal = 0 folder.oldMessagesUidsToFetch.clear() folder.newMessagesUidsToFetch.clear() - folder.remainingOldMessagesToFetch = Utils.NUMBER_OF_OLD_MESSAGES_TO_FETCH + folder.remainingOldMessagesToFetch = Folder.NUMBER_OF_OLD_MESSAGES_TO_FETCH } } diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt index 4641d646e6b..d68d05e6a08 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt @@ -23,14 +23,14 @@ import com.infomaniak.core.sentry.SentryLog import com.infomaniak.mail.data.api.ApiRepository import com.infomaniak.mail.data.cache.RealmDatabase import com.infomaniak.mail.data.models.Folder -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.Snoozable import com.infomaniak.mail.data.models.SnoozeState import com.infomaniak.mail.data.models.SwissTransferContainer import com.infomaniak.mail.data.models.isSnoozed import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.data.models.thread.Thread -import com.infomaniak.mail.data.models.thread.Thread.ThreadFilter +import com.infomaniak.mail.data.models.thread.ThreadFilter import com.infomaniak.mail.utils.ErrorCode import com.infomaniak.mail.utils.SentryDebug import com.infomaniak.mail.utils.extensions.findSuspend diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/refreshStrategies/DefaultRefreshStrategy.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/refreshStrategies/DefaultRefreshStrategy.kt index abe0a9a4ab4..356e14205b7 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/refreshStrategies/DefaultRefreshStrategy.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/refreshStrategies/DefaultRefreshStrategy.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -23,7 +23,8 @@ import com.infomaniak.mail.data.cache.mailboxContent.ImpactedFolders import com.infomaniak.mail.data.cache.mailboxContent.MessageController import com.infomaniak.mail.data.cache.mailboxContent.ThreadController import com.infomaniak.mail.data.cache.mailboxContent.refreshStrategies.ThreadRecomputations.recomputeThread -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole +import com.infomaniak.mail.data.models.extensions.addMessageWithConditions import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.data.models.thread.Thread diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/refreshStrategies/InboxRefreshStrategy.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/refreshStrategies/InboxRefreshStrategy.kt index f39c2d77bf1..777222f06b6 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/refreshStrategies/InboxRefreshStrategy.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/refreshStrategies/InboxRefreshStrategy.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -19,7 +19,7 @@ package com.infomaniak.mail.data.cache.mailboxContent.refreshStrategies import com.infomaniak.mail.data.cache.mailboxContent.ImpactedFolders import com.infomaniak.mail.data.cache.mailboxContent.ThreadController -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.thread.Thread import io.realm.kotlin.TypedRealm diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/refreshStrategies/RefreshStrategy.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/refreshStrategies/RefreshStrategy.kt index 3c53ccf7009..f7c36666f1a 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/refreshStrategies/RefreshStrategy.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/refreshStrategies/RefreshStrategy.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -19,7 +19,7 @@ package com.infomaniak.mail.data.cache.mailboxContent.refreshStrategies import android.content.Context import com.infomaniak.mail.data.cache.mailboxContent.ImpactedFolders -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.data.models.thread.Thread diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/refreshStrategies/SnoozeRefreshStrategy.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/refreshStrategies/SnoozeRefreshStrategy.kt index 1d7efcaba87..cfdefd6d020 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/refreshStrategies/SnoozeRefreshStrategy.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/refreshStrategies/SnoozeRefreshStrategy.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -23,7 +23,7 @@ import com.infomaniak.mail.data.cache.mailboxContent.ImpactedFolders import com.infomaniak.mail.data.cache.mailboxContent.MessageController import com.infomaniak.mail.data.cache.mailboxContent.ThreadController import com.infomaniak.mail.data.cache.mailboxContent.refreshStrategies.ThreadRecomputations.recomputeThread -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.data.models.thread.Thread diff --git a/app/src/main/java/com/infomaniak/mail/data/models/Attachable.kt b/app/src/main/java/com/infomaniak/mail/data/models/Attachable.kt deleted file mode 100644 index 2de8a62b9e1..00000000000 --- a/app/src/main/java/com/infomaniak/mail/data/models/Attachable.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.data.models - -import android.content.Context -import androidx.annotation.DrawableRes -import com.infomaniak.core.legacy.utils.guessMimeType -import com.infomaniak.mail.R -import com.infomaniak.mail.data.api.ApiRoutes -import com.infomaniak.mail.utils.AccountUtils -import com.infomaniak.mail.utils.AttachableMimeTypeUtils -import com.infomaniak.mail.utils.Utils -import java.io.File - -interface Attachable { - - var size: Long - var name: String - var mimeType: String - var resource: String? - var localUuid: String - - val downloadUrl get() = ApiRoutes.resource(resource!!) - - val safeMimeType get() = if (mimeType == Utils.MIMETYPE_UNKNOWN) name.guessMimeType() else mimeType - - fun getFileTypeFromMimeType(): AttachmentType = AttachableMimeTypeUtils.getFileTypeFromMimeType(safeMimeType) - - fun hasUsableCache( - context: Context, - file: File? = null, - userId: Int = AccountUtils.currentUserId, - mailboxId: Int = AccountUtils.currentMailboxId, - ): Boolean - - fun isInlineCachedFile(context: Context): Boolean - - fun getCacheFile( - context: Context, - userId: Int = AccountUtils.currentUserId, - mailboxId: Int = AccountUtils.currentMailboxId, - ): File - - enum class AttachmentType(@DrawableRes val icon: Int) { - ARCHIVE(R.drawable.ic_file_zip), - AUDIO(R.drawable.ic_file_audio), - CALENDAR(R.drawable.ic_file_calendar), - CODE(R.drawable.ic_file_code), - FONT(R.drawable.ic_file_font), - IMAGE(R.drawable.ic_file_image), - PDF(R.drawable.ic_file_pdf), - POINTS(R.drawable.ic_file_office_graph), - SPREADSHEET(R.drawable.ic_file_office_sheet), - TEXT(R.drawable.ic_file_text), - VCARD(R.drawable.ic_file_vcard), - VIDEO(R.drawable.ic_file_video), - UNKNOWN(R.drawable.ic_file_unknown), - } -} diff --git a/app/src/main/java/com/infomaniak/mail/data/models/AttachmentType.kt b/app/src/main/java/com/infomaniak/mail/data/models/AttachmentType.kt new file mode 100644 index 00000000000..115506e22df --- /dev/null +++ b/app/src/main/java/com/infomaniak/mail/data/models/AttachmentType.kt @@ -0,0 +1,37 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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.data.models + +import androidx.annotation.DrawableRes +import com.infomaniak.mail.R + +enum class AttachmentType(@DrawableRes val icon: Int) { + ARCHIVE(R.drawable.ic_file_zip), + AUDIO(R.drawable.ic_file_audio), + CALENDAR(R.drawable.ic_file_calendar), + CODE(R.drawable.ic_file_code), + FONT(R.drawable.ic_file_font), + IMAGE(R.drawable.ic_file_image), + PDF(R.drawable.ic_file_pdf), + POINTS(R.drawable.ic_file_office_graph), + SPREADSHEET(R.drawable.ic_file_office_sheet), + TEXT(R.drawable.ic_file_text), + VCARD(R.drawable.ic_file_vcard), + VIDEO(R.drawable.ic_file_video), + UNKNOWN(R.drawable.ic_file_unknown), +} diff --git a/app/src/main/java/com/infomaniak/mail/data/models/SwipeAction.kt b/app/src/main/java/com/infomaniak/mail/data/models/SwipeAction.kt index 78619182fc8..8ba53f1a9a7 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/SwipeAction.kt +++ b/app/src/main/java/com/infomaniak/mail/data/models/SwipeAction.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -24,7 +24,6 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import com.infomaniak.mail.MatomoMail.MatomoName import com.infomaniak.mail.R -import com.infomaniak.mail.data.models.Folder.FolderRole import com.infomaniak.mail.utils.FeatureAvailability enum class SwipeAction( diff --git a/app/src/main/java/com/infomaniak/mail/data/models/SwipeDisplayBehavior.kt b/app/src/main/java/com/infomaniak/mail/data/models/SwipeDisplayBehavior.kt index 4222a45ee94..cc3d450dd8f 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/SwipeDisplayBehavior.kt +++ b/app/src/main/java/com/infomaniak/mail/data/models/SwipeDisplayBehavior.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -18,7 +18,6 @@ package com.infomaniak.mail.data.models import com.infomaniak.mail.data.LocalSettings -import com.infomaniak.mail.data.models.Folder.FolderRole import com.infomaniak.mail.data.models.mailbox.Mailbox.FeatureFlagSet fun interface SwipeDisplayBehavior { diff --git a/app/src/main/java/com/infomaniak/mail/data/models/calendar/AttendanceState.kt b/app/src/main/java/com/infomaniak/mail/data/models/calendar/AttendanceState.kt new file mode 100644 index 00000000000..9786e0c64f4 --- /dev/null +++ b/app/src/main/java/com/infomaniak/mail/data/models/calendar/AttendanceState.kt @@ -0,0 +1,35 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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.data.models.calendar + +import androidx.annotation.ColorRes +import androidx.annotation.DrawableRes +import com.infomaniak.mail.MatomoMail.MatomoName +import com.infomaniak.mail.R + +enum class AttendanceState( + val apiValue: String, + @DrawableRes val icon: Int?, + @ColorRes val iconColor: Int?, + val matomoName: MatomoName?, +) { + ACCEPTED("ACCEPTED", R.drawable.ic_check_rounded, R.color.greenSuccess, MatomoName.ReplyYes), + NEEDS_ACTION("NEEDS-ACTION", null, null, null), + TENTATIVE("TENTATIVE", R.drawable.ic_calendar_maybe, R.color.iconColorSecondaryText, MatomoName.ReplyMaybe), + DECLINED("DECLINED", R.drawable.ic_calendar_no, R.color.redDestructiveAction, MatomoName.ReplyNo), +} diff --git a/app/src/main/java/com/infomaniak/mail/data/models/correspondent/Correspondent.kt b/app/src/main/java/com/infomaniak/mail/data/models/correspondent/Correspondent.kt deleted file mode 100644 index 22c8d6be231..00000000000 --- a/app/src/main/java/com/infomaniak/mail/data/models/correspondent/Correspondent.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Infomaniak Mail - Android - * Copyright (C) 2022-2025 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.data.models.correspondent - -import android.content.Context -import android.os.Parcelable -import com.infomaniak.core.legacy.utils.firstOrEmpty -import com.infomaniak.mail.R -import com.infomaniak.mail.utils.AccountUtils -import com.infomaniak.mail.utils.extensions.getStartAndEndOfPlusEmail -import io.sentry.Sentry - -interface Correspondent : Parcelable { - var email: String - var name: String - - val initials: String - - fun isMe(): Boolean { - val userEmail = AccountUtils.currentMailboxEmail?.lowercase() - val correspondentEmail = email.lowercase() - - val isRealMe = userEmail == correspondentEmail - if (isRealMe) return true - - val (start, end) = userEmail.getStartAndEndOfPlusEmail() - val isPlusMe = correspondentEmail.startsWith(start) && correspondentEmail.endsWith(end) - return isPlusMe - } - - fun shouldDisplayUserAvatar(): Boolean = isMe() && email.lowercase() == AccountUtils.currentUser?.email?.lowercase() - - fun getNameOrEmail(): String = name.ifBlank { email } - - fun computeInitials(): String { - return runCatching { - val (firstName, lastName) = computeFirstAndLastName() - val first = firstName.removeControlAndPunctuation().ifBlank { firstName }.first() - val last = lastName.removeControlAndPunctuation().firstOrEmpty() - - return@runCatching "$first$last".uppercase() - }.getOrElse { exception -> - Sentry.captureException(exception) { scope -> - scope.setExtra("email", email) - scope.setExtra("name size", name.count().toString()) - scope.setExtra("name is blank", name.isBlank().toString()) - scope.setExtra("characters not letters", name.filterNot(Char::isLetter)) - } - - return@getOrElse "" - } - } - - fun computeFirstAndLastName(): Pair { - val words = getNameOrEmail().trim().replace(Regex("\\s+"), " ").split(" ", limit = 2) - - return when (words.count()) { - 0 -> "" to "" - 1 -> words.single() to "" - else -> words.first() to words.last() - } - } - - private fun String.removeControlAndPunctuation() = replace(Regex("\\p{Punct}|\\p{C}"), "") - - fun displayedName(context: Context): String = if (isMe()) context.getString(R.string.contactMe) else getNameOrEmail() -} diff --git a/app/src/main/java/com/infomaniak/mail/data/models/draft/DraftAction.kt b/app/src/main/java/com/infomaniak/mail/data/models/draft/DraftAction.kt new file mode 100644 index 00000000000..7d2548ef35b --- /dev/null +++ b/app/src/main/java/com/infomaniak/mail/data/models/draft/DraftAction.kt @@ -0,0 +1,27 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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.data.models.draft + +import com.infomaniak.mail.MatomoMail + +enum class DraftAction(val apiCallValue: String, val matomoName: MatomoMail.MatomoName) { + SAVE("save", MatomoMail.MatomoName.SaveDraft), + SEND("send", MatomoMail.MatomoName.SendMail), + SEND_REACTION("send_reaction", MatomoMail.MatomoName.SendReaction), + SCHEDULE("schedule", MatomoMail.MatomoName.ScheduleDraft), +} diff --git a/app/src/main/java/com/infomaniak/mail/data/models/extensions/CorrespondentExtensions.kt b/app/src/main/java/com/infomaniak/mail/data/models/extensions/CorrespondentExtensions.kt new file mode 100644 index 00000000000..d5961d24fac --- /dev/null +++ b/app/src/main/java/com/infomaniak/mail/data/models/extensions/CorrespondentExtensions.kt @@ -0,0 +1,82 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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 . + */ +@file:OptIn(InternalModelProperties::class) + +package com.infomaniak.mail.data.models.extensions + +import android.content.Context +import com.infomaniak.core.auth.firstOrEmpty +import com.infomaniak.mail.R +import com.infomaniak.mail.data.models.InternalModelProperties +import com.infomaniak.mail.data.models.correspondent.Correspondent +import com.infomaniak.mail.utils.AccountUtils +import com.infomaniak.mail.utils.extensions.getStartAndEndOfPlusEmail +import io.sentry.Sentry + +fun Correspondent.isMe(): Boolean { + val userEmail = AccountUtils.currentMailboxEmail?.lowercase() + val correspondentEmail = email.lowercase() + + val isRealMe = userEmail == correspondentEmail + if (isRealMe) return true + + val (start, end) = userEmail.getStartAndEndOfPlusEmail() + val isPlusMe = correspondentEmail.startsWith(start) && correspondentEmail.endsWith(end) + return isPlusMe +} + +fun Correspondent.shouldDisplayUserAvatar(): Boolean = isMe() && email.lowercase() == AccountUtils.currentUser?.email?.lowercase() + +val Correspondent.initials: String get() = cachedInitials ?: synchronized(this) { + cachedInitials ?: computeInitials().also { cachedInitials = it } +} + +fun Correspondent.computeInitials(): String { + return runCatching { + val (firstName, lastName) = computeFirstAndLastName() + val first = firstName.removeControlAndPunctuation().ifBlank { firstName }.first() + val last = lastName.removeControlAndPunctuation().firstOrEmpty() + + return@runCatching "$first$last".uppercase() + }.getOrElse { exception -> + Sentry.captureException(exception) { scope -> + scope.setExtra("email", email) + scope.setExtra("name size", name.count().toString()) + scope.setExtra("name is blank", name.isBlank().toString()) + scope.setExtra("characters not letters", name.filterNot(Char::isLetter)) + } + + return@getOrElse "" + } +} + +fun Correspondent.computeFirstAndLastName(): Pair { + val words = getNameOrEmail().trim().replace(Regex("\\s+"), " ").split(" ", limit = 2) + + return when (words.count()) { + 0 -> "" to "" + 1 -> words.single() to "" + else -> words.first() to words.last() + } +} + +fun Correspondent.displayedName(context: Context): String { + return if (isMe()) context.getString(R.string.contactMe) else getNameOrEmail() +} + +private fun String.removeControlAndPunctuation() = replace(Regex("\\p{Punct}|\\p{C}"), "") diff --git a/app/src/main/java/com/infomaniak/mail/data/models/extensions/MessageExtensions.kt b/app/src/main/java/com/infomaniak/mail/data/models/extensions/MessageExtensions.kt new file mode 100644 index 00000000000..ec7de9ea3c5 --- /dev/null +++ b/app/src/main/java/com/infomaniak/mail/data/models/extensions/MessageExtensions.kt @@ -0,0 +1,176 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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.data.models.extensions + +import android.content.Context +import com.infomaniak.mail.R +import com.infomaniak.mail.data.cache.mailboxContent.FolderController +import com.infomaniak.mail.data.models.Attachment +import com.infomaniak.mail.data.models.Folder +import com.infomaniak.mail.data.models.FolderRole +import com.infomaniak.mail.data.models.SnoozeState +import com.infomaniak.mail.data.models.correspondent.Recipient +import com.infomaniak.mail.data.models.getMessages.DefaultMessageFlags +import com.infomaniak.mail.data.models.getMessages.SnoozeMessageFlags +import com.infomaniak.mail.data.models.message.FormatedPreview +import com.infomaniak.mail.data.models.message.Message +import com.infomaniak.mail.utils.AccountUtils +import com.infomaniak.mail.utils.extensions.toRealmInstant +import io.realm.kotlin.ext.copyFromRealm +import io.realm.kotlin.ext.isManaged +import io.realm.kotlin.types.RealmList +import io.realm.kotlin.types.TypedRealmObject +import io.sentry.Sentry +import io.sentry.SentryLevel +import kotlin.random.Random + +val Message.folder: Folder + get() = run { + + // TODO: Remove the whole content of this `run` and replace it with this commented code when the parental issue is fixed + // val sameFolderThread = threads.singleOrNull { it.folderId == folderId } + // val searchFolderThread = threads.first { it.folderId == FolderController.SEARCH_FOLDER_ID } + // return@run (sameFolderThread ?: searchFolderThread).folder + + var correctFolder: Folder? + var reason: String? + + computeFolderAndReason(folderId).let { + correctFolder = it.first + reason = it.second + } + + if (correctFolder == null) { + computeFolderAndReason(FolderController.SEARCH_FOLDER_ID).let { + correctFolder = it.first + reason = it.second + } + } + + if (correctFolder == null) { + // TODO: As of 20/05/2025, this event is taking the Sentry server down because it's emitting way too often. + // So we temporarily set it to emit in only 2% of cases, while we are working on a fix. + if (Random.nextInt(0, 100) < 2) { + Sentry.captureMessage( + "Message doesn't have a parent Thread from its own Folder, it should not be possible", + SentryLevel.ERROR, + ) { scope -> + scope.setTag("issueType", reason ?: "null") // The `null` value is supposedly impossible + scope.setExtra("threadsUid", threads.joinToString { it.uid }) + scope.setExtra("threadsCount", "${threads.count()}") + scope.setExtra( + "threadsFolder", + "${threads.map { "role:[${it.folder.role?.name}] (folderId:[${it.folderId}] | folder.id:[${it.folder.id}])" }}", + ) + scope.setExtra("messageUid", uid) + scope.setExtra("folderId", folderId) + scope.setExtra("email", AccountUtils.currentMailboxEmail.toString()) + } + } + + correctFolder = (threads.firstOrNull { it.folderId == folderId } ?: threads.first()).folder + } + + return@run correctFolder + } + +fun Message.isInSpamFolder() = folder.role == FolderRole.SPAM + +fun Message.computeFolderAndReason(folderId: String): Pair { + + var correctFolder: Folder? = null + var reason: String? = null + + val list = threads.filter { it.folderId == folderId } + val count = list.count() + + when { + count < 1 -> reason = "no parent Thread from correct Folder" + count > 1 -> reason = "multiple same parent Threads" + else -> correctFolder = list.first().folder + } + + return correctFolder to reason +} + +val Message.calendarAttachment: Attachment? get() = if (isDraft) null else attachments.firstOrNull(Attachment::isCalendarEvent) + +fun Message.getFormattedPreview(context: Context, totalUnseenReactionOnLastEmoji: Int = 0 ): FormatedPreview = when { + isEncrypted -> FormatedPreview.Encryption(context.getString(R.string.encryptedMessageHeader)) + isReaction && totalUnseenReactionOnLastEmoji > 1 -> FormatedPreview.Reaction(context.resources.getQuantityString( + R.plurals.previewMultiReaction, + (totalUnseenReactionOnLastEmoji - 1), + emojiReaction, + from.firstOrNull()?.name.orEmpty(), + (totalUnseenReactionOnLastEmoji - 1))) + isReaction -> FormatedPreview.Reaction(context.getString(R.string.previewReaction, from.first().name, emojiReaction)) + preview.isBlank() -> FormatedPreview.Empty(context.getString(R.string.noBodyDescription)) + else -> FormatedPreview.Body(preview.trim()) +} + +fun Message.updateFlags(flags: DefaultMessageFlags) { + isSeen = flags.isSeen + isFavorite = flags.isFavorite + isAnswered = flags.isAnswered + isForwarded = flags.isForwarded + isScheduledMessage = flags.isScheduledMessage + + if (flags.isSeen && snoozeState == SnoozeState.Unsnoozed) { + snoozeState = null + snoozeEndDate = null + snoozeUuid = null + } +} + +fun Message.updateSnoozeFlags(flags: SnoozeMessageFlags) { + snoozeEndDate = flags.snoozeEndDate.toRealmInstant() +} + +fun Message.getRecipientsForReplyTo(replyAll: Boolean = false): Pair, List> { + + fun cleanedFrom() = from.detachedFromRealm(this).filterNot { it.isMe() } + + val cleanedTo = to.detachedFromRealm(this).filterNot { it.isMe() } + val cleanedCc = cc.detachedFromRealm(this).filterNot { it.isMe() } + + var to = replyTo.detachedFromRealm(this).filterNot { it.isMe() }.ifEmpty { cleanedFrom() } + var cc = emptyList() + + if (to.isEmpty()) { + to = cleanedTo + } else if (replyAll) { + cc = cleanedTo + } + + if (to.isEmpty()) { + to = cleanedCc + } else if (replyAll) { + cc = cc + cleanedCc + } + + if (to.isEmpty()) to = from.detachedFromRealm(this) + + return to to cc +} + +private inline fun RealmList.detachedFromRealm( + message: Message, + depth: UInt = UInt.MIN_VALUE +): List { + return if (message.isManaged()) copyFromRealm(depth) else this +} diff --git a/app/src/main/java/com/infomaniak/mail/data/models/extensions/ModelsExtensions.kt b/app/src/main/java/com/infomaniak/mail/data/models/extensions/ModelsExtensions.kt new file mode 100644 index 00000000000..6702df6294f --- /dev/null +++ b/app/src/main/java/com/infomaniak/mail/data/models/extensions/ModelsExtensions.kt @@ -0,0 +1,399 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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 . + */ +@file:OptIn(InternalModelProperties::class) + +package com.infomaniak.mail.data.models.extensions + +import android.content.Context +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.core.app.NotificationManagerCompat +import androidx.core.net.toFile +import androidx.core.net.toUri +import com.infomaniak.core.common.FormatterFileSize.formatShortFileSize +import com.infomaniak.core.common.utils.enumValueOfOrNull +import com.infomaniak.core.ksuite.data.KSuite +import com.infomaniak.core.legacy.utils.guessMimeType +import com.infomaniak.core.network.api.ApiController +import com.infomaniak.core.network.utils.ErrorCodeTranslated +import com.infomaniak.mail.MatomoMail +import com.infomaniak.mail.R +import com.infomaniak.mail.data.api.ApiRoutes +import com.infomaniak.mail.data.cache.mailboxContent.MessageController +import com.infomaniak.mail.data.cache.mailboxContent.refreshStrategies.RefreshStrategy +import com.infomaniak.mail.data.cache.mailboxContent.refreshStrategies.defaultRefreshStrategy +import com.infomaniak.mail.data.cache.mailboxContent.refreshStrategies.inboxRefreshStrategy +import com.infomaniak.mail.data.cache.mailboxContent.refreshStrategies.scheduledDraftRefreshStrategy +import com.infomaniak.mail.data.cache.mailboxContent.refreshStrategies.snoozeRefreshStrategy +import com.infomaniak.mail.data.models.Attachable +import com.infomaniak.mail.data.models.Attachment +import com.infomaniak.mail.data.models.AttachmentDisposition +import com.infomaniak.mail.data.models.AttachmentType +import com.infomaniak.mail.data.models.AttachmentUploadStatus +import com.infomaniak.mail.data.models.Folder +import com.infomaniak.mail.data.models.FolderRole +import com.infomaniak.mail.data.models.InternalModelProperties +import com.infomaniak.mail.data.models.Quotas +import com.infomaniak.mail.data.models.SwissTransferFile +import com.infomaniak.mail.data.models.calendar.AttendanceState +import com.infomaniak.mail.data.models.calendar.Attendee +import com.infomaniak.mail.data.models.calendar.CalendarEventResponse +import com.infomaniak.mail.data.models.calendar.CalendarEventResponse.AttachmentEventMethod +import com.infomaniak.mail.data.models.correspondent.Recipient +import com.infomaniak.mail.data.models.draft.Draft +import com.infomaniak.mail.data.models.draft.DraftAction +import com.infomaniak.mail.data.models.mailbox.Mailbox +import com.infomaniak.mail.data.models.message.EmojiReactionAuthor +import com.infomaniak.mail.data.models.message.EmojiReactionNotAllowedReason +import com.infomaniak.mail.data.models.message.EmojiReactionNotAllowedReason.EmojiReactionFolderNotAllowedDraft +import com.infomaniak.mail.data.models.message.EmojiReactionNotAllowedReason.EmojiReactionFolderNotAllowedScheduledDraft +import com.infomaniak.mail.data.models.message.EmojiReactionNotAllowedReason.EmojiReactionFolderNotAllowedSpam +import com.infomaniak.mail.data.models.message.EmojiReactionNotAllowedReason.EmojiReactionFolderNotAllowedTrash +import com.infomaniak.mail.data.models.message.EmojiReactionNotAllowedReason.EmojiReactionMaxRecipient +import com.infomaniak.mail.data.models.message.EmojiReactionNotAllowedReason.EmojiReactionMessageInReplyToEncrypted +import com.infomaniak.mail.data.models.message.EmojiReactionNotAllowedReason.EmojiReactionMessageInReplyToNotAllowed +import com.infomaniak.mail.data.models.message.EmojiReactionNotAllowedReason.EmojiReactionMessageInReplyToNotValid +import com.infomaniak.mail.data.models.message.EmojiReactionNotAllowedReason.EmojiReactionRecipientNotAllowed +import com.infomaniak.mail.data.models.message.EmojiReactionState +import com.infomaniak.mail.data.models.message.Message +import com.infomaniak.mail.data.models.signature.Signature +import com.infomaniak.mail.utils.AccountUtils +import com.infomaniak.mail.utils.AttachableMimeTypeUtils +import com.infomaniak.mail.utils.ExternalUtils.ExternalData +import com.infomaniak.mail.utils.LocalStorageUtils +import com.infomaniak.mail.utils.SentryDebug +import com.infomaniak.mail.utils.UnreadDisplay +import com.infomaniak.mail.utils.Utils +import com.infomaniak.mail.utils.extensions.getDefault +import com.infomaniak.mail.utils.extensions.isEmail +import com.infomaniak.mail.utils.extensions.isUserIn +import io.realm.kotlin.TypedRealm +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.encodeToJsonElement +import kotlinx.serialization.json.jsonObject +import java.io.File + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Attendee +/////////////////////////////////////////////////////////////////////////////////////////////////// + +val Attendee.state get() = AttendanceState.entries.firstOrNull { it.apiValue == _state } ?: AttendanceState.NEEDS_ACTION + +fun Attendee.manuallyOverrideAttendanceState(newAttendanceState: AttendanceState) { + _state = newAttendanceState.apiValue +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Recipient +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// Computes if the Recipient is external, according to the required conditions. +// Does not tell anything about how to display the Recipient chip when composing a new Message. +fun Recipient.isExternal(externalData: ExternalData): Boolean = with(externalData) { + val isUnknownContact = email !in emailDictionary + val isAlias = email in aliases + val isUntrustedDomain = email.isEmail() && trustedDomains.none(email::endsWith) + val isMailerDaemon = """mailer-daemon@(?:.+\.)?infomaniak\.ch""".toRegex(RegexOption.IGNORE_CASE).matches(email) + + return@with isUnknownContact && !isAlias && isUntrustedDomain && !isMailerDaemon +} + +fun Recipient.Companion.createValidRecipientOrNull( + email: String, + name: String? = null, + hasExternalProvider: Boolean? = null +): Recipient? { + if (!email.isEmail()) return null + return createValidRecipient( + syntacticallyValidEmail = email, + name = name, + hasExternalProvider = hasExternalProvider + ) +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Attachable +/////////////////////////////////////////////////////////////////////////////////////////////////// + +val Attachable.downloadUrl get() = ApiRoutes.resource(resource!!) + +val Attachable.safeMimeType get() = if (mimeType == Utils.MIMETYPE_UNKNOWN) name.guessMimeType() else mimeType + +fun Attachable.getFileTypeFromMimeType(): AttachmentType = AttachableMimeTypeUtils.getFileTypeFromMimeType(safeMimeType) + +fun Attachable.hasUsableCache( + context: Context, + file: File? = null, + userId: Int = AccountUtils.currentUserId, + mailboxId: Int = AccountUtils.currentMailboxId, +): Boolean = when (this) { + is Attachment -> { + val cachedFile = file ?: getCacheFile(context, userId, mailboxId) + cachedFile.length() > 0 && cachedFile.canRead() + } + is SwissTransferFile -> false +} + +fun Attachable.isInlineCachedFile(context: Context): Boolean = when (this) { + is Attachment -> getCacheFile(context).exists() && disposition == AttachmentDisposition.INLINE + is SwissTransferFile -> false +} + +fun Attachable.getCacheFile( + context: Context, + userId: Int = AccountUtils.currentUserId, + mailboxId: Int = AccountUtils.currentMailboxId, +): File = when (this) { + is Attachment -> { + val cacheFolder = LocalStorageUtils.getAttachmentsCacheDir(context, extractPathFromResource(), userId, mailboxId) + File(cacheFolder, name) + } + is SwissTransferFile -> File("") +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Attachment +/////////////////////////////////////////////////////////////////////////////////////////////////// + +val Attachment.isCalendarEvent: Boolean get() = AttachableMimeTypeUtils.calendarMatches.contains(mimeType) + +fun Attachment.setUploadStatus(attachmentUploadStatus: AttachmentUploadStatus, draft: Draft? = null, step: String = "") { + draft?.let { SentryDebug.addDraftBreadcrumbs(it, step) } + _uploadStatus = attachmentUploadStatus.name +} + +/** + * After uploading an Attachment, we replace the local version with the remote one. + * The remote one doesn't know about local data, so we have to backup them. + */ +fun Attachment.backupLocalData(oldAttachment: Attachment, draft: Draft) { + localUuid = oldAttachment.localUuid + uploadLocalUri = oldAttachment.uploadLocalUri + setUploadStatus(AttachmentUploadStatus.UPLOADED, draft, "backupLocalData -> setUploadStatus") +} + +fun Attachment.getUploadLocalFile() = uploadLocalUri?.toUri()?.toFile() + +private fun Attachment.extractPathFromResource(): String { + return resource?.substringAfter("folder/")?.replace(Regex("(message|attachment)/"), "") ?: "" +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Signature +/////////////////////////////////////////////////////////////////////////////////////////////////// + +fun Signature.Companion.getDummySignature( + context: Context, + email: String = AccountUtils.currentMailboxEmail!!, + isDefault: Boolean = false, +) = Signature().apply { + id = Draft.NO_IDENTITY + isDummy = true + name = context.getString(R.string.selectSignatureNone) + senderEmailIdn = email + this.isDefault = isDefault +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Quotas +/////////////////////////////////////////////////////////////////////////////////////////////////// + +fun Quotas.getText(context: Context): String { + + val usedSize = context.formatShortFileSize(size) + val maxSize = maxStorage?.let { context.formatShortFileSize(it) } + + return context.getString(R.string.menuDrawerMailboxStorage, usedSize, maxSize) +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Mailbox +/////////////////////////////////////////////////////////////////////////////////////////////////// + +inline val Mailbox.kSuite: KSuite? + get() = when { + // For KSuite Pro tiers, only Free & Standard are relevant in kMail, all Pro paid tiers got the same functionalities + isKSuitePro && isKSuiteProFree -> KSuite.Pro.Free + isKSuitePro && !isKSuiteProFree -> KSuite.Pro.Standard + isKSuitePerso && isLimited -> KSuite.Perso.Free + isKSuitePerso && !isLimited -> KSuite.Perso.Plus + isPartOfStarterPack -> KSuite.StarterPack + else -> null // It's an older offer, but it checks out. + } + +fun Mailbox.notificationsIsDisabled(notificationManagerCompat: NotificationManagerCompat): Boolean = with(notificationManagerCompat) { + val isGroupBlocked = getNotificationChannelGroupCompat(channelGroupId)?.isBlocked == true + val isChannelBlocked = getNotificationChannelCompat(channelId)?.importance == NotificationManagerCompat.IMPORTANCE_NONE + return@with !areNotificationsEnabled() || isGroupBlocked || isChannelBlocked +} + +val Mailbox.unreadCountDisplay: UnreadDisplay + get() = UnreadDisplay( + count = unreadCountLocal, + shouldDisplayPastille = unreadCountLocal == 0 && unreadCountRemote > 0, + ) + +fun Mailbox.getDefaultSignatureWithFallback(): Signature { + return signatures.getDefault() ?: signatures.first() +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Draft +/////////////////////////////////////////////////////////////////////////////////////////////////// + +var Draft.action + get() = enumValueOfOrNull(_action) + set(value) { + _action = value?.apiCallValue + } + +fun Draft.getJsonRequestBody(): MutableMap { + return draftJson.encodeToJsonElement(this).jsonObject.toMutableMap().apply { + this[Draft::attachments.name] = JsonArray(attachments.map { JsonPrimitive(it.uuid) }) + } +} + +private val draftJson = Json(ApiController.json) { encodeDefaults = true } + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Folder +/////////////////////////////////////////////////////////////////////////////////////////////////// + +fun Folder.messagesBlocking(realm: TypedRealm): List = MessageController.getMessagesByFolderIdBlocking(id, realm) + +@DrawableRes +fun Folder.getIcon(): Int { + return role?.folderIconRes ?: if (isFavorite) R.drawable.ic_folder_star else R.drawable.ic_folder +} + +val Folder.unreadCountDisplay: UnreadDisplay + inline get() = UnreadDisplay( + count = unreadCountLocal, + shouldDisplayPastille = unreadCountLocal == 0 && unreadCountRemote > 0, + ) + +val Folder.refreshStrategy: RefreshStrategy get() = role?.refreshStrategy ?: defaultRefreshStrategy + +fun Folder.getLocalizedName(context: Context): String { + return role?.folderNameRes?.let(context::getString) ?: name +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// EmojiReactionNotAllowedReason +/////////////////////////////////////////////////////////////////////////////////////////////////// + +fun EmojiReactionNotAllowedReason.getTranslateRes(): Int = when (this) { + EmojiReactionFolderNotAllowedDraft -> R.string.errorEmojiReactionFolderNotAllowedDraft + EmojiReactionFolderNotAllowedScheduledDraft -> R.string.errorEmojiReactionFolderNotAllowedScheduledDraft + EmojiReactionFolderNotAllowedSpam -> R.string.errorEmojiReactionFolderNotAllowedSpam + EmojiReactionFolderNotAllowedTrash -> R.string.errorEmojiReactionFolderNotAllowedTrash + EmojiReactionMessageInReplyToNotAllowed -> R.string.errorEmojiReactionMessageInReplyToNotAllowed + EmojiReactionMessageInReplyToNotValid -> R.string.errorEmojiReactionMessageInReplyToNotValid + EmojiReactionMessageInReplyToEncrypted -> R.string.errorEmojiReactionMessageInReplyEncrypted + EmojiReactionMaxRecipient -> R.string.errorEmojiReactionMaxRecipient + EmojiReactionRecipientNotAllowed -> R.string.errorEmojiReactionRecipientNotAllowed +} + +fun EmojiReactionNotAllowedReason.asErrorCodeTranslated(): ErrorCodeTranslated = object : ErrorCodeTranslated { + override val code: String = "emoji_reaction__$apiValue" + override val translateRes: Int = getTranslateRes() +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// CalendarEventResponse +/////////////////////////////////////////////////////////////////////////////////////////////////// + +fun CalendarEventResponse.isReplyAuthorized(): Boolean { + return (attachmentEventMethod == null || attachmentEventMethod == AttachmentEventMethod.REQUEST) + && !isCanceled + && calendarEvent?.attendees?.isUserIn() == true +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// EmojiReactionState +/////////////////////////////////////////////////////////////////////////////////////////////////// + +fun EmojiReactionState.addAuthor(newAuthor: EmojiReactionAuthor) { + authors.add(newAuthor) + if (hasReacted.not()) hasReacted = newAuthor.recipient?.isMe() == true +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// FolderRole +/////////////////////////////////////////////////////////////////////////////////////////////////// + +val FolderRole.folderNameRes: Int + @StringRes + get() = when (this) { + FolderRole.INBOX -> R.string.inboxFolder + FolderRole.COMMERCIAL -> R.string.commercialFolder + FolderRole.SOCIALNETWORKS -> R.string.socialNetworksFolder + FolderRole.SENT -> R.string.sentFolder + FolderRole.SNOOZED -> R.string.snoozedFolder + FolderRole.SCHEDULED_DRAFTS -> R.string.scheduledMessagesFolder + FolderRole.DRAFT -> R.string.draftFolder + FolderRole.SPAM -> R.string.spamFolder + FolderRole.TRASH -> R.string.trashFolder + FolderRole.ARCHIVE -> R.string.archiveFolder + } + +val FolderRole.folderIconRes: Int + @DrawableRes + get() = when (this) { + FolderRole.INBOX -> R.drawable.ic_drawer_inbox + FolderRole.COMMERCIAL -> R.drawable.ic_promotions + FolderRole.SOCIALNETWORKS -> R.drawable.ic_social_media + FolderRole.SENT -> R.drawable.ic_send + FolderRole.SNOOZED -> R.drawable.ic_alarm_clock + FolderRole.SCHEDULED_DRAFTS -> R.drawable.ic_schedule_send + FolderRole.DRAFT -> R.drawable.ic_draft + FolderRole.SPAM -> R.drawable.ic_spam + FolderRole.TRASH -> R.drawable.ic_bin + FolderRole.ARCHIVE -> R.drawable.ic_archive_folder + } + +val FolderRole.matomoName: MatomoMail.MatomoName + get() = when (this) { + FolderRole.INBOX -> MatomoMail.MatomoName.InboxFolder + FolderRole.COMMERCIAL -> MatomoMail.MatomoName.CommercialFolder + FolderRole.SOCIALNETWORKS -> MatomoMail.MatomoName.SocialNetworksFolder + FolderRole.SENT -> MatomoMail.MatomoName.SentFolder + FolderRole.SNOOZED -> MatomoMail.MatomoName.SnoozedFolder + FolderRole.SCHEDULED_DRAFTS -> MatomoMail.MatomoName.ScheduledDraftsFolder + FolderRole.DRAFT -> MatomoMail.MatomoName.DraftFolder + FolderRole.SPAM -> MatomoMail.MatomoName.SpamFolder + FolderRole.TRASH -> MatomoMail.MatomoName.TrashFolder + FolderRole.ARCHIVE -> MatomoMail.MatomoName.ArchiveFolder + } + +val FolderRole.refreshStrategy: RefreshStrategy + get() = when (this) { + FolderRole.INBOX -> inboxRefreshStrategy + FolderRole.COMMERCIAL -> defaultRefreshStrategy + FolderRole.SOCIALNETWORKS -> defaultRefreshStrategy + FolderRole.SENT -> defaultRefreshStrategy + FolderRole.SNOOZED -> snoozeRefreshStrategy + FolderRole.SCHEDULED_DRAFTS -> scheduledDraftRefreshStrategy + FolderRole.DRAFT -> defaultRefreshStrategy + FolderRole.SPAM -> defaultRefreshStrategy + FolderRole.TRASH -> defaultRefreshStrategy + FolderRole.ARCHIVE -> defaultRefreshStrategy + } diff --git a/app/src/main/java/com/infomaniak/mail/data/models/extensions/ThreadExtensions.kt b/app/src/main/java/com/infomaniak/mail/data/models/extensions/ThreadExtensions.kt new file mode 100644 index 00000000000..187a659bdd9 --- /dev/null +++ b/app/src/main/java/com/infomaniak/mail/data/models/extensions/ThreadExtensions.kt @@ -0,0 +1,170 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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.data.models.extensions + +import android.content.Context +import com.infomaniak.mail.data.LocalSettings +import com.infomaniak.mail.data.cache.mailboxContent.FolderController +import com.infomaniak.mail.data.models.Bimi +import com.infomaniak.mail.data.models.FolderRole +import com.infomaniak.mail.data.models.correspondent.Recipient +import com.infomaniak.mail.data.models.isSnoozed +import com.infomaniak.mail.data.models.isUnsnoozed +import com.infomaniak.mail.data.models.mailbox.Mailbox +import com.infomaniak.mail.data.models.message.Message +import com.infomaniak.mail.data.models.thread.Thread +import com.infomaniak.mail.ui.main.folder.ThreadListDateDisplay +import com.infomaniak.mail.utils.AccountUtils +import com.infomaniak.mail.utils.FeatureAvailability +import io.realm.kotlin.TypedRealm +import io.realm.kotlin.types.RealmList +import io.sentry.Sentry +import io.sentry.SentryLevel +import kotlin.random.Random + +// TODO: Remove this `runCatching / getOrElse` when the Threads parental issues are fixed +val Thread.folder + get() = runCatching { + // The only situation were we can have more than 1 parent folder is if the parent folders + // are 2 with this exact situation : 1x any real folder and 1x the Search folder. + if (_folders.count() == 2) { + _folders.single { it.id != FolderController.SEARCH_FOLDER_ID } + } else { + _folders.single() + } + }.getOrElse { exception -> + val reason = if (_folders.isEmpty()) { + "no parents" // Thread has 0 parent folders + } else { + val allFoldersAreSearch = _folders.all { it.id == FolderController.SEARCH_FOLDER_ID } + val allFoldersAreTheSame = _folders.all { it.id == _folders.firstOrNull()?.id } + when { + allFoldersAreSearch -> "multiple SEARCH folder" // Thread has multiple times the Search folder as parent + allFoldersAreTheSame -> "multiple same parent" // Thread has multiple times the same parent folder + else -> "multiple parents" // Thread has multiple parent folders + } + } + // TODO: As of 20/05/2025, this event is taking the Sentry server down because it's emitting way too often. + // So we temporarily set it to emit in only 2% of cases, while we are working on a fix. + if (Random.nextInt(0, 100) < 2) { + Sentry.captureMessage( + "Thread doesn't have a unique parent Folder, it should not be possible", + SentryLevel.ERROR, + ) { scope -> + scope.setTag("issueType", reason) + scope.setTag("foldersId", _folders.joinToString { it.id }) + scope.setTag("foldersCount", "${_folders.count()}") + scope.setExtra("folders_", "${_folders.map { "role:[${it.role?.name}] (id:[${it.id}])" }}") + scope.setExtra("foldersCount_", "${_folders.count()}") + scope.setExtra("threadUid", uid) + scope.setExtra("email", AccountUtils.currentMailboxEmail.toString()) + scope.setExtra("exception", exception.message.toString()) + } + } + + return@getOrElse _folders.firstOrNull { uid.contains(it.id) } ?: _folders.first() + } + +fun Thread.getDisplayedMessages(featureFlags: Mailbox.FeatureFlagSet?, localSettings: LocalSettings): RealmList { + return if (FeatureAvailability.isReactionsAvailable(featureFlags, localSettings)) messagesWithContent else messages +} + +fun Thread.addMessageWithConditions(newMessage: Message, realm: TypedRealm) { + + val shouldAddMessage = when (FolderController.getFolderBlocking(folderId, realm)?.role) { + FolderRole.DRAFT -> newMessage.isDraft // In Draft folder: only add draft Messages. + FolderRole.SCHEDULED_DRAFTS -> newMessage.isScheduledDraft // In ScheduledDrafts folder: only add scheduled Messages. + FolderRole.TRASH -> newMessage.isTrashed // In Trash folder: only add deleted Messages. + else -> !newMessage.isTrashed // In other folders: only add non-deleted Messages. + } + + if (shouldAddMessage) { + val twinMessage = messages.firstOrNull { it.messageId == newMessage.messageId } + if (twinMessage == null) { + messages.add(newMessage) + } else { + addDuplicatedMessage(twinMessage, newMessage) + } + } +} + +private fun Thread.addDuplicatedMessage(twinMessage: Message, newMessage: Message) { + val isTwinTheRealMessage = twinMessage.folderId == folderId + if (isTwinTheRealMessage) { + duplicates.add(newMessage) + } else { + messages.remove(twinMessage) + duplicates.add(twinMessage) + messages.add(newMessage) + } +} + +fun Thread.containsOnlyScheduledDrafts(featureFlags: Mailbox.FeatureFlagSet?, localSettings: LocalSettings): Boolean { + return getDisplayedMessages(featureFlags, localSettings).count() == numberOfScheduledDrafts +} + +fun Thread.computeThreadListDateDisplay(featureFlags: Mailbox.FeatureFlagSet?, localSettings: LocalSettings) = when { + containsOnlyScheduledDrafts(featureFlags, localSettings) -> ThreadListDateDisplay.Scheduled + isSnoozed() -> ThreadListDateDisplay.Snoozed + isUnsnoozed() -> ThreadListDateDisplay.Unsnoozed + else -> ThreadListDateDisplay.Default +} + +fun Thread.computePreview(context: Context): String { + val message = if (folder.role == FolderRole.SENT) { + messages.lastOrNull { it.folderId == folderId } ?: messages.last() + } else { + messages.last() + } + + return message.getFormattedPreview(context).content +} + +fun Thread.computeAvatarRecipient(): Pair = runCatching { + val message = messages.lastOrNull { + it.folder.role != FolderRole.SENT && + it.folder.role != FolderRole.DRAFT && + it.folder.role != FolderRole.SCHEDULED_DRAFTS + } ?: messages.last() + + val recipients = when (message.folder.role) { + FolderRole.SENT, FolderRole.DRAFT, FolderRole.SCHEDULED_DRAFTS -> message.to + else -> message.from + } + + return@runCatching recipients.firstOrNull() to message.bimi + +}.getOrElse { throwable -> + Sentry.captureException(throwable) { scope -> + scope.setExtra("thread.folder.role", folder.role?.name.toString()) + scope.setExtra("thread.folder.id", folder.id) + scope.setExtra("thread.folderId", folderId) + scope.setExtra("thread.uid", uid) + scope.setExtra("thread.messages.count", "${messages.count()}") + scope.setExtra("thread.duplicates.count", "${duplicates.count()}") + scope.setExtra("thread.isFromSearch", "$isFromSearch") + scope.setExtra("thread.hasDrafts", "$hasDrafts") + } + + return@getOrElse null to null +} + +fun Thread.computeDisplayedRecipients(): RealmList = when (folder.role) { + FolderRole.SENT, FolderRole.DRAFT, FolderRole.SCHEDULED_DRAFTS -> to + else -> from +} diff --git a/app/src/main/java/com/infomaniak/mail/data/models/message/EmojiReactionNotAllowedReason.kt b/app/src/main/java/com/infomaniak/mail/data/models/message/EmojiReactionNotAllowedReason.kt deleted file mode 100644 index 6671fa064fc..00000000000 --- a/app/src/main/java/com/infomaniak/mail/data/models/message/EmojiReactionNotAllowedReason.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Infomaniak Mail - Android - * Copyright (C) 2025-2026 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.data.models.message - -import com.infomaniak.core.network.utils.ErrorCodeTranslated -import com.infomaniak.core.common.utils.ApiEnum -import com.infomaniak.mail.R - -enum class EmojiReactionNotAllowedReason( - override val apiValue: String, - override val translateRes: Int, -) : ApiEnum, ErrorCodeTranslated { - EmojiReactionFolderNotAllowedDraft("folder_not_allowed_draft", R.string.errorEmojiReactionFolderNotAllowedDraft), - EmojiReactionFolderNotAllowedScheduledDraft( - "folder_not_allowed_scheduled_draft", - R.string.errorEmojiReactionFolderNotAllowedScheduledDraft - ), - EmojiReactionFolderNotAllowedSpam("folder_not_allowed_spam", R.string.errorEmojiReactionFolderNotAllowedSpam), - EmojiReactionFolderNotAllowedTrash("folder_not_allowed_trash", R.string.errorEmojiReactionFolderNotAllowedTrash), - EmojiReactionMessageInReplyToNotAllowed( - "message_in_reply_to_not_allowed", - R.string.errorEmojiReactionMessageInReplyToNotAllowed - ), - EmojiReactionMessageInReplyToNotValid( - "message_in_reply_to_not_valid", - R.string.errorEmojiReactionMessageInReplyToNotValid - ), - EmojiReactionMessageInReplyToEncrypted("message_in_reply_to_encrypted", R.string.errorEmojiReactionMessageInReplyEncrypted), - EmojiReactionMaxRecipient("max_recipient", R.string.errorEmojiReactionMaxRecipient), - EmojiReactionRecipientNotAllowed("recipient_not_allowed", R.string.errorEmojiReactionRecipientNotAllowed); - - override val code: String = "emoji_reaction__$apiValue" -} diff --git a/app/src/main/java/com/infomaniak/mail/data/models/thread/Thread.kt b/app/src/main/java/com/infomaniak/mail/data/models/thread/Thread.kt deleted file mode 100644 index 2bcfed85c69..00000000000 --- a/app/src/main/java/com/infomaniak/mail/data/models/thread/Thread.kt +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Infomaniak Mail - Android - * Copyright (C) 2022-2026 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 . - */ -@file:UseSerializers(RealmListKSerializer::class, RealmInstantSerializer::class) - -package com.infomaniak.mail.data.models.thread - -import android.content.Context -import com.infomaniak.core.common.utils.apiEnum -import com.infomaniak.mail.MatomoMail.MatomoName -import com.infomaniak.mail.data.LocalSettings -import com.infomaniak.mail.data.api.RealmInstantSerializer -import com.infomaniak.mail.data.cache.mailboxContent.FolderController -import com.infomaniak.mail.data.models.Bimi -import com.infomaniak.mail.data.models.Folder -import com.infomaniak.mail.data.models.Folder.FolderRole -import com.infomaniak.mail.data.models.Snoozable -import com.infomaniak.mail.data.models.SnoozeState -import com.infomaniak.mail.data.models.correspondent.Recipient -import com.infomaniak.mail.data.models.isSnoozed -import com.infomaniak.mail.data.models.isUnsnoozed -import com.infomaniak.mail.data.models.mailbox.Mailbox -import com.infomaniak.mail.data.models.message.Message -import com.infomaniak.mail.ui.main.folder.ThreadListDateDisplay -import com.infomaniak.mail.utils.AccountUtils -import com.infomaniak.mail.utils.FeatureAvailability -import com.infomaniak.mail.utils.extensions.toRealmInstant -import io.realm.kotlin.TypedRealm -import io.realm.kotlin.ext.backlinks -import io.realm.kotlin.ext.realmListOf -import io.realm.kotlin.ext.realmSetOf -import io.realm.kotlin.serializers.RealmListKSerializer -import io.realm.kotlin.types.RealmInstant -import io.realm.kotlin.types.RealmList -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.annotations.Ignore -import io.realm.kotlin.types.annotations.PrimaryKey -import io.sentry.Sentry -import io.sentry.SentryLevel -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient -import kotlinx.serialization.UseSerializers -import java.util.Date -import kotlin.random.Random - -@Serializable -class Thread : RealmObject, Snoozable { - - //region Remote data - @PrimaryKey - var uid: String = "" - var messages = realmListOf() - // This value should always be provided because messages always have at least an internalDate. Because of this, the initial value is meaningless - @SerialName("internal_date") - var internalDate: RealmInstant = Date().toRealmInstant() - @SerialName("date") - var displayDate: RealmInstant = internalDate - @SerialName("unseen_messages") - var unseenMessagesCount: Int = 0 - var from = realmListOf() - var to = realmListOf() - var subject: String? = null - @SerialName("has_drafts") - var hasDrafts: Boolean = false - @SerialName("flagged") - var isFavorite: Boolean = false - @SerialName("answered") - var isAnswered: Boolean = false - @SerialName("forwarded") - var isForwarded: Boolean = false - @SerialName("snooze_state") - private var _snoozeState: String? = null - @SerialName("snooze_end_date") - override var snoozeEndDate: RealmInstant? = null - @SerialName("snooze_uuid") - override var snoozeUuid: String? = null - //endregion - - //region Local data (Transient) - @Transient - var folderId: String = "" - @Transient - var folderName: String = "" - @Transient - var duplicates = realmListOf() - @Transient - var messagesIds = realmSetOf() - @Transient - var isFromSearch: Boolean = false - @Transient - var hasAttachable: Boolean = false - // Has been moved (archived, spammed, deleted, moved) but API call hasn't been done yet. - // It's only used to locally filter the Threads' list. - @Transient - var isLocallyMovedOut: Boolean = false - // When deserializing threads from the api, this way of initializing the value will compute the correct - // numberOfScheduledDrafts right after deserialization - @Transient - var numberOfScheduledDrafts: Int = messages.count { it.isScheduledDraft } - @Transient - var isLastInboxMessageSnoozed: Boolean = false - - /** - * The list messages where messages that are emoji reactions have been filtered out - */ - @Transient - var messagesWithContent = realmListOf() - //endregion - - val isSeen get() = unseenMessagesCount == 0 - - @Ignore - override var snoozeState: SnoozeState? by apiEnum(::_snoozeState) - - // TODO: Put this back in `private` when the Threads parental issues are fixed - val _folders by backlinks(Folder::threads) - - // TODO: Remove this `runCatching / getOrElse` when the Threads parental issues are fixed - val folder - get() = runCatching { - // The only situation were we can have more than 1 parent folder is if the parent folders - // are 2 with this exact situation : 1x any real folder and 1x the Search folder. - if (_folders.count() == 2) { - _folders.single { it.id != FolderController.SEARCH_FOLDER_ID } - } else { - _folders.single() - } - }.getOrElse { exception -> - val reason = if (_folders.isEmpty()) { - "no parents" // Thread has 0 parent folders - } else { - val allFoldersAreSearch = _folders.all { it.id == FolderController.SEARCH_FOLDER_ID } - val allFoldersAreTheSame = _folders.all { it.id == _folders.firstOrNull()?.id } - when { - allFoldersAreSearch -> "multiple SEARCH folder" // Thread has multiple times the Search folder as parent - allFoldersAreTheSame -> "multiple same parent" // Thread has multiple times the same parent folder - else -> "multiple parents" // Thread has multiple parent folders - } - } - // TODO: As of 20/05/2025, this event is taking the Sentry server down because it's emitting way too often. - // So we temporarily set it to emit in only 2% of cases, while we are working on a fix. - if (Random.nextInt(0, 100) < 2) { - Sentry.captureMessage( - "Thread doesn't have a unique parent Folder, it should not be possible", - SentryLevel.ERROR, - ) { scope -> - scope.setTag("issueType", reason) - scope.setTag("foldersId", _folders.joinToString { it.id }) - scope.setTag("foldersCount", "${_folders.count()}") - scope.setExtra("folders_", "${_folders.map { "role:[${it.role?.name}] (id:[${it.id}])" }}") - scope.setExtra("foldersCount_", "${_folders.count()}") - scope.setExtra("threadUid", uid) - scope.setExtra("email", AccountUtils.currentMailboxEmail.toString()) - scope.setExtra("exception", exception.message.toString()) - } - } - - return@getOrElse _folders.firstOrNull { uid.contains(it.id) } ?: _folders.first() - } - - val isOnlyOneDraft get() = messages.count() == 1 && hasDrafts - - fun getDisplayedMessages(featureFlags: Mailbox.FeatureFlagSet?, localSettings: LocalSettings): RealmList { - return if (FeatureAvailability.isReactionsAvailable(featureFlags, localSettings)) messagesWithContent else messages - } - - fun addMessageWithConditions(newMessage: Message, realm: TypedRealm) { - - val shouldAddMessage = when (FolderController.getFolderBlocking(folderId, realm)?.role) { - FolderRole.DRAFT -> newMessage.isDraft // In Draft folder: only add draft Messages. - FolderRole.SCHEDULED_DRAFTS -> newMessage.isScheduledDraft // In ScheduledDrafts folder: only add scheduled Messages. - FolderRole.TRASH -> newMessage.isTrashed // In Trash folder: only add deleted Messages. - else -> !newMessage.isTrashed // In other folders: only add non-deleted Messages. - } - - if (shouldAddMessage) { - val twinMessage = messages.firstOrNull { it.messageId == newMessage.messageId } - if (twinMessage == null) { - messages.add(newMessage) - } else { - addDuplicatedMessage(twinMessage, newMessage) - } - } - } - - private fun addDuplicatedMessage(twinMessage: Message, newMessage: Message) { - val isTwinTheRealMessage = twinMessage.folderId == folderId - if (isTwinTheRealMessage) { - duplicates.add(newMessage) - } else { - messages.remove(twinMessage) - duplicates.add(twinMessage) - messages.add(newMessage) - } - } - - fun containsOnlyScheduledDrafts(featureFlags: Mailbox.FeatureFlagSet?, localSettings: LocalSettings): Boolean { - return getDisplayedMessages(featureFlags, localSettings).count() == numberOfScheduledDrafts - } - - /** - * Only used for when the api tells us we're trying to automatically unsnooze a thread that's not snoozed - */ - override fun manuallyUnsnooze() { - super.manuallyUnsnooze() - messages.forEach(Message::manuallyUnsnooze) - duplicates.forEach(Message::manuallyUnsnooze) - } - - fun computeAvatarRecipient(): Pair = runCatching { - val message = messages.lastOrNull { - it.folder.role != FolderRole.SENT && - it.folder.role != FolderRole.DRAFT && - it.folder.role != FolderRole.SCHEDULED_DRAFTS - } ?: messages.last() - - val recipients = when (message.folder.role) { - FolderRole.SENT, FolderRole.DRAFT, FolderRole.SCHEDULED_DRAFTS -> message.to - else -> message.from - } - - return@runCatching recipients.firstOrNull() to message.bimi - - }.getOrElse { throwable -> - Sentry.captureException(throwable) { scope -> - scope.setExtra("thread.folder.role", folder.role?.name.toString()) - scope.setExtra("thread.folder.id", folder.id) - scope.setExtra("thread.folderId", folderId) - scope.setExtra("thread.uid", uid) - scope.setExtra("thread.messages.count", "${messages.count()}") - scope.setExtra("thread.duplicates.count", "${duplicates.count()}") - scope.setExtra("thread.isFromSearch", "$isFromSearch") - scope.setExtra("thread.hasDrafts", "$hasDrafts") - } - - return@getOrElse null to null - } - - fun computeDisplayedRecipients(): RealmList = when (folder.role) { - FolderRole.SENT, FolderRole.DRAFT, FolderRole.SCHEDULED_DRAFTS -> to - else -> from - } - - fun computePreview(context: Context): String { - val message = if (folder.role == FolderRole.SENT) { - messages.lastOrNull { it.folderId == folderId } ?: messages.last() - } else { - messages.last() - } - - return message.getFormattedPreview(context).content - } - - fun computeThreadListDateDisplay(featureFlags: Mailbox.FeatureFlagSet?, localSettings: LocalSettings) = when { - containsOnlyScheduledDrafts(featureFlags, localSettings) -> ThreadListDateDisplay.Scheduled - isSnoozed() -> ThreadListDateDisplay.Snoozed - isUnsnoozed() -> ThreadListDateDisplay.Unsnoozed - else -> ThreadListDateDisplay.Default - } - - override fun equals(other: Any?) = other === this || (other is Thread && other.uid == uid) - - override fun hashCode(): Int = uid.hashCode() - - enum class ThreadFilter(val matomoName: MatomoName) { - ALL(MatomoName.FolderFilter), - SEEN(MatomoName.ReadFilter), - UNSEEN(MatomoName.UnreadFilter), - STARRED(MatomoName.FavoriteFilter), - ATTACHMENTS(MatomoName.AttachmentFilter), - FOLDER(MatomoName.FolderFilter), - } - - companion object { - const val FORMAT_DAY_OF_THE_WEEK = "EEE" - } -} diff --git a/app/src/main/java/com/infomaniak/mail/data/models/thread/ThreadEmojiReactionsComputation.kt b/app/src/main/java/com/infomaniak/mail/data/models/thread/ThreadEmojiReactionsComputation.kt index 5eafa28198e..48d50114554 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/thread/ThreadEmojiReactionsComputation.kt +++ b/app/src/main/java/com/infomaniak/mail/data/models/thread/ThreadEmojiReactionsComputation.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -17,6 +17,7 @@ */ package com.infomaniak.mail.data.models.thread +import com.infomaniak.mail.data.models.extensions.addAuthor import com.infomaniak.mail.data.models.message.EmojiReactionAuthor import com.infomaniak.mail.data.models.message.EmojiReactionState import com.infomaniak.mail.data.models.message.Message diff --git a/app/src/main/java/com/infomaniak/mail/data/models/thread/ThreadFilter.kt b/app/src/main/java/com/infomaniak/mail/data/models/thread/ThreadFilter.kt new file mode 100644 index 00000000000..4136f742945 --- /dev/null +++ b/app/src/main/java/com/infomaniak/mail/data/models/thread/ThreadFilter.kt @@ -0,0 +1,29 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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.data.models.thread + +import com.infomaniak.mail.MatomoMail + +enum class ThreadFilter(val matomoName: MatomoMail.MatomoName) { + ALL(MatomoMail.MatomoName.FolderFilter), + SEEN(MatomoMail.MatomoName.ReadFilter), + UNSEEN(MatomoMail.MatomoName.UnreadFilter), + STARRED(MatomoMail.MatomoName.FavoriteFilter), + ATTACHMENTS(MatomoMail.MatomoName.AttachmentFilter), + FOLDER(MatomoMail.MatomoName.FolderFilter), +} diff --git a/app/src/main/java/com/infomaniak/mail/data/models/thread/ThreadResult.kt b/app/src/main/java/com/infomaniak/mail/data/models/thread/ThreadResult.kt index cd593594745..95e5fa6d91e 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/thread/ThreadResult.kt +++ b/app/src/main/java/com/infomaniak/mail/data/models/thread/ThreadResult.kt @@ -17,6 +17,7 @@ */ package com.infomaniak.mail.data.models.thread +import com.infomaniak.mail.data.models.thread.Thread import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/com/infomaniak/mail/receivers/NotificationActionsReceiver.kt b/app/src/main/java/com/infomaniak/mail/receivers/NotificationActionsReceiver.kt index 2825ef44039..7555cf2f7eb 100644 --- a/app/src/main/java/com/infomaniak/mail/receivers/NotificationActionsReceiver.kt +++ b/app/src/main/java/com/infomaniak/mail/receivers/NotificationActionsReceiver.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2023-2025 Infomaniak Network SA + * Copyright (C) 2023-2026 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 @@ -36,7 +36,8 @@ import com.infomaniak.mail.data.cache.mailboxContent.RefreshController import com.infomaniak.mail.data.cache.mailboxContent.RefreshController.RefreshMode import com.infomaniak.mail.data.cache.mailboxInfo.MailboxController import com.infomaniak.mail.data.models.Folder -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole +import com.infomaniak.mail.data.models.extensions.folder import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.di.IoDispatcher import com.infomaniak.mail.utils.AccountUtils diff --git a/app/src/main/java/com/infomaniak/mail/ui/MainActivity.kt b/app/src/main/java/com/infomaniak/mail/ui/MainActivity.kt index d18cb8831f5..8400951b8e9 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/MainActivity.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/MainActivity.kt @@ -64,7 +64,8 @@ import com.infomaniak.mail.MatomoMail.trackMenuDrawerEvent import com.infomaniak.mail.MatomoMail.trackNewMessageEvent import com.infomaniak.mail.R import com.infomaniak.mail.data.models.Folder -import com.infomaniak.mail.data.models.draft.Draft.DraftAction +import com.infomaniak.mail.data.models.draft.DraftAction +import com.infomaniak.mail.data.models.extensions.kSuite import com.infomaniak.mail.databinding.ActivityMainBinding import com.infomaniak.mail.firebase.FirebaseNotificationReceiver import com.infomaniak.mail.ui.alertDialogs.DescriptionAlertDialog 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 edae27b1aa2..24c7a60da7f 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt @@ -60,11 +60,17 @@ import com.infomaniak.mail.data.cache.mailboxInfo.QuotasController import com.infomaniak.mail.data.cache.userInfo.AddressBookController import com.infomaniak.mail.data.cache.userInfo.MergedContactController import com.infomaniak.mail.data.models.Folder -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.FolderUi import com.infomaniak.mail.data.models.MoveResult +import com.infomaniak.mail.data.models.thread.Thread import com.infomaniak.mail.data.models.correspondent.Recipient import com.infomaniak.mail.data.models.draft.Draft +import com.infomaniak.mail.data.models.draft.DraftAction +import com.infomaniak.mail.data.models.extensions.action +import com.infomaniak.mail.data.models.extensions.getDisplayedMessages +import com.infomaniak.mail.data.models.extensions.getLocalizedName +import com.infomaniak.mail.data.models.extensions.kSuite import com.infomaniak.mail.data.models.forEachNestedItem import com.infomaniak.mail.data.models.isSnoozed import com.infomaniak.mail.data.models.mailbox.Mailbox @@ -72,8 +78,7 @@ import com.infomaniak.mail.data.models.mailbox.Mailbox.FeatureFlagSet import com.infomaniak.mail.data.models.mailbox.SendersRestrictions import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.data.models.snooze.BatchSnoozeResult -import com.infomaniak.mail.data.models.thread.Thread -import com.infomaniak.mail.data.models.thread.Thread.ThreadFilter +import com.infomaniak.mail.data.models.thread.ThreadFilter import com.infomaniak.mail.di.IoDispatcher import com.infomaniak.mail.di.MailboxInfoRealm import com.infomaniak.mail.ui.main.SnackbarManager @@ -235,7 +240,7 @@ class MainViewModel @Inject constructor( .map { it.list } .removeRolesThatHideWhenEmpty() .map { it.toFolderUiTree(isInDefaultFolderSection = true) } - }.catch {} + }.catch { it.printStackTrace() } private val customFoldersFlow = _currentMailboxObjectId.filterNotNull().flatMapLatest { folderController @@ -243,7 +248,7 @@ class MainViewModel @Inject constructor( .map { it.list } .keepTopLevelFolders() .map { it.toFolderUiTree(isInDefaultFolderSection = false) } - }.catch {} + }.catch { it.printStackTrace() } val displayedFoldersFlow = combine(defaultFoldersFlow, customFoldersFlow) { default, custom -> DisplayedFolders(default, custom) @@ -1486,7 +1491,7 @@ class MainViewModel @Inject constructor( mimeType = Utils.TEXT_HTML - action = Draft.DraftAction.SEND_REACTION + action = DraftAction.SEND_REACTION emojiReaction = emoji } diff --git a/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/AccountBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/AccountBottomSheetDialog.kt index fbcc55e2cdc..125e6139db3 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/AccountBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/AccountBottomSheetDialog.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2024-2025 Infomaniak Network SA + * Copyright (C) 2024-2026 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 @@ -22,14 +22,13 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import com.infomaniak.core.legacy.utils.context import com.infomaniak.core.legacy.utils.safeBinding import com.infomaniak.mail.MatomoMail.MatomoName import com.infomaniak.mail.MatomoMail.trackAccountEvent import com.infomaniak.mail.R +import com.infomaniak.mail.data.models.extensions.kSuite import com.infomaniak.mail.databinding.BottomSheetAccountBinding import com.infomaniak.mail.di.IoDispatcher import com.infomaniak.mail.ui.MainActivity diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/folder/PerformSwipeActionManager.kt b/app/src/main/java/com/infomaniak/mail/ui/main/folder/PerformSwipeActionManager.kt index f7c2ff69528..d6c7558d83a 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/folder/PerformSwipeActionManager.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/folder/PerformSwipeActionManager.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -25,11 +25,12 @@ import com.infomaniak.mail.MatomoMail.trackEvent import com.infomaniak.mail.R import com.infomaniak.mail.data.LocalSettings import com.infomaniak.mail.data.models.Folder -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.SwipeAction +import com.infomaniak.mail.data.models.extensions.folder import com.infomaniak.mail.data.models.isSnoozed import com.infomaniak.mail.data.models.thread.Thread -import com.infomaniak.mail.data.models.thread.Thread.ThreadFilter +import com.infomaniak.mail.data.models.thread.ThreadFilter import com.infomaniak.mail.ui.main.folderPicker.FolderPickerAction import com.infomaniak.mail.ui.main.settings.appearance.swipe.SwipeActionsSettingsFragment import com.infomaniak.mail.ui.main.thread.ThreadViewModel.SnoozeScheduleType diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadItem.kt b/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadItem.kt index c81a038f41f..e530ca56d2a 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadItem.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadItem.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -17,7 +17,7 @@ */ package com.infomaniak.mail.ui.main.folder -import com.infomaniak.mail.data.models.Folder +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.correspondent.MergedContact import com.infomaniak.mail.data.models.thread.Thread @@ -25,7 +25,7 @@ sealed interface ThreadListItem { data class Content(val thread: Thread) : ThreadListItem data class SectionTitle(val title: String) : ThreadListItem data class ContactItem(val contact: MergedContact) : ThreadListItem - data class FlushFolderButton(val folderRole: Folder.FolderRole) : ThreadListItem + data class FlushFolderButton(val folderRole: FolderRole) : ThreadListItem data object LoadMore : ThreadListItem data object Spacer : ThreadListItem } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListAdapter.kt b/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListAdapter.kt index 441434df1d8..573fed20507 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListAdapter.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListAdapter.kt @@ -52,11 +52,20 @@ import com.infomaniak.mail.R import com.infomaniak.mail.data.LocalSettings import com.infomaniak.mail.data.LocalSettings.ThreadDensity import com.infomaniak.mail.data.cache.RealmDatabase -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.SwipeAction +import com.infomaniak.mail.data.models.thread.Thread import com.infomaniak.mail.data.models.correspondent.MergedContact import com.infomaniak.mail.data.models.correspondent.Recipient -import com.infomaniak.mail.data.models.thread.Thread +import com.infomaniak.mail.data.models.extensions.computeAvatarRecipient +import com.infomaniak.mail.data.models.extensions.computeDisplayedRecipients +import com.infomaniak.mail.data.models.extensions.computeFirstAndLastName +import com.infomaniak.mail.data.models.extensions.computePreview +import com.infomaniak.mail.data.models.extensions.computeThreadListDateDisplay +import com.infomaniak.mail.data.models.extensions.displayedName +import com.infomaniak.mail.data.models.extensions.folder +import com.infomaniak.mail.data.models.extensions.getDisplayedMessages +import com.infomaniak.mail.data.models.extensions.isMe import com.infomaniak.mail.databinding.CardviewThreadItemBinding import com.infomaniak.mail.databinding.ItemBannerWithActionViewBinding import com.infomaniak.mail.databinding.ItemContactSearchBinding diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListFragment.kt index 3b6c2b1cefb..81ff76c5f1d 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListFragment.kt @@ -75,12 +75,16 @@ import com.infomaniak.mail.MatomoMail.trackThreadListEvent import com.infomaniak.mail.R import com.infomaniak.mail.data.LocalSettings.ThreadDensity.COMPACT import com.infomaniak.mail.data.models.Folder -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.SwipeAction import com.infomaniak.mail.data.models.correspondent.MergedContact +import com.infomaniak.mail.data.models.extensions.folder +import com.infomaniak.mail.data.models.extensions.getLocalizedName +import com.infomaniak.mail.data.models.extensions.kSuite +import com.infomaniak.mail.data.models.extensions.notificationsIsDisabled import com.infomaniak.mail.data.models.mailbox.Mailbox.FeatureFlagSet import com.infomaniak.mail.data.models.thread.Thread -import com.infomaniak.mail.data.models.thread.Thread.ThreadFilter +import com.infomaniak.mail.data.models.thread.ThreadFilter import com.infomaniak.mail.databinding.FragmentThreadListBinding import com.infomaniak.mail.ui.MainActivity import com.infomaniak.mail.ui.alertDialogs.DescriptionAlertDialog diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListMultiSelection.kt b/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListMultiSelection.kt index 357e30fc502..c671f37ce1c 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListMultiSelection.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListMultiSelection.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2023-2025 Infomaniak Network SA + * Copyright (C) 2023-2026 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 @@ -29,7 +29,7 @@ import com.infomaniak.mail.MatomoMail.trackMultiSelectActionEvent import com.infomaniak.mail.R import com.infomaniak.mail.data.LocalSettings import com.infomaniak.mail.data.LocalSettings.ThreadDensity -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.thread.Thread import com.infomaniak.mail.ui.MainActivity import com.infomaniak.mail.ui.MainViewModel 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 f1893e7ec71..a17d717d159 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 @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2023-2025 Infomaniak Network SA + * Copyright (C) 2023-2026 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 @@ -38,6 +38,8 @@ import com.infomaniak.mail.R import com.infomaniak.mail.data.LocalSettings import com.infomaniak.mail.data.LocalSettings.AutoAdvanceMode import com.infomaniak.mail.data.cache.mailboxContent.FolderController +import com.infomaniak.mail.data.models.extensions.getLocalizedName +import com.infomaniak.mail.data.models.extensions.kSuite import com.infomaniak.mail.data.models.thread.Thread import com.infomaniak.mail.ui.MainActivity import com.infomaniak.mail.ui.MainViewModel diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/folderPicker/FolderPickerAdapter.kt b/app/src/main/java/com/infomaniak/mail/ui/main/folderPicker/FolderPickerAdapter.kt index c615afd9f23..8c9e6b21b97 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/folderPicker/FolderPickerAdapter.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/folderPicker/FolderPickerAdapter.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2025 Infomaniak Network SA + * Copyright (C) 2022-2026 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 @@ -28,6 +28,7 @@ import androidx.viewbinding.ViewBinding import com.infomaniak.mail.R import com.infomaniak.mail.data.models.Folder import com.infomaniak.mail.data.models.FolderUi +import com.infomaniak.mail.data.models.extensions.folderIconRes import com.infomaniak.mail.databinding.ItemDividerHorizontalBinding import com.infomaniak.mail.databinding.ItemSelectableFolderBinding import com.infomaniak.mail.ui.main.folderPicker.FolderPickerAdapter.MoveFolderViewHolder @@ -126,7 +127,7 @@ class FolderPickerAdapter @Inject constructor() : ListAdapter() { override fun areItemsTheSame(oldItem: FolderPickerItem, newItem: FolderPickerItem) = runCatchingRealm { - return when (oldItem) { + when (oldItem) { is FolderPickerItem.Divider if newItem is FolderPickerItem.Divider -> true // Dividers don't have any content, so always true. is FolderPickerItem.Folder if newItem is FolderPickerItem.Folder && oldItem.folderUi.folder.id == newItem.folderUi.folder.id -> true else -> false diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/folderPicker/FolderPickerViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/folderPicker/FolderPickerViewModel.kt index 6013b44b98f..95731539451 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/folderPicker/FolderPickerViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/folderPicker/FolderPickerViewModel.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2023-2025 Infomaniak Network SA + * Copyright (C) 2023-2026 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 @@ -22,7 +22,8 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole +import com.infomaniak.mail.data.models.extensions.folderNameRes import com.infomaniak.mail.di.IoDispatcher import com.infomaniak.mail.ui.MainViewModel import com.infomaniak.mail.utils.coroutineContext diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/menuDrawer/MenuDrawerAdapter.kt b/app/src/main/java/com/infomaniak/mail/ui/main/menuDrawer/MenuDrawerAdapter.kt index 5f627a44271..a45f3321785 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/menuDrawer/MenuDrawerAdapter.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/menuDrawer/MenuDrawerAdapter.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2024-2025 Infomaniak Network SA + * Copyright (C) 2024-2026 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 @@ -26,6 +26,7 @@ import androidx.viewbinding.ViewBinding import com.infomaniak.mail.R import com.infomaniak.mail.data.models.Folder import com.infomaniak.mail.data.models.FolderUi +import com.infomaniak.mail.data.models.extensions.unreadCountDisplay import com.infomaniak.mail.data.models.forEachNestedItem import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.data.models.mailbox.MailboxPermissions diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/menuDrawer/items/FolderViewHolder.kt b/app/src/main/java/com/infomaniak/mail/ui/main/menuDrawer/items/FolderViewHolder.kt index 8201959b3a7..68aeb14cbec 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/menuDrawer/items/FolderViewHolder.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/menuDrawer/items/FolderViewHolder.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2024-2025 Infomaniak Network SA + * Copyright (C) 2024-2026 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 @@ -25,8 +25,12 @@ import com.infomaniak.core.sentry.SentryLog import com.infomaniak.mail.MatomoMail.MatomoName import com.infomaniak.mail.MatomoMail.trackMenuDrawerEvent import com.infomaniak.mail.R -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.FolderUi +import com.infomaniak.mail.data.models.extensions.folderIconRes +import com.infomaniak.mail.data.models.extensions.getLocalizedName +import com.infomaniak.mail.data.models.extensions.matomoName +import com.infomaniak.mail.data.models.extensions.unreadCountDisplay import com.infomaniak.mail.databinding.ItemMenuDrawerFolderBinding import com.infomaniak.mail.ui.main.menuDrawer.MenuDrawerAdapter.MenuDrawerViewHolder import com.infomaniak.mail.utils.UnreadDisplay diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/menuDrawer/items/FooterViewHolder.kt b/app/src/main/java/com/infomaniak/mail/ui/main/menuDrawer/items/FooterViewHolder.kt index e0956d4ebea..fbcac8a9a2f 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/menuDrawer/items/FooterViewHolder.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/menuDrawer/items/FooterViewHolder.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2024 Infomaniak Network SA + * Copyright (C) 2024-2026 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 @@ -25,6 +25,7 @@ import com.infomaniak.core.legacy.utils.context import com.infomaniak.core.sentry.SentryLog import com.infomaniak.mail.BuildConfig import com.infomaniak.mail.data.models.Quotas +import com.infomaniak.mail.data.models.extensions.getText import com.infomaniak.mail.databinding.ItemMenuDrawerFooterBinding import com.infomaniak.mail.ui.main.menuDrawer.MenuDrawerAdapter.MenuDrawerViewHolder diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/menuDrawer/items/MailboxViewHolder.kt b/app/src/main/java/com/infomaniak/mail/ui/main/menuDrawer/items/MailboxViewHolder.kt index e8dee7c7b72..405b3b9c472 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/menuDrawer/items/MailboxViewHolder.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/menuDrawer/items/MailboxViewHolder.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2024 Infomaniak Network SA + * Copyright (C) 2024-2026 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 @@ -22,6 +22,7 @@ import android.view.ViewGroup import com.infomaniak.core.sentry.SentryLog import com.infomaniak.mail.MatomoMail.MatomoName import com.infomaniak.mail.MatomoMail.trackMenuDrawerEvent +import com.infomaniak.mail.data.models.extensions.unreadCountDisplay import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.databinding.ItemMenuDrawerMailboxBinding import com.infomaniak.mail.ui.main.menuDrawer.MenuDrawerAdapter.MenuDrawerViewHolder diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/search/NamedFolder.kt b/app/src/main/java/com/infomaniak/mail/ui/main/search/NamedFolder.kt index 0a6e9febf62..e54c00820a6 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/search/NamedFolder.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/search/NamedFolder.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -19,9 +19,12 @@ package com.infomaniak.mail.ui.main.search import android.content.Context import com.infomaniak.mail.data.models.Folder +import com.infomaniak.mail.data.models.FolderRole +import com.infomaniak.mail.data.models.extensions.folderNameRes +import com.infomaniak.mail.data.models.extensions.getLocalizedName sealed interface NamedFolder { - data class Role(val role: Folder.FolderRole) : NamedFolder { + data class Role(val role: FolderRole) : NamedFolder { override fun getName(context: Context): String = context.getString(role.folderNameRes) } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/search/SearchFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/main/search/SearchFragment.kt index 505b3080717..09653e1294e 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/search/SearchFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/search/SearchFragment.kt @@ -46,11 +46,11 @@ import com.infomaniak.mail.MatomoMail.MatomoName import com.infomaniak.mail.MatomoMail.trackSearchEvent import com.infomaniak.mail.MatomoMail.trackThreadListEvent import com.infomaniak.mail.R -import com.infomaniak.mail.data.models.Folder +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.correspondent.MergedContact import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.data.models.thread.Thread -import com.infomaniak.mail.data.models.thread.Thread.ThreadFilter +import com.infomaniak.mail.data.models.thread.ThreadFilter import com.infomaniak.mail.databinding.FragmentSearchBinding import com.infomaniak.mail.ui.main.folder.ThreadListAdapterCallbacks import com.infomaniak.mail.ui.main.folder.TwoPaneFragment @@ -245,7 +245,7 @@ class SearchFragment : TwoPaneFragment() { private fun selectCurrentFolder() { val sourceFolder = mainViewModel.currentFolder.value - if (!searchViewModel.isAllFoldersSelected && searchViewModel.filterFolder == null && sourceFolder?.role != Folder.FolderRole.INBOX) { + if (!searchViewModel.isAllFoldersSelected && searchViewModel.filterFolder == null && sourceFolder?.role != FolderRole.INBOX) { searchViewModel.selectFolder(sourceFolder) } updateAllFoldersButtonUi() diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/search/SearchViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/search/SearchViewModel.kt index d5b09a48ee8..695a5b922c0 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/search/SearchViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/search/SearchViewModel.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2023-2025 Infomaniak Network SA + * Copyright (C) 2023-2026 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 @@ -40,7 +40,7 @@ import com.infomaniak.mail.data.models.Folder import com.infomaniak.mail.data.models.Folder.Companion.DUMMY_FOLDER_ID import com.infomaniak.mail.data.models.correspondent.MergedContact import com.infomaniak.mail.data.models.thread.Thread -import com.infomaniak.mail.data.models.thread.Thread.ThreadFilter +import com.infomaniak.mail.data.models.thread.ThreadFilter import com.infomaniak.mail.di.IoDispatcher import com.infomaniak.mail.ui.main.folder.ThreadListItem import com.infomaniak.mail.ui.main.search.SearchFragment.VisibilityMode diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/settings/mailbox/SignatureSettingAdapter.kt b/app/src/main/java/com/infomaniak/mail/ui/main/settings/mailbox/SignatureSettingAdapter.kt index 0fcbd818314..8fa51ef3301 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/settings/mailbox/SignatureSettingAdapter.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/settings/mailbox/SignatureSettingAdapter.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2023-2024 Infomaniak Network SA + * Copyright (C) 2023-2026 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 @@ -23,6 +23,7 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView.Adapter import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.infomaniak.mail.data.models.extensions.getDummySignature import com.infomaniak.mail.data.models.signature.Signature import com.infomaniak.mail.databinding.ItemSettingsSignatureBinding import com.infomaniak.mail.ui.main.settings.mailbox.SignatureSettingAdapter.SettingsSignatureViewHolder diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/settings/mailbox/SignatureSettingFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/main/settings/mailbox/SignatureSettingFragment.kt index 9d32045f469..8ccf587b600 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/settings/mailbox/SignatureSettingFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/settings/mailbox/SignatureSettingFragment.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2024 Infomaniak Network SA + * Copyright (C) 2022-2026 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 @@ -32,6 +32,7 @@ import com.infomaniak.core.legacy.utils.safeBinding import com.infomaniak.core.ui.view.utils.SnackbarUtils.showSnackbar import com.infomaniak.mail.MANAGE_SIGNATURES_URL import com.infomaniak.mail.MatomoMail.MatomoName +import com.infomaniak.mail.data.models.extensions.kSuite import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.data.models.signature.Signature import com.infomaniak.mail.databinding.FragmentSignatureSettingBinding diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadAdapter.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadAdapter.kt index ffb1eb8322a..eab0e8f32e5 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadAdapter.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadAdapter.kt @@ -53,10 +53,15 @@ import com.infomaniak.mail.R import com.infomaniak.mail.data.models.Attachable import com.infomaniak.mail.data.models.Attachment import com.infomaniak.mail.data.models.Bimi +import com.infomaniak.mail.data.models.calendar.AttendanceState import com.infomaniak.mail.data.models.calendar.Attendee -import com.infomaniak.mail.data.models.calendar.Attendee.AttendanceState import com.infomaniak.mail.data.models.calendar.CalendarEventResponse import com.infomaniak.mail.data.models.correspondent.Recipient +import com.infomaniak.mail.data.models.extensions.calendarAttachment +import com.infomaniak.mail.data.models.extensions.displayedName +import com.infomaniak.mail.data.models.extensions.isInSpamFolder +import com.infomaniak.mail.data.models.extensions.isMe +import com.infomaniak.mail.data.models.extensions.isReplyAuthorized import com.infomaniak.mail.data.models.mailbox.SenderDetails import com.infomaniak.mail.data.models.mailbox.SendersRestrictions import com.infomaniak.mail.data.models.message.Message diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadFragment.kt index 59d88d244a8..d302c5e3bbe 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadFragment.kt @@ -61,14 +61,18 @@ import com.infomaniak.mail.data.LocalSettings.ExternalContent import com.infomaniak.mail.data.api.ApiRoutes import com.infomaniak.mail.data.models.Attachable import com.infomaniak.mail.data.models.Attachment -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.SwissTransferFile import com.infomaniak.mail.data.models.calendar.Attendee import com.infomaniak.mail.data.models.correspondent.Recipient import com.infomaniak.mail.data.models.draft.Draft.DraftMode +import com.infomaniak.mail.data.models.extensions.containsOnlyScheduledDrafts +import com.infomaniak.mail.data.models.extensions.downloadUrl +import com.infomaniak.mail.data.models.extensions.kSuite import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.data.models.snooze.BatchSnoozeResult import com.infomaniak.mail.data.models.thread.Thread +import com.infomaniak.mail.data.models.extensions.getRecipientsForReplyTo import com.infomaniak.mail.databinding.FragmentThreadBinding import com.infomaniak.mail.ui.MainViewModel import com.infomaniak.mail.ui.alertDialogs.ConfirmScheduledDraftModificationDialog diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadState.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadState.kt index 2c8d7764426..a82e7966c51 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadState.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadState.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2024-2025 Infomaniak Network SA + * Copyright (C) 2024-2026 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 @@ -18,7 +18,7 @@ package com.infomaniak.mail.ui.main.thread import com.infomaniak.mail.ui.main.thread.ThreadAdapter.SuperCollapsedBlock -import com.infomaniak.mail.utils.MessageBodyUtils.SplitBody +import com.infomaniak.mail.data.models.message.SplitBody import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadViewModel.kt index 636eb071a71..518d88e7f04 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadViewModel.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2025 Infomaniak Network SA + * Copyright (C) 2022-2026 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 @@ -43,9 +43,12 @@ import com.infomaniak.mail.data.cache.mailboxContent.RefreshController import com.infomaniak.mail.data.cache.mailboxContent.RefreshController.RefreshMode import com.infomaniak.mail.data.cache.mailboxContent.ThreadController import com.infomaniak.mail.data.cache.mailboxInfo.MailboxController -import com.infomaniak.mail.data.models.Folder.FolderRole -import com.infomaniak.mail.data.models.calendar.Attendee.AttendanceState +import com.infomaniak.mail.data.models.FolderRole +import com.infomaniak.mail.data.models.calendar.AttendanceState import com.infomaniak.mail.data.models.calendar.CalendarEventResponse +import com.infomaniak.mail.data.models.extensions.calendarAttachment +import com.infomaniak.mail.data.models.extensions.folder +import com.infomaniak.mail.data.models.extensions.getDisplayedMessages import com.infomaniak.mail.data.models.isSnoozed import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.data.models.message.EmojiReactionState @@ -94,7 +97,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/AttachmentActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/AttachmentActionsBottomSheetDialog.kt index 05cae1db6aa..ec1f2b8d2a1 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/AttachmentActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/AttachmentActionsBottomSheetDialog.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2023-2024 Infomaniak Network SA + * Copyright (C) 2023-2026 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 @@ -32,6 +32,7 @@ import com.infomaniak.mail.MatomoMail.trackAttachmentActionsEvent import com.infomaniak.mail.data.models.Attachable import com.infomaniak.mail.data.models.Attachment import com.infomaniak.mail.data.models.SwissTransferFile +import com.infomaniak.mail.data.models.extensions.downloadUrl import com.infomaniak.mail.databinding.BottomSheetAttachmentActionsBinding import com.infomaniak.mail.ui.MainViewModel import com.infomaniak.mail.ui.main.SnackbarManager diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadAttachmentViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadAttachmentViewModel.kt index 75248c3174d..f01b87f964b 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadAttachmentViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/DownloadAttachmentViewModel.kt @@ -25,6 +25,9 @@ import androidx.lifecycle.viewModelScope import com.infomaniak.core.common.cancellable import com.infomaniak.mail.data.cache.mailboxContent.AttachmentController import com.infomaniak.mail.data.models.Attachable +import com.infomaniak.mail.data.models.extensions.getCacheFile +import com.infomaniak.mail.data.models.extensions.getUploadLocalFile +import com.infomaniak.mail.data.models.extensions.hasUsableCache import com.infomaniak.mail.di.IoDispatcher import com.infomaniak.mail.utils.LocalStorageUtils import com.infomaniak.mail.utils.Utils.runCatchingRealm 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 0dd491d81a3..02c334c2ba5 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 @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2025 Infomaniak Network SA + * Copyright (C) 2022-2026 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 @@ -28,6 +28,7 @@ import com.infomaniak.core.ksuite.data.KSuite import com.infomaniak.core.legacy.utils.safeBinding import com.infomaniak.mail.MatomoMail.MatomoName import com.infomaniak.mail.R +import com.infomaniak.mail.data.models.extensions.kSuite import com.infomaniak.mail.databinding.BottomSheetActionsMenuBinding import com.infomaniak.mail.ui.MainViewModel import com.infomaniak.mail.ui.main.folder.ThreadListFragment 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 3546d9e357c..6a0126478a2 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,8 +33,9 @@ import com.infomaniak.mail.MatomoMail.trackBottomSheetMessageActionsEvent import com.infomaniak.mail.MatomoMail.trackBottomSheetThreadActionsEvent import com.infomaniak.mail.R import com.infomaniak.mail.data.models.Folder -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.draft.Draft.DraftMode +import com.infomaniak.mail.data.models.extensions.folder import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.ui.MainActivity import com.infomaniak.mail.ui.alertDialogs.DescriptionAlertDialog 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 83674d7f648..2dc1988823b 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 @@ -37,7 +37,7 @@ import com.infomaniak.mail.MatomoMail.trackMultiSelectActionEvent import com.infomaniak.mail.R import com.infomaniak.mail.data.LocalSettings import com.infomaniak.mail.data.models.Folder -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.isSnoozed import com.infomaniak.mail.data.models.thread.Thread import com.infomaniak.mail.databinding.BottomSheetMultiSelectBinding 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 bcb78fac254..a749a40156c 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 @@ -36,7 +36,7 @@ import com.infomaniak.mail.R import com.infomaniak.mail.data.LocalSettings import com.infomaniak.mail.data.cache.mailboxContent.ThreadController import com.infomaniak.mail.data.models.Folder -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.correspondent.Recipient import com.infomaniak.mail.data.models.draft.Draft.DraftMode import com.infomaniak.mail.data.models.isSnoozed diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/calendar/AttendanceAvatarView.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/calendar/AttendanceAvatarView.kt index 47f04717270..fbc91a43100 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/calendar/AttendanceAvatarView.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/calendar/AttendanceAvatarView.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2023-2024 Infomaniak Network SA + * Copyright (C) 2023-2026 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 @@ -27,6 +27,7 @@ import androidx.core.view.isVisible import com.infomaniak.core.legacy.utils.getAttributes import com.infomaniak.mail.R import com.infomaniak.mail.data.models.calendar.Attendee +import com.infomaniak.mail.data.models.extensions.state import com.infomaniak.mail.databinding.ViewAttendanceAvatarBinding import com.infomaniak.mail.utils.extensions.getColorOrNull import com.infomaniak.mail.utils.extensions.getTransparentColor diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/calendar/CalendarEventBannerView.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/calendar/CalendarEventBannerView.kt index 71b2b264933..e3b75e76e72 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/calendar/CalendarEventBannerView.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/calendar/CalendarEventBannerView.kt @@ -24,18 +24,19 @@ import android.widget.FrameLayout import androidx.core.view.isGone import androidx.core.view.isVisible import com.google.android.material.button.MaterialButton -import com.infomaniak.core.legacy.utils.context import com.infomaniak.core.common.utils.FormatData import com.infomaniak.core.common.utils.addDays import com.infomaniak.core.common.utils.formatWithLocal import com.infomaniak.core.common.utils.isSameDayAs +import com.infomaniak.core.legacy.utils.context import com.infomaniak.mail.MatomoMail.MatomoName import com.infomaniak.mail.MatomoMail.trackCalendarEventEvent import com.infomaniak.mail.R import com.infomaniak.mail.data.models.Attachment +import com.infomaniak.mail.data.models.calendar.AttendanceState import com.infomaniak.mail.data.models.calendar.Attendee -import com.infomaniak.mail.data.models.calendar.Attendee.AttendanceState import com.infomaniak.mail.data.models.calendar.CalendarEvent +import com.infomaniak.mail.data.models.extensions.state import com.infomaniak.mail.databinding.ViewCalendarEventBannerBinding import com.infomaniak.mail.ui.main.SnackbarManager import com.infomaniak.mail.utils.UiUtils.getPrettyNameAndEmail diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/models/EmojiReactionAuthorUi.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/models/EmojiReactionAuthorUi.kt index 61aeaaea400..064de40b42f 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/models/EmojiReactionAuthorUi.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/models/EmojiReactionAuthorUi.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -22,6 +22,7 @@ import com.infomaniak.core.avatar.models.AvatarType import com.infomaniak.mail.R import com.infomaniak.mail.data.models.Bimi import com.infomaniak.mail.data.models.correspondent.Correspondent +import com.infomaniak.mail.data.models.extensions.displayedName import com.infomaniak.mail.utils.AccountUtils import com.infomaniak.mail.utils.AvatarTypeUtils import com.infomaniak.mail.utils.AvatarTypeUtils.fromUser diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/webViewClient/MessageWebViewClient.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/webViewClient/MessageWebViewClient.kt index 2e8c55f878e..8f6c661ec5e 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/webViewClient/MessageWebViewClient.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/webViewClient/MessageWebViewClient.kt @@ -24,6 +24,8 @@ import android.webkit.WebView import android.webkit.WebViewClient import com.infomaniak.mail.data.api.ApiRepository import com.infomaniak.mail.data.models.Attachment +import com.infomaniak.mail.data.models.extensions.getCacheFile +import com.infomaniak.mail.data.models.extensions.hasUsableCache import com.infomaniak.mail.utils.LocalStorageUtils import com.infomaniak.mail.utils.Utils import com.infomaniak.mail.utils.Utils.runCatchingRealm diff --git a/app/src/main/java/com/infomaniak/mail/ui/newMessage/ContactAdapter.kt b/app/src/main/java/com/infomaniak/mail/ui/newMessage/ContactAdapter.kt index fccad28aaae..df73d49f22c 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/newMessage/ContactAdapter.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/newMessage/ContactAdapter.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2025 Infomaniak Network SA + * Copyright (C) 2022-2026 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 diff --git a/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageActivity.kt b/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageActivity.kt index 40a219c5b4f..ae116430dff 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageActivity.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageActivity.kt @@ -34,7 +34,7 @@ import com.infomaniak.mail.BuildConfig import com.infomaniak.mail.MatomoMail.trackDestination import com.infomaniak.mail.R import com.infomaniak.mail.data.models.AppSettings -import com.infomaniak.mail.data.models.draft.Draft.DraftAction +import com.infomaniak.mail.data.models.draft.DraftAction import com.infomaniak.mail.databinding.ActivityNewMessageBinding import com.infomaniak.mail.ui.BaseActivity import com.infomaniak.mail.ui.LaunchActivity diff --git a/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageAiManager.kt b/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageAiManager.kt index bdd8143096d..1565bbe403a 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageAiManager.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageAiManager.kt @@ -40,6 +40,7 @@ import com.infomaniak.mail.R import com.infomaniak.mail.data.LocalSettings import com.infomaniak.mail.data.models.FeatureFlag import com.infomaniak.mail.data.models.ai.AiPromptOpeningStatus +import com.infomaniak.mail.data.models.extensions.kSuite import com.infomaniak.mail.databinding.FragmentNewMessageBinding import com.infomaniak.mail.ui.newMessage.EditorContentManager.Companion.toSanitizedHtml import com.infomaniak.mail.utils.WebViewUtils.Companion.evaluateJs diff --git a/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageFragment.kt index 4680b7c861f..c6f4e7937ff 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageFragment.kt @@ -70,8 +70,9 @@ import com.infomaniak.mail.data.models.Attachment import com.infomaniak.mail.data.models.AttachmentDisposition import com.infomaniak.mail.data.models.FeatureFlag import com.infomaniak.mail.data.models.draft.Draft -import com.infomaniak.mail.data.models.draft.Draft.DraftAction import com.infomaniak.mail.data.models.draft.Draft.DraftMode +import com.infomaniak.mail.data.models.draft.DraftAction +import com.infomaniak.mail.data.models.extensions.kSuite import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.data.models.signature.Signature import com.infomaniak.mail.databinding.FragmentNewMessageBinding diff --git a/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageViewModel.kt index 8307b1a69c3..8018fdc5598 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageViewModel.kt @@ -68,8 +68,14 @@ import com.infomaniak.mail.data.models.correspondent.ContactAutocompletable import com.infomaniak.mail.data.models.correspondent.MergedContact import com.infomaniak.mail.data.models.correspondent.Recipient import com.infomaniak.mail.data.models.draft.Draft -import com.infomaniak.mail.data.models.draft.Draft.DraftAction import com.infomaniak.mail.data.models.draft.Draft.DraftMode +import com.infomaniak.mail.data.models.draft.DraftAction +import com.infomaniak.mail.data.models.extensions.action +import com.infomaniak.mail.data.models.extensions.createValidRecipientOrNull +import com.infomaniak.mail.data.models.extensions.getDefaultSignatureWithFallback +import com.infomaniak.mail.data.models.extensions.getDummySignature +import com.infomaniak.mail.data.models.extensions.getUploadLocalFile +import com.infomaniak.mail.data.models.extensions.setUploadStatus import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.data.models.message.Body import com.infomaniak.mail.data.models.message.Message diff --git a/app/src/main/java/com/infomaniak/mail/ui/newMessage/RecipientFieldView.kt b/app/src/main/java/com/infomaniak/mail/ui/newMessage/RecipientFieldView.kt index 966d23236c2..166c6ea5ba3 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/newMessage/RecipientFieldView.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/newMessage/RecipientFieldView.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2025 Infomaniak Network SA + * Copyright (C) 2022-2026 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 @@ -47,6 +47,8 @@ import com.infomaniak.mail.data.models.addressBook.ContactGroup import com.infomaniak.mail.data.models.correspondent.ContactAutocompletable import com.infomaniak.mail.data.models.correspondent.MergedContact import com.infomaniak.mail.data.models.correspondent.Recipient +import com.infomaniak.mail.data.models.extensions.createValidRecipientOrNull +import com.infomaniak.mail.data.models.extensions.isExternal import com.infomaniak.mail.databinding.ViewContactChipContextMenuBinding import com.infomaniak.mail.databinding.ViewRecipientFieldBinding import com.infomaniak.mail.ui.main.SnackbarManager diff --git a/app/src/main/java/com/infomaniak/mail/ui/newMessage/encryption/EncryptionPasswordFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/newMessage/encryption/EncryptionPasswordFragment.kt index b8f3855f204..a140e3ef08e 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/newMessage/encryption/EncryptionPasswordFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/newMessage/encryption/EncryptionPasswordFragment.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -29,6 +29,7 @@ import com.infomaniak.core.legacy.utils.safeBinding import com.infomaniak.mail.MatomoMail import com.infomaniak.mail.MatomoMail.MatomoName import com.infomaniak.mail.data.models.correspondent.Recipient +import com.infomaniak.mail.data.models.extensions.createValidRecipientOrNull import com.infomaniak.mail.databinding.FragmentEncryptionPasswordBinding import com.infomaniak.mail.ui.main.SnackbarManager import com.infomaniak.mail.ui.newMessage.ContactChipAdapter diff --git a/app/src/main/java/com/infomaniak/mail/utils/AttachableMimeTypeUtils.kt b/app/src/main/java/com/infomaniak/mail/utils/AttachableMimeTypeUtils.kt index 19f018c96de..24895369288 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/AttachableMimeTypeUtils.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/AttachableMimeTypeUtils.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2023-2024 Infomaniak Network SA + * Copyright (C) 2023-2026 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 @@ -17,7 +17,7 @@ */ package com.infomaniak.mail.utils -import com.infomaniak.mail.data.models.Attachable.AttachmentType +import com.infomaniak.mail.data.models.AttachmentType object AttachableMimeTypeUtils { diff --git a/app/src/main/java/com/infomaniak/mail/utils/AvatarTypeUtils.kt b/app/src/main/java/com/infomaniak/mail/utils/AvatarTypeUtils.kt index 9994fd41dca..bbe4b0d88b7 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/AvatarTypeUtils.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/AvatarTypeUtils.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -31,6 +31,8 @@ import com.infomaniak.mail.data.api.ApiRoutes import com.infomaniak.mail.data.models.Bimi import com.infomaniak.mail.data.models.correspondent.Correspondent import com.infomaniak.mail.data.models.correspondent.MergedContact +import com.infomaniak.mail.data.models.extensions.initials +import com.infomaniak.mail.data.models.extensions.shouldDisplayUserAvatar import com.infomaniak.mail.utils.extensions.MergedContactDictionary object AvatarTypeUtils { diff --git a/app/src/main/java/com/infomaniak/mail/utils/ContactUtils.kt b/app/src/main/java/com/infomaniak/mail/utils/ContactUtils.kt index 61b8afd8854..a90ef4887cc 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/ContactUtils.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/ContactUtils.kt @@ -23,10 +23,12 @@ import android.provider.ContactsContract.CommonDataKinds.Contactables import android.provider.ContactsContract.CommonDataKinds.Email import com.infomaniak.core.common.cancellable import com.infomaniak.core.legacy.utils.hasPermissions +import com.infomaniak.mail.data.api.ApiRoutes import com.infomaniak.mail.data.models.correspondent.Contact import com.infomaniak.mail.data.models.correspondent.ContactAutocompletable import com.infomaniak.mail.data.models.correspondent.MergedContact import com.infomaniak.mail.data.models.correspondent.Recipient +import com.infomaniak.mail.data.models.extensions.createValidRecipientOrNull import com.infomaniak.mail.utils.extensions.MergedContactDictionary import io.sentry.Sentry import kotlinx.coroutines.CoroutineDispatcher @@ -100,7 +102,12 @@ object ContactUtils { apiContact.emails.forEach { email -> Recipient.createValidRecipientOrNull(email = email, name = apiContact.name)?.let { key -> phoneMergedContacts[key]?.updatePhoneContactWithApiContact(apiContact) ?: run { - phoneMergedContacts[key] = MergedContact(email, apiContact, comesFromApi = true) + phoneMergedContacts[key] = MergedContact( + email = email, + apiContact = apiContact, + comesFromApi = true, + avatarUrl = apiContact.avatar?.let { avatar -> ApiRoutes.resource(avatar) } + ) } } } diff --git a/app/src/main/java/com/infomaniak/mail/utils/DraftInitManager.kt b/app/src/main/java/com/infomaniak/mail/utils/DraftInitManager.kt index 7fe9fe25427..983898e1bfe 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/DraftInitManager.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/DraftInitManager.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -27,6 +27,9 @@ import com.infomaniak.mail.data.models.AttachmentUploadStatus import com.infomaniak.mail.data.models.correspondent.Recipient import com.infomaniak.mail.data.models.draft.Draft import com.infomaniak.mail.data.models.draft.Draft.DraftMode +import com.infomaniak.mail.data.models.extensions.getDummySignature +import com.infomaniak.mail.data.models.extensions.getRecipientsForReplyTo +import com.infomaniak.mail.data.models.extensions.setUploadStatus import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.data.models.signature.Signature import com.infomaniak.mail.utils.DraftInitManager.SignatureScore.EXACT_MATCH diff --git a/app/src/main/java/com/infomaniak/mail/utils/ErrorCode.kt b/app/src/main/java/com/infomaniak/mail/utils/ErrorCode.kt index ec3ccdc32ef..c096cc2229b 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/ErrorCode.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/ErrorCode.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2023-2025 Infomaniak Network SA + * Copyright (C) 2023-2026 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 @@ -19,6 +19,7 @@ package com.infomaniak.mail.utils import com.infomaniak.core.network.utils.ApiErrorCode import com.infomaniak.mail.R +import com.infomaniak.mail.data.models.extensions.asErrorCodeTranslated import com.infomaniak.mail.data.models.message.EmojiReactionNotAllowedReason import com.infomaniak.core.legacy.R as RCore @@ -165,15 +166,15 @@ object ErrorCode { ApiErrorCode(ENCRYPTION_PASSWORD_IS_REQUIRED, R.string.encryptedStatePanelIncomplete), // Emoji reactions - EmojiReactionNotAllowedReason.EmojiReactionFolderNotAllowedDraft, - EmojiReactionNotAllowedReason.EmojiReactionFolderNotAllowedScheduledDraft, - EmojiReactionNotAllowedReason.EmojiReactionFolderNotAllowedSpam, - EmojiReactionNotAllowedReason.EmojiReactionFolderNotAllowedTrash, - EmojiReactionNotAllowedReason.EmojiReactionMessageInReplyToNotAllowed, - EmojiReactionNotAllowedReason.EmojiReactionMessageInReplyToNotValid, - EmojiReactionNotAllowedReason.EmojiReactionMessageInReplyToEncrypted, - EmojiReactionNotAllowedReason.EmojiReactionMaxRecipient, - EmojiReactionNotAllowedReason.EmojiReactionRecipientNotAllowed, + EmojiReactionNotAllowedReason.EmojiReactionFolderNotAllowedDraft.asErrorCodeTranslated(), + EmojiReactionNotAllowedReason.EmojiReactionFolderNotAllowedScheduledDraft.asErrorCodeTranslated(), + EmojiReactionNotAllowedReason.EmojiReactionFolderNotAllowedSpam.asErrorCodeTranslated(), + EmojiReactionNotAllowedReason.EmojiReactionFolderNotAllowedTrash.asErrorCodeTranslated(), + EmojiReactionNotAllowedReason.EmojiReactionMessageInReplyToNotAllowed.asErrorCodeTranslated(), + EmojiReactionNotAllowedReason.EmojiReactionMessageInReplyToNotValid.asErrorCodeTranslated(), + EmojiReactionNotAllowedReason.EmojiReactionMessageInReplyToEncrypted.asErrorCodeTranslated(), + EmojiReactionNotAllowedReason.EmojiReactionMaxRecipient.asErrorCodeTranslated(), + EmojiReactionNotAllowedReason.EmojiReactionRecipientNotAllowed.asErrorCodeTranslated(), EmojiReactions.maxReactionReached, EmojiReactions.alreadyUsed, diff --git a/app/src/main/java/com/infomaniak/mail/utils/ExternalUtils.kt b/app/src/main/java/com/infomaniak/mail/utils/ExternalUtils.kt index e5a4034f6bf..d864536af96 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/ExternalUtils.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/ExternalUtils.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2023-2025 Infomaniak Network SA + * Copyright (C) 2023-2026 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 @@ -18,6 +18,7 @@ package com.infomaniak.mail.utils import com.infomaniak.mail.data.models.correspondent.Recipient +import com.infomaniak.mail.data.models.extensions.isExternal import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.utils.extensions.MergedContactDictionary diff --git a/app/src/main/java/com/infomaniak/mail/utils/FetchMessagesManager.kt b/app/src/main/java/com/infomaniak/mail/utils/FetchMessagesManager.kt index 004c5c0606f..46f39f3f0b8 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/FetchMessagesManager.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/FetchMessagesManager.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2023-2025 Infomaniak Network SA + * Copyright (C) 2023-2026 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 @@ -30,11 +30,14 @@ import com.infomaniak.mail.data.cache.mailboxContent.MessageController import com.infomaniak.mail.data.cache.mailboxContent.RefreshController import com.infomaniak.mail.data.cache.mailboxContent.RefreshController.RefreshMode import com.infomaniak.mail.data.cache.mailboxContent.ThreadController -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole +import com.infomaniak.mail.data.models.extensions.getDisplayedMessages +import com.infomaniak.mail.data.models.extensions.getFormattedPreview +import com.infomaniak.mail.data.models.extensions.notificationsIsDisabled import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.data.models.message.Message -import com.infomaniak.mail.data.models.message.Message.Companion.parseMessagesIds import com.infomaniak.mail.data.models.thread.Thread +import com.infomaniak.mail.data.models.extensions.displayedName import com.infomaniak.mail.utils.NotificationPayload.NotificationBehavior import com.infomaniak.mail.utils.NotificationPayload.NotificationBehavior.NotificationType import com.infomaniak.mail.utils.NotificationUtils.Companion.EXTRA_MESSAGE_UID @@ -45,7 +48,6 @@ import io.realm.kotlin.Realm import io.realm.kotlin.ext.copyFromRealm import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.first import okhttp3.OkHttpClient import javax.inject.Inject diff --git a/app/src/main/java/com/infomaniak/mail/utils/FolderRoleUtils.kt b/app/src/main/java/com/infomaniak/mail/utils/FolderRoleUtils.kt index 7ba8841e36b..045b38332fe 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/FolderRoleUtils.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/FolderRoleUtils.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -18,7 +18,7 @@ package com.infomaniak.mail.utils import com.infomaniak.mail.data.cache.mailboxContent.FolderController -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.Snoozable import com.infomaniak.mail.data.models.isSnoozed import com.infomaniak.mail.data.models.message.Message diff --git a/app/src/main/java/com/infomaniak/mail/utils/FolderUiUtils.kt b/app/src/main/java/com/infomaniak/mail/utils/FolderUiUtils.kt index 4def9b22d1c..89607d8c486 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/FolderUiUtils.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/FolderUiUtils.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -18,6 +18,7 @@ package com.infomaniak.mail.utils import com.infomaniak.mail.data.models.Folder +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.FolderUi import com.infomaniak.mail.data.models.forEachNestedItem import com.infomaniak.mail.ui.MainViewModel @@ -87,7 +88,7 @@ private fun Folder.shouldBeExcluded(excludeRoleFolder: Boolean): Boolean { */ fun MainViewModel.DisplayedFolders.flattenAndAddDividerBeforeFirstCustomFolder( dividerType: FolderPickerItem.Divider, - excludedFolderRoles: Set = emptySet(), + excludedFolderRoles: Set = emptySet(), ): List = buildList { runCatchingRealm { default.forEachNestedItem { folderUi, _ -> 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 7e0573c0cb9..5615ee0e472 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/LocalStorageUtils.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/LocalStorageUtils.kt @@ -23,6 +23,7 @@ import com.infomaniak.core.common.cancellable import com.infomaniak.mail.R import com.infomaniak.mail.data.api.ApiRepository import com.infomaniak.mail.data.models.Attachment +import com.infomaniak.mail.data.models.extensions.getCacheFile import com.infomaniak.mail.ui.main.SnackbarManager import io.sentry.Sentry import okhttp3.Response diff --git a/app/src/main/java/com/infomaniak/mail/utils/MessageBodyUtils.kt b/app/src/main/java/com/infomaniak/mail/utils/MessageBodyUtils.kt index df207359a39..d0e8d0d7ae6 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/MessageBodyUtils.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/MessageBodyUtils.kt @@ -21,6 +21,7 @@ import android.content.Context import com.infomaniak.mail.data.models.draft.Draft import com.infomaniak.mail.data.models.message.Body import com.infomaniak.mail.data.models.message.Message +import com.infomaniak.mail.data.models.message.SplitBody import com.infomaniak.mail.data.models.message.SubBody import com.infomaniak.mail.ui.newMessage.BodyContentPayload import com.infomaniak.mail.ui.newMessage.BodyContentType @@ -226,16 +227,5 @@ object MessageBodyUtils { private fun anyCssClassContaining(cssClass: String) = "[class*=$cssClass]" //endregion - data class SplitBody( - val content: String, - val quote: String? = null, - ) { - override fun equals(other: Any?): Boolean { - return other === this || (other is SplitBody && other.content == content && other.quote == quote) - } - - override fun hashCode(): Int = 31 * content.hashCode() + quote.hashCode() - } - data class BodyData(val body: BodyContentPayload, val signature: Element?, val quote: Element?) } diff --git a/app/src/main/java/com/infomaniak/mail/utils/MessageUtils.kt b/app/src/main/java/com/infomaniak/mail/utils/MessageUtils.kt index 3437cfd92c6..58f25898562 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/MessageUtils.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/MessageUtils.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -21,9 +21,10 @@ import com.infomaniak.mail.data.LocalSettings import com.infomaniak.mail.data.cache.mailboxContent.MessageController import com.infomaniak.mail.data.cache.mailboxContent.ThreadController import com.infomaniak.mail.data.models.correspondent.Recipient +import com.infomaniak.mail.data.models.extensions.getDisplayedMessages +import com.infomaniak.mail.data.models.extensions.isMe import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.data.models.message.Message -import com.infomaniak.mail.data.models.thread.Thread object MessageUtils { diff --git a/app/src/main/java/com/infomaniak/mail/utils/NotificationUtils.kt b/app/src/main/java/com/infomaniak/mail/utils/NotificationUtils.kt index 96fdc77be42..f0df9fde0d9 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/NotificationUtils.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/NotificationUtils.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2025 Infomaniak Network SA + * Copyright (C) 2022-2026 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 @@ -41,8 +41,8 @@ import com.infomaniak.mail.R import com.infomaniak.mail.data.LocalSettings import com.infomaniak.mail.data.api.ApiRepository import com.infomaniak.mail.data.cache.mailboxInfo.MailboxController -import com.infomaniak.mail.data.models.draft.Draft.DraftAction import com.infomaniak.mail.data.models.draft.Draft.DraftMode +import com.infomaniak.mail.data.models.draft.DraftAction import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.di.MailboxInfoRealm import com.infomaniak.mail.receivers.NotificationActionsReceiver @@ -391,5 +391,3 @@ class NotificationUtils @Inject constructor( } } } - - diff --git a/app/src/main/java/com/infomaniak/mail/utils/SearchUtils.kt b/app/src/main/java/com/infomaniak/mail/utils/SearchUtils.kt index 6119f5018b3..2f8f6158eb9 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/SearchUtils.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/SearchUtils.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2023-2025 Infomaniak Network SA + * Copyright (C) 2023-2026 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 @@ -26,11 +26,12 @@ import com.infomaniak.mail.data.cache.mailboxContent.ThreadController import com.infomaniak.mail.data.cache.mailboxContent.refreshStrategies.ThreadRecomputations.recomputeMessagesWithContent import com.infomaniak.mail.data.cache.mailboxContent.refreshStrategies.ThreadRecomputations.recomputeThread import com.infomaniak.mail.data.models.Folder -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.isSnoozed import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.data.models.thread.Thread -import com.infomaniak.mail.data.models.thread.Thread.ThreadFilter +import com.infomaniak.mail.data.models.extensions.folderNameRes +import com.infomaniak.mail.data.models.thread.ThreadFilter import com.infomaniak.mail.di.IoDispatcher import com.infomaniak.mail.ui.main.search.NamedFolder import dagger.hilt.android.qualifiers.ApplicationContext diff --git a/app/src/main/java/com/infomaniak/mail/utils/SentryDebug.kt b/app/src/main/java/com/infomaniak/mail/utils/SentryDebug.kt index def97e4477f..63032bb3937 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/SentryDebug.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/SentryDebug.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2023-2024 Infomaniak Network SA + * Copyright (C) 2023-2026 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 @@ -24,6 +24,9 @@ import com.infomaniak.mail.BuildConfig import com.infomaniak.mail.data.cache.mailboxContent.MessageController import com.infomaniak.mail.data.models.Folder import com.infomaniak.mail.data.models.draft.Draft +import com.infomaniak.mail.data.models.extensions.action +import com.infomaniak.mail.data.models.extensions.folder +import com.infomaniak.mail.data.models.extensions.messagesBlocking import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.data.models.thread.Thread diff --git a/app/src/main/java/com/infomaniak/mail/utils/SharedUtils.kt b/app/src/main/java/com/infomaniak/mail/utils/SharedUtils.kt index 69a93ce77d8..0568a7db1b9 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/SharedUtils.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/SharedUtils.kt @@ -30,13 +30,14 @@ import com.infomaniak.mail.data.cache.mailboxContent.RefreshController.RefreshCa import com.infomaniak.mail.data.cache.mailboxContent.RefreshController.RefreshMode import com.infomaniak.mail.data.cache.mailboxContent.ThreadController import com.infomaniak.mail.data.cache.mailboxInfo.MailboxController -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.isSnoozed import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.data.models.snooze.BatchSnoozeResponse.Companion.computeSnoozeResult import com.infomaniak.mail.data.models.snooze.BatchSnoozeResult import com.infomaniak.mail.data.models.thread.Thread +import com.infomaniak.mail.data.models.extensions.getRecipientsForReplyTo import com.infomaniak.mail.ui.MainViewModel import com.infomaniak.mail.utils.JsoupParserUtil.jsoupParseWithLog import com.infomaniak.mail.utils.SharedUtils.Companion.unsnoozeThreadsWithoutRefresh diff --git a/app/src/main/java/com/infomaniak/mail/utils/UiUtils.kt b/app/src/main/java/com/infomaniak/mail/utils/UiUtils.kt index 58a4d455156..253e9bcd179 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/UiUtils.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/UiUtils.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2024 Infomaniak Network SA + * Copyright (C) 2022-2026 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 @@ -34,6 +34,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import com.infomaniak.mail.R import com.infomaniak.mail.data.models.correspondent.Correspondent +import com.infomaniak.mail.data.models.extensions.isMe import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/infomaniak/mail/utils/Utils.kt b/app/src/main/java/com/infomaniak/mail/utils/Utils.kt index d370351a97b..4b17bbba0e1 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/Utils.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/Utils.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2025 Infomaniak Network SA + * Copyright (C) 2022-2026 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 @@ -25,7 +25,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import com.infomaniak.core.legacy.utils.UtilsUi.openUrl import com.infomaniak.mail.CHATBOT_URL -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import io.sentry.Sentry import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.TimeoutCancellationException @@ -42,7 +42,6 @@ object Utils { const val MIMETYPE_UNKNOWN = "application/octet-stream" const val NUMBER_OF_OLD_UIDS_TO_FETCH = 10_000 // Total number of Messages we will ever fetch in a Folder history - const val NUMBER_OF_OLD_MESSAGES_TO_FETCH = 500 // Number of Messages we want to fetch when 1st opening a Folder const val PAGE_SIZE = 50 // Beware: the API refuses a PAGE_SIZE bigger than 200 const val MIN_THREADS_TO_GET_ENOUGH_THREADS = PAGE_SIZE / 2 // We want to get at least 25 Threads when we fetch 1 old page const val MAX_OLD_PAGES_TO_FETCH_TO_GET_ENOUGH_THREADS = 5 // We don't want to spam the API, so we just get a few pages diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExt.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExt.kt index ae1198d532f..d777158bafe 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExt.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/AttachmentExt.kt @@ -31,6 +31,13 @@ import com.infomaniak.mail.R import com.infomaniak.mail.data.api.ApiRepository import com.infomaniak.mail.data.cache.mailboxContent.DraftController import com.infomaniak.mail.data.models.Attachment +import com.infomaniak.mail.data.models.extensions.backupLocalData +import com.infomaniak.mail.data.models.extensions.getCacheFile +import com.infomaniak.mail.data.models.extensions.getFileTypeFromMimeType +import com.infomaniak.mail.data.models.extensions.getUploadLocalFile +import com.infomaniak.mail.data.models.extensions.hasUsableCache +import com.infomaniak.mail.data.models.extensions.isInlineCachedFile +import com.infomaniak.mail.data.models.extensions.safeMimeType import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.ui.main.SnackbarManager import com.infomaniak.mail.ui.main.thread.actions.DownloadAttachmentProgressDialogArgs diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/ConfirmationPopupExt.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/ConfirmationPopupExt.kt index fbc9112e986..4e952ca58f8 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/ConfirmationPopupExt.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/ConfirmationPopupExt.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 @@ -19,7 +19,7 @@ package com.infomaniak.mail.utils.extensions import com.infomaniak.core.legacy.utils.context import com.infomaniak.mail.R -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.ui.alertDialogs.DescriptionAlertDialog import com.infomaniak.mail.utils.Utils diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/Extensions.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/Extensions.kt index 609fc6d1be1..def9fd5f7dc 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/Extensions.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/Extensions.kt @@ -70,6 +70,7 @@ import com.infomaniak.core.common.utils.startOfTheDay import com.infomaniak.core.common.utils.startOfTheWeek import com.infomaniak.core.legacy.utils.hideKeyboard import com.infomaniak.core.legacy.utils.removeAccents +import com.infomaniak.core.login.InfomaniakLogin import com.infomaniak.core.network.LOGIN_ENDPOINT_URL import com.infomaniak.core.network.api.ApiController import com.infomaniak.core.network.models.ApiResponse @@ -78,7 +79,6 @@ import com.infomaniak.core.sentry.SentryLog import com.infomaniak.core.ui.showToast import com.infomaniak.core.ui.view.utils.SnackbarUtils.showSnackbar import com.infomaniak.dragdropswiperecyclerview.DragDropSwipeRecyclerView -import com.infomaniak.core.login.InfomaniakLogin import com.infomaniak.mail.BuildConfig import com.infomaniak.mail.R import com.infomaniak.mail.data.LocalSettings.ThreadDensity @@ -86,12 +86,14 @@ import com.infomaniak.mail.data.cache.mailboxContent.FolderController import com.infomaniak.mail.data.cache.mailboxContent.ImpactedFolders import com.infomaniak.mail.data.models.Attachment import com.infomaniak.mail.data.models.Folder -import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.FolderRole import com.infomaniak.mail.data.models.SnoozeState import com.infomaniak.mail.data.models.correspondent.Correspondent import com.infomaniak.mail.data.models.correspondent.MergedContact import com.infomaniak.mail.data.models.correspondent.Recipient import com.infomaniak.mail.data.models.draft.Draft.DraftMode +import com.infomaniak.mail.data.models.extensions.getLocalizedName +import com.infomaniak.mail.data.models.extensions.isMe import com.infomaniak.mail.data.models.javascriptBridge.EditorJavascriptBridge import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.data.models.signature.Signature @@ -120,7 +122,6 @@ import com.infomaniak.mail.utils.Utils.kSyncAccountUri import com.infomaniak.mail.utils.WebViewUtils import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.query.Sort -import io.realm.kotlin.types.RealmInstant import org.jsoup.nodes.Document import java.util.Calendar import java.util.Date @@ -152,13 +153,6 @@ fun String?.getStartAndEndOfPlusEmail(): Pair { } //region Date -fun RealmInstant.toDate(): Date = Date(epochSeconds * 1_000L + nanosecondsOfSecond / 1_000L) - -fun Date.toRealmInstant(): RealmInstant { - val seconds = time / 1_000L - val nanoseconds = (time - seconds * 1_000L).toInt() - return RealmInstant.from(seconds, nanoseconds) -} fun Date.isSmallerThanDays(daysAgo: Int): Boolean { val lastDay = Calendar.getInstance().apply { diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/RealmExt.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/RealmExt.kt index 90bcef8718e..4d5b0c437a5 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/RealmExt.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/RealmExt.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2025 Infomaniak Network SA + * Copyright (C) 2025-2026 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 diff --git a/app/src/main/java/com/infomaniak/mail/views/AttachmentDetailsView.kt b/app/src/main/java/com/infomaniak/mail/views/AttachmentDetailsView.kt index cb3049292fe..d24a3b092bb 100644 --- a/app/src/main/java/com/infomaniak/mail/views/AttachmentDetailsView.kt +++ b/app/src/main/java/com/infomaniak/mail/views/AttachmentDetailsView.kt @@ -29,6 +29,7 @@ import com.infomaniak.core.legacy.utils.getAttributes import com.infomaniak.core.ui.view.extension.setMarginsRelative import com.infomaniak.mail.R import com.infomaniak.mail.data.models.Attachable +import com.infomaniak.mail.data.models.extensions.getFileTypeFromMimeType import com.infomaniak.mail.databinding.ViewAttachmentDetailsBinding import com.infomaniak.core.legacy.R as RCore diff --git a/app/src/main/java/com/infomaniak/mail/views/AvatarView.kt b/app/src/main/java/com/infomaniak/mail/views/AvatarView.kt index 871f157c9ed..9ea53b2e030 100644 --- a/app/src/main/java/com/infomaniak/mail/views/AvatarView.kt +++ b/app/src/main/java/com/infomaniak/mail/views/AvatarView.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2022-2025 Infomaniak Network SA + * Copyright (C) 2022-2026 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 @@ -39,6 +39,8 @@ import com.infomaniak.mail.R import com.infomaniak.mail.data.models.Bimi import com.infomaniak.mail.data.models.correspondent.Correspondent import com.infomaniak.mail.data.models.correspondent.MergedContact +import com.infomaniak.mail.data.models.extensions.initials +import com.infomaniak.mail.data.models.extensions.shouldDisplayUserAvatar import com.infomaniak.mail.databinding.ViewAvatarBinding import com.infomaniak.mail.utils.AccountUtils import com.infomaniak.mail.utils.AvatarTypeUtils.correspondentAvatarColors diff --git a/app/src/main/java/com/infomaniak/mail/views/itemViews/SelectableItemView.kt b/app/src/main/java/com/infomaniak/mail/views/itemViews/SelectableItemView.kt index 4ac5161fd6b..a4816d15be4 100644 --- a/app/src/main/java/com/infomaniak/mail/views/itemViews/SelectableItemView.kt +++ b/app/src/main/java/com/infomaniak/mail/views/itemViews/SelectableItemView.kt @@ -1,6 +1,6 @@ /* * Infomaniak Mail - Android - * Copyright (C) 2023-2025 Infomaniak Network SA + * Copyright (C) 2023-2026 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 @@ -28,6 +28,7 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.core.widget.TextViewCompat import com.infomaniak.mail.R import com.infomaniak.mail.data.models.Folder +import com.infomaniak.mail.data.models.extensions.getLocalizedName import com.infomaniak.mail.utils.Utils.runCatchingRealm import com.infomaniak.mail.utils.extensions.getAttributeColor import androidx.appcompat.R as RAndroid diff --git a/app/src/main/java/com/infomaniak/mail/workers/MailActionsManager.kt b/app/src/main/java/com/infomaniak/mail/workers/MailActionsManager.kt index b29f63d1b83..ffa523f93e3 100644 --- a/app/src/main/java/com/infomaniak/mail/workers/MailActionsManager.kt +++ b/app/src/main/java/com/infomaniak/mail/workers/MailActionsManager.kt @@ -32,7 +32,9 @@ import com.infomaniak.mail.data.cache.RealmDatabase import com.infomaniak.mail.data.cache.mailboxContent.DraftController import com.infomaniak.mail.data.models.AttachmentUploadStatus import com.infomaniak.mail.data.models.draft.Draft -import com.infomaniak.mail.data.models.draft.Draft.DraftAction +import com.infomaniak.mail.data.models.draft.DraftAction +import com.infomaniak.mail.data.models.extensions.action +import com.infomaniak.mail.data.models.extensions.getDefaultSignatureWithFallback import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.utils.ApiErrorException import com.infomaniak.mail.utils.ErrorCode diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml index 17866b587ba..70ce2e8f9ae 100644 --- a/app/src/main/res/navigation/main_navigation.xml +++ b/app/src/main/res/navigation/main_navigation.xml @@ -1,6 +1,6 @@