Skip to content

Commit

Permalink
feat(MyKSuite-04): Add storage banner (#2184)
Browse files Browse the repository at this point in the history
  • Loading branch information
FabianDevel authored Feb 20, 2025
2 parents c9d0a97 + f79256c commit d95a9b0
Show file tree
Hide file tree
Showing 14 changed files with 255 additions and 2 deletions.
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 resetStorageBannerAppLaunches() {
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
resetStorageBannerAppLaunches()
}
)
}
}
}

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

0 comments on commit d95a9b0

Please sign in to comment.