Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(MyKSuite-04): Add storage banner #2184

Merged
merged 7 commits into from
Feb 20, 2025
2 changes: 2 additions & 0 deletions app/src/main/java/com/infomaniak/mail/MainApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ open class MainApplication : Application(), ImageLoaderFactory, DefaultLifecycle
configureInfomaniakCore()
notificationUtils.initNotificationChannel()
configureHttpClient()

localSettings.storageBannerDisplayAppLaunches++
}

override fun onStart(owner: LifecycleOwner) {
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/com/infomaniak/mail/data/LocalSettings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ class LocalSettings private constructor(context: Context) : SharedValues {
var showWebViewOutdated by sharedValue("showWebViewOutdatedKey", true)
var accessTokenApiCallRecord by sharedValue<ApiCallRecord>("accessTokenApiCallRecordKey", null)
var lastSelectedScheduleEpoch by sharedValue<Long>("lastSelectedScheduleEpochKey", null)
var storageBannerDisplayAppLaunches by sharedValue("storageBannerDisplayAppLaunchesKey", 0)
var hasClosedStorageBanner by sharedValue("hasClosedStorageBannerKey", false)

fun removeSettings() = sharedPreferences.transaction { clear() }

Expand All @@ -93,6 +95,11 @@ class LocalSettings private constructor(context: Context) : SharedValues {
firebaseRegisteredUsers = mutableSetOf()
}

fun resetStorageBannerSettings() {
hasClosedStorageBanner = true
storageBannerDisplayAppLaunches = 0
}

enum class EmailForwarding(@StringRes val localisedNameRes: Int) {
IN_BODY(R.string.settingsTransferInBody),
AS_ATTACHMENT(R.string.settingsTransferAsAttachment),
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/infomaniak/mail/data/models/Quotas.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class Quotas : EmbeddedRealmObject {

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

val isFull get() = getProgress() >= 100

fun getText(context: Context): String {

val usedSize = context.formatShortFileSize(size)
Expand Down
21 changes: 21 additions & 0 deletions app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.infomaniak.lib.core.utils.SentryLog
import com.infomaniak.lib.core.utils.SingleLiveEvent
import com.infomaniak.mail.MatomoMail.trackMultiSelectionEvent
import com.infomaniak.mail.R
import com.infomaniak.mail.data.LocalSettings
import com.infomaniak.mail.data.api.ApiRepository
import com.infomaniak.mail.data.api.ApiRoutes
import com.infomaniak.mail.data.cache.RealmDatabase
Expand Down Expand Up @@ -64,6 +65,7 @@ import com.infomaniak.mail.utils.Utils.isPermanentDeleteFolder
import com.infomaniak.mail.utils.Utils.runCatchingRealm
import com.infomaniak.mail.utils.extensions.*
import com.infomaniak.mail.views.itemViews.AvatarMergedContactData
import com.infomaniak.mail.views.itemViews.MyKSuiteStorageBanner.StorageLevel
import dagger.hilt.android.lifecycle.HiltViewModel
import io.realm.kotlin.Realm
import io.realm.kotlin.ext.toRealmList
Expand Down Expand Up @@ -109,6 +111,9 @@ class MainViewModel @Inject constructor(
private val ioCoroutineContext = viewModelScope.coroutineContext(ioDispatcher)
private var refreshEverythingJob: Job? = null

@Inject
lateinit var localSettings: LocalSettings

val isDownloadingChanges: MutableLiveData<Boolean> = MutableLiveData(false)
val isMovedToNewFolder = SingleLiveEvent<Boolean>()
val toggleLightThemeForMessage = SingleLiveEvent<Message>()
Expand Down Expand Up @@ -162,6 +167,22 @@ class MainViewModel @Inject constructor(
it?.let(quotasController::getQuotasAsync) ?: emptyFlow()
}.asLiveData(ioCoroutineContext)

val storageBannerStatus = currentQuotasLive.map { quotas ->
when {
quotas == null -> null
quotas.isFull -> StorageLevel.Full
quotas.getProgress() > StorageLevel.WARNING_THRESHOLD -> {
if (!localSettings.hasClosedStorageBanner || localSettings.storageBannerDisplayAppLaunches % 10 == 0) {
localSettings.hasClosedStorageBanner = false
StorageLevel.Warning
} else {
StorageLevel.Normal
}
}
else -> StorageLevel.Normal
}
}

val currentPermissionsLive = _currentMailboxObjectId.flatMapLatest {
it?.let(permissionsController::getPermissionsAsync) ?: emptyFlow()
}.asLiveData(ioCoroutineContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ class ThreadListFragment : TwoPaneFragment() {
setupListeners()
setupUserAvatar()
setupUnreadCountChip()
setupMyKSuiteStorageBanner()

threadListMultiSelection.initMultiSelection(
mainViewModel = mainViewModel,
Expand Down Expand Up @@ -527,6 +528,20 @@ class ThreadListFragment : TwoPaneFragment() {
}
}

private fun setupMyKSuiteStorageBanner() = with(localSettings) {
mainViewModel.storageBannerStatus.observeNotNull(viewLifecycleOwner) { storageBannerStatus ->
binding.myKSuiteStorageBanner.apply {
storageLevel = storageBannerStatus
setupListener(
onCloseButtonClicked = {
binding.myKSuiteStorageBanner.isGone = true
resetStorageBannerSettings()
}
)
}
}
}

private fun observeNetworkStatus() {
viewLifecycleOwner.lifecycleScope.launch {
mainViewModel.isNetworkAvailable.collect { isNetworkAvailable ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -718,9 +718,9 @@ class NewMessageFragment : Fragment() {
sendButton.isEnabled = it
}

scheduleButton.setOnClickListener { navigateToScheduleSendBottomSheet() }
scheduleButton.setOnClickListener { if (checkMailboxStorage()) navigateToScheduleSendBottomSheet() }

sendButton.setOnClickListener { tryToSendEmail() }
sendButton.setOnClickListener { if (checkMailboxStorage()) tryToSendEmail() }
}

private fun navigateToScheduleSendBottomSheet() {
Expand Down Expand Up @@ -768,6 +768,16 @@ class NewMessageFragment : Fragment() {
}
}

private fun checkMailboxStorage(): Boolean {
val isMailboxFull = newMessageViewModel.currentMailbox.quotas?.isFull == true
if (isMailboxFull) {
trackNewMessageEvent("trySendingWithMailboxFull")
showSnackbar(R.string.myKSuiteSpaceFullAlert)
}

return !isMailboxFull
}

private fun Activity.finishAppAndRemoveTaskIfNeeded() {
if (isTaskRoot) finishAndRemoveTask() else finish()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Infomaniak Mail - Android
* Copyright (C) 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 <http://www.gnu.org/licenses/>.
*/
package com.infomaniak.mail.views.itemViews

import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.annotation.ColorRes
import androidx.annotation.StringRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.infomaniak.mail.R
import com.infomaniak.mail.databinding.ViewBannerMyKsuiteStorageBinding

class MyKSuiteStorageBanner @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : ConstraintLayout(context, attrs, defStyleAttr) {

private val binding by lazy { ViewBannerMyKsuiteStorageBinding.inflate(LayoutInflater.from(context), this, true) }

var storageLevel = StorageLevel.Normal
set(value) {
binding.root.isGone = value == StorageLevel.Normal
if (value != StorageLevel.Normal) setStorageLevelUi(value)
field = value
}


fun setupListener(onCloseButtonClicked: () -> Unit) {
binding.closeButton.setOnClickListener { onCloseButtonClicked() }
}

private fun setStorageLevelUi(newStorageLevel: StorageLevel) = with(binding) {
if (newStorageLevel == storageLevel) return@with

title.text = context.getText(newStorageLevel.titleRes)
description.text = context.getText(newStorageLevel.descriptionRes)
alertIcon.setColorFilter(context.getColor(newStorageLevel.iconColorRes))

closeButton.isVisible = newStorageLevel == StorageLevel.Warning
}

enum class StorageLevel(
@ColorRes val iconColorRes: Int,
@StringRes val titleRes: Int,
@StringRes val descriptionRes: Int,
) {
Normal(
iconColorRes = ResourcesCompat.ID_NULL,
titleRes = ResourcesCompat.ID_NULL,
descriptionRes = ResourcesCompat.ID_NULL,
),
Warning(
iconColorRes = R.color.orangeWarning,
titleRes = R.string.myKSuiteQuotasAlertTitle,
descriptionRes = R.string.myKSuiteQuotasAlertDescription,
),
Full(
iconColorRes = R.color.redDestructiveAction,
titleRes = R.string.myKSuiteQuotasAlertFullTitle,
descriptionRes = R.string.myKSuiteQuotasAlertFullDescription,
);

companion object {
const val WARNING_THRESHOLD = 85
}
}
}
5 changes: 5 additions & 0 deletions app/src/main/res/layout/fragment_thread_list.xml
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@
android:layout_height="match_parent"
android:orientation="vertical">

<com.infomaniak.mail.views.itemViews.MyKSuiteStorageBanner
android:id="@+id/myKSuiteStorageBanner"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<com.infomaniak.mail.views.itemViews.BannerWithActionView
android:id="@+id/installUpdate"
android:layout_width="match_parent"
Expand Down
74 changes: 74 additions & 0 deletions app/src/main/res/layout/view_banner_my_ksuite_storage.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Infomaniak Mail - Android
~ Copyright (C) 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 <http://www.gnu.org/licenses/>.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/informationBlockBackground"
android:paddingVertical="@dimen/marginStandardSmall"
android:visibility="gone"
tools:visibility="visible">

<ImageView
android:id="@+id/alertIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/marginStandardMedium"
android:importantForAccessibility="no"
android:src="@drawable/ic_warning"
app:layout_constraintBottom_toBottomOf="@id/title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/title"
tools:tint="@color/orange_light" />

<TextView
android:id="@+id/title"
style="@style/BodySmallMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/alternativeMargin"
android:layout_marginEnd="@dimen/marginStandardVerySmall"
app:layout_constraintEnd_toStartOf="@id/closeButton"
app:layout_constraintStart_toEndOf="@id/alertIcon"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/myKSuiteQuotasAlertTitle" />

<com.google.android.material.button.MaterialButton
android:id="@+id/closeButton"
style="@style/IconButtonSmall"
android:layout_marginEnd="@dimen/marginStandardSmall"
app:icon="@drawable/ic_close_small"
app:layout_constraintBottom_toBottomOf="@id/title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/title"
app:layout_constraintTop_toTopOf="@id/title" />

<TextView
android:id="@+id/description"
style="@style/Label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/marginStandardVerySmall"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/title"
app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintTop_toBottomOf="@id/title"
tools:text="@string/myKSuiteQuotasAlertDescription" />

</androidx.constraintlayout.widget.ConstraintLayout>
6 changes: 6 additions & 0 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
<string name="buttonDownloadAll">Alle herunterladen</string>
<string name="buttonFeedback">Rückmeldung</string>
<string name="buttonFolders">Ordner</string>
<string name="buttonFreeTrial">Kostenlos ausprobieren</string>
<string name="buttonHelp">Hilfe</string>
<string name="buttonHyperlink">Einen Hyperlink hinzufügen</string>
<string name="buttonImportEmails">E-Mails importieren</string>
Expand Down Expand Up @@ -358,6 +359,11 @@
<item quantity="one">%d ausgewählt</item>
<item quantity="other">%d ausgewählt</item>
</plurals>
<string name="myKSuiteQuotasAlertDescription">Wenn Sie dieses Limit erreicht haben, können Sie keine Nachrichten mehr senden oder empfangen. Geben Sie Speicherplatz frei oder wechseln Sie zu my kSuite+.</string>
<string name="myKSuiteQuotasAlertFullDescription">Geben Sie Speicherplatz frei oder wechseln Sie zu my kSuite+.</string>
<string name="myKSuiteQuotasAlertFullTitle">Empfang und Versand von Nachrichten blockiert</string>
<string name="myKSuiteQuotasAlertTitle">Speichergrenze fast erreicht</string>
<string name="myKSuiteSpaceFullAlert">Es gibt nicht genügend Speicherplatz, um diese Aktion durchzuführen.</string>
<string name="newAccountDescription">Kommunizieren und speichern Sie Ihre Dokumente und Fotos in einem werbefreien Ökosystem, das Ihre Privatsphäre respektiert</string>
<string name="newAccountStorageDrive">15 GB kDrive-Speicher</string>
<string name="newAccountStorageMail">20 GB Mail-Speicher</string>
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/res/values-es/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
<string name="buttonDownloadAll">Descargar todo</string>
<string name="buttonFeedback">Comentarios</string>
<string name="buttonFolders">Carpetas</string>
<string name="buttonFreeTrial">Pruébelo gratis</string>
<string name="buttonHelp">Ayuda</string>
<string name="buttonHyperlink">Añadir un hipervínculo</string>
<string name="buttonImportEmails">Importar correos electrónicos</string>
Expand Down Expand Up @@ -358,6 +359,11 @@
<item quantity="one">%d seleccionado</item>
<item quantity="other">%d seleccionados</item>
</plurals>
<string name="myKSuiteQuotasAlertDescription">Una vez alcanzado este límite, ya no podrás enviar ni recibir mensajes. Libera espacio o actualiza a my kSuite+.</string>
<string name="myKSuiteQuotasAlertFullDescription">Libera espacio o actualízate a my kSuite+.</string>
<string name="myKSuiteQuotasAlertFullTitle">Recepción y envío de mensajes bloqueados</string>
<string name="myKSuiteQuotasAlertTitle">Límite de almacenamiento casi alcanzado</string>
<string name="myKSuiteSpaceFullAlert">No hay espacio suficiente para llevar a cabo esta acción.</string>
<string name="newAccountDescription">Comunica y almacena tus documentos y fotos en un ecosistema sin publicidad que respeta tu privacidad</string>
<string name="newAccountStorageDrive">15 GB de almacenamiento kDrive</string>
<string name="newAccountStorageMail">20 GB de almacenamiento de correo</string>
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@
<string name="buttonDownloadAll">Tout télécharger</string>
<string name="buttonFeedback">Feedback</string>
<string name="buttonFolders">Dossiers</string>
<string name="buttonFreeTrial">Essayer gratuitement</string>
<string name="buttonHelp">Aide</string>
<string name="buttonHyperlink">Ajouter un lien hypertexte</string>
<string name="buttonImportEmails">Importer des e-mails</string>
Expand Down Expand Up @@ -366,6 +367,11 @@
<item quantity="other">%d sélectionnés</item>
<item quantity="many">%d de sélectionnés</item>
</plurals>
<string name="myKSuiteQuotasAlertDescription">Une fois atteinte, vous ne pourrez plus recevoir ou envoyer de messages. Libérez de l’espace ou faites évoluer votre offre vers my kSuite+.</string>
<string name="myKSuiteQuotasAlertFullDescription">Libérer de l’espace ou faites évoluer votre offre vers my kSuite+.</string>
<string name="myKSuiteQuotasAlertFullTitle">Réception et envoi de messages bloqués</string>
<string name="myKSuiteQuotasAlertTitle">Limite de stockage presque atteinte</string>
<string name="myKSuiteSpaceFullAlert">L’espace de stockage n’est pas suffisant pour effectuer cette action.</string>
<string name="newAccountDescription">Communiquez et stockez vos documents et photos dans un écosystème sans publicité qui respecte votre vie privée</string>
<string name="newAccountStorageDrive">15 Go de stockage kDrive</string>
<string name="newAccountStorageMail">20 Go de stockage Mail</string>
Expand Down
Loading
Loading