diff --git a/CHANGELOG.md b/CHANGELOG.md index 734b62088c5..64038868f7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ ownCloud admins and users. * Enhancement - Added text labels for BottomNavigationView: [#4484](https://github.com/owncloud/android/issues/4484) * Enhancement - OCIS Light Users: [#4490](https://github.com/owncloud/android/issues/4490) * Enhancement - Enforce OIDC auth flow via branding: [#4500](https://github.com/owncloud/android/issues/4500) +* Enhancement - Technical improvements for user quota: [#4521](https://github.com/owncloud/android/issues/4521) ## Details @@ -124,6 +125,14 @@ ownCloud admins and users. https://github.com/owncloud/android/issues/4500 https://github.com/owncloud/android/pull/4516 +* Enhancement - Technical improvements for user quota: [#4521](https://github.com/owncloud/android/issues/4521) + + A new use case has been added to fetch the user quota as a flow. Also, all + unnecessary calls from DrawerActivity have been removed. + + https://github.com/owncloud/android/issues/4521 + https://github.com/owncloud/android/pull/4525 + # Changelog for ownCloud Android Client [4.4.1] (2024-10-30) The following sections list the changes in ownCloud Android Client 4.4.1 relevant to diff --git a/changelog/unreleased/4525 b/changelog/unreleased/4525 new file mode 100644 index 00000000000..20f5b8e346b --- /dev/null +++ b/changelog/unreleased/4525 @@ -0,0 +1,7 @@ +Enhancement: Technical improvements for user quota + +A new use case has been added to fetch the user quota as a flow. +Also, all unnecessary calls from DrawerActivity have been removed. + +https://github.com/owncloud/android/issues/4521 +https://github.com/owncloud/android/pull/4525 diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt index 1ff523b3797..6ba70e60e17 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt @@ -5,6 +5,7 @@ * @author Abel García de Prada * @author Juan Carlos Garrote Gascón * @author Aitor Ballesteros Pavón + * @author Jorge Aguado Recio * * Copyright (C) 2024 ownCloud GmbH. * @@ -101,6 +102,7 @@ import com.owncloud.android.domain.transfers.usecases.GetAllTransfersAsStreamUse import com.owncloud.android.domain.transfers.usecases.GetAllTransfersUseCase import com.owncloud.android.domain.transfers.usecases.UpdatePendingUploadsPathUseCase import com.owncloud.android.domain.user.usecases.GetStoredQuotaUseCase +import com.owncloud.android.domain.user.usecases.GetStoredQuotaAsStreamUseCase import com.owncloud.android.domain.user.usecases.GetUserAvatarAsyncUseCase import com.owncloud.android.domain.user.usecases.GetUserInfoAsyncUseCase import com.owncloud.android.domain.user.usecases.GetUserQuotasUseCase @@ -252,6 +254,7 @@ val useCaseModule = module { factoryOf(::UploadFilesFromSystemUseCase) // User + factoryOf(::GetStoredQuotaAsStreamUseCase) factoryOf(::GetStoredQuotaUseCase) factoryOf(::GetUserAvatarAsyncUseCase) factoryOf(::GetUserInfoAsyncUseCase) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/common/DrawerViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/common/DrawerViewModel.kt index 01ba4de8ce8..8d8bea931a4 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/common/DrawerViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/common/DrawerViewModel.kt @@ -25,14 +25,12 @@ package com.owncloud.android.presentation.common import android.accounts.Account import android.content.Context -import androidx.lifecycle.LiveData -import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.owncloud.android.R import com.owncloud.android.data.providers.LocalStorageProvider import com.owncloud.android.domain.user.model.UserQuota -import com.owncloud.android.domain.user.usecases.GetStoredQuotaUseCase +import com.owncloud.android.domain.user.usecases.GetStoredQuotaAsStreamUseCase import com.owncloud.android.domain.user.usecases.GetUserQuotasUseCase import com.owncloud.android.domain.utils.Event import com.owncloud.android.extensions.ViewModelExt.runUseCaseWithResult @@ -40,50 +38,46 @@ import com.owncloud.android.presentation.authentication.AccountUtils import com.owncloud.android.providers.ContextProvider import com.owncloud.android.providers.CoroutinesDispatcherProvider import com.owncloud.android.usecases.accounts.RemoveAccountUseCase +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import timber.log.Timber class DrawerViewModel( - private val getStoredQuotaUseCase: GetStoredQuotaUseCase, + getStoredQuotaAsStreamUseCase: GetStoredQuotaAsStreamUseCase, private val removeAccountUseCase: RemoveAccountUseCase, private val getUserQuotasUseCase: GetUserQuotasUseCase, private val localStorageProvider: LocalStorageProvider, private val coroutinesDispatcherProvider: CoroutinesDispatcherProvider, private val contextProvider: ContextProvider, + accountName: String, ) : ViewModel() { - private val _userQuota = MediatorLiveData>>() - val userQuota: LiveData>> = _userQuota + private val _userQuota = MutableStateFlow>>?>(null) + val userQuota: StateFlow>>?> = _userQuota - fun getStoredQuota( - accountName: String - ) = runUseCaseWithResult( - coroutineDispatcher = coroutinesDispatcherProvider.io, - requiresConnection = false, - showLoading = true, - liveData = _userQuota, - useCase = getStoredQuotaUseCase, - useCaseParams = GetStoredQuotaUseCase.Params(accountName = accountName) - ) + init { + runUseCaseWithResult( + coroutineDispatcher = coroutinesDispatcherProvider.io, + requiresConnection = false, + showLoading = true, + flow = _userQuota, + useCase = getStoredQuotaAsStreamUseCase, + useCaseParams = GetStoredQuotaAsStreamUseCase.Params(accountName = accountName), + ) + } fun getAccounts(context: Context): List { return AccountUtils.getAccounts(context).asList() } - fun getCurrentAccount(context: Context): Account? { - return AccountUtils.getCurrentOwnCloudAccount(context) - } - fun getUsernameOfAccount(accountName: String): String { return AccountUtils.getUsernameOfAccount(accountName) } fun getFeedbackMail() = contextProvider.getString(R.string.mail_feedback) - fun setCurrentAccount(context: Context, accountName: String): Boolean { - return AccountUtils.setCurrentOwnCloudAccount(context, accountName) - } - fun removeAccount(context: Context) { viewModelScope.launch(coroutinesDispatcherProvider.io) { val loggedAccounts = AccountUtils.getAccounts(context) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt index 10a17618690..a6887bdecd8 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.kt @@ -57,8 +57,10 @@ import com.google.android.material.navigation.NavigationView import com.owncloud.android.R import com.owncloud.android.domain.capabilities.model.OCCapability import com.owncloud.android.domain.files.model.FileListOption +import com.owncloud.android.domain.user.model.UserQuota import com.owncloud.android.domain.user.model.UserQuotaState import com.owncloud.android.domain.utils.Event +import com.owncloud.android.extensions.collectLatestLifecycleFlow import com.owncloud.android.extensions.goToUrl import com.owncloud.android.extensions.openPrivacyPolicy import com.owncloud.android.extensions.sendEmailOrOpenFeedbackDialogAction @@ -82,7 +84,11 @@ import timber.log.Timber */ abstract class DrawerActivity : ToolbarActivity() { - private val drawerViewModel by viewModel() + private val drawerViewModel by viewModel { + parametersOf( + account?.name + ) + } private val capabilitiesViewModel by viewModel { parametersOf( account?.name @@ -287,7 +293,6 @@ abstract class DrawerActivity : ToolbarActivity() { open fun openDrawer() { getDrawerLayout()?.openDrawer(GravityCompat.START) findViewById(R.id.nav_view).requestFocus() - drawerViewModel.getStoredQuota(account.name) } /** @@ -305,115 +310,118 @@ abstract class DrawerActivity : ToolbarActivity() { */ private fun updateQuota() { Timber.d("Update Quota") - val account = drawerViewModel.getCurrentAccount(this) ?: return - drawerViewModel.getStoredQuota(account.name) - drawerViewModel.userQuota.observe(this) { event -> - when (val uiResult = event.peekContent()) { - is UIResult.Success -> { - uiResult.data?.let { userQuota -> - when { - userQuota.available == -4L -> { // Light users (oCIS) - getAccountQuotaText()?.text = getString(R.string.drawer_unavailable_used_storage) - getAccountQuotaBar()?.isVisible = false - getAccountQuotaStatusText()?.isVisible = false + collectLatestLifecycleFlow(drawerViewModel.userQuota) { event -> + event?.let { + when (val uiResult = event.peekContent()) { + is UIResult.Success -> { + uiResult.data?.let { userQuotaData -> + collectLatestLifecycleFlow(userQuotaData) { quota -> + quota?.let { onUpdateQuotaIsSuccessful(it) } } + } + } + is UIResult.Loading -> getAccountQuotaText()?.text = getString(R.string.drawer_loading_quota) + is UIResult.Error -> getAccountQuotaText()?.text = getString(R.string.drawer_unavailable_used_storage) + } + } + } + } - userQuota.available < 0 -> { // Pending, unknown or unlimited free storage - getAccountQuotaBar()?.apply { - isVisible = true - progress = 0 - progressTintList = ColorStateList.valueOf(resources.getColor(R.color.color_accent)) - } - getAccountQuotaText()?.text = String.format( - getString(R.string.drawer_unavailable_free_storage), - DisplayUtils.bytesToHumanReadable(userQuota.used, this, true) - ) - getAccountQuotaStatusText()?.visibility = View.GONE - } + private fun onUpdateQuotaIsSuccessful(userQuota: UserQuota) { + when { + userQuota.available == -4L -> { // Light users (oCIS) + getAccountQuotaText()?.text = getString(R.string.drawer_unavailable_used_storage) + getAccountQuotaBar()?.isVisible = false + getAccountQuotaStatusText()?.isVisible = false + } - userQuota.available == 0L -> { // Exceeded storage. The value is over 100%. - getAccountQuotaBar()?.apply { - isVisible = true - progress = 100 - progressTintList = ColorStateList.valueOf(resources.getColor(R.color.quota_exceeded)) - } - - if (userQuota.state == UserQuotaState.EXCEEDED) { // oCIS - getAccountQuotaText()?.apply { - text = String.format( - getString(R.string.drawer_quota), - DisplayUtils.bytesToHumanReadable(userQuota.used, context, true), - DisplayUtils.bytesToHumanReadable(userQuota.getTotal(), context, true), - userQuota.getRelative() - ) - } - getAccountQuotaStatusText()?.apply { - visibility = View.VISIBLE - text = getString(R.string.drawer_exceeded_quota) - } - } else { // oC10 - getAccountQuotaText()?.text = getString(R.string.drawer_exceeded_quota) - getAccountQuotaStatusText()?.visibility = View.GONE - } - } + userQuota.available < 0 -> { // Pending, unknown or unlimited free storage + getAccountQuotaBar()?.visibility = View.INVISIBLE + getAccountQuotaText()?.text = String.format( + getString(R.string.drawer_unavailable_free_storage), + DisplayUtils.bytesToHumanReadable(userQuota.used, this, true) + ) + getAccountQuotaStatusText()?.visibility = View.GONE + } - else -> { // Limited storage. Value under 100% - if (userQuota.state == UserQuotaState.NEARING) { // Nearing storage. Value between 75% and 90% - getAccountQuotaBar()?.apply { - isVisible = true - progress = userQuota.getRelative().toInt() - progressTintList = ColorStateList.valueOf(resources.getColor(R.color.color_accent)) - } - getAccountQuotaText()?.apply { - text = String.format( - getString(R.string.drawer_quota), - DisplayUtils.bytesToHumanReadable(userQuota.used, context, true), - DisplayUtils.bytesToHumanReadable(userQuota.getTotal(), context, true), - userQuota.getRelative() - ) - } - getAccountQuotaStatusText()?.apply { - visibility = View.VISIBLE - text = getString(R.string.drawer_nearing_quota) - } - } else if (userQuota.state == UserQuotaState.CRITICAL || userQuota.state == UserQuotaState.EXCEEDED) { // Critical storage. Value over 90% - getAccountQuotaBar()?.apply { - isVisible = true - progress = userQuota.getRelative().toInt() - progressTintList = ColorStateList.valueOf(resources.getColor(R.color.quota_exceeded)) - } - getAccountQuotaText()?.apply { - text = String.format( - getString(R.string.drawer_quota), - DisplayUtils.bytesToHumanReadable(userQuota.used, context, true), - DisplayUtils.bytesToHumanReadable(userQuota.getTotal(), context, true), - userQuota.getRelative() - ) - } - getAccountQuotaStatusText()?.apply { - visibility = View.VISIBLE - text = getString(R.string.drawer_critical_quota) - } - } else { // Normal storage. Value under 75% - getAccountQuotaBar()?.apply { - progress = userQuota.getRelative().toInt() - isVisible = true - progressTintList = ColorStateList.valueOf(resources.getColor(R.color.color_accent)) - } - getAccountQuotaText()?.text = String.format( - getString(R.string.drawer_quota), - DisplayUtils.bytesToHumanReadable(userQuota.used, this, true), - DisplayUtils.bytesToHumanReadable(userQuota.getTotal(), this, true), - userQuota.getRelative() - ) - getAccountQuotaStatusText()?.visibility = View.GONE - } - } - } + userQuota.available == 0L -> { // Exceeded storage. The value is over 100%. + getAccountQuotaBar()?.apply { + isVisible = true + progress = 100 + progressTintList = ColorStateList.valueOf(resources.getColor(R.color.quota_exceeded)) + } + + if (userQuota.state == UserQuotaState.EXCEEDED) { // oCIS + getAccountQuotaText()?.apply { + text = String.format( + getString(R.string.drawer_quota), + DisplayUtils.bytesToHumanReadable(userQuota.used, context, true), + DisplayUtils.bytesToHumanReadable(userQuota.getTotal(), context, true), + userQuota.getRelative() + ) + } + getAccountQuotaStatusText()?.apply { + visibility = View.VISIBLE + text = getString(R.string.drawer_exceeded_quota) + } + } else { // oC10 + getAccountQuotaText()?.text = getString(R.string.drawer_exceeded_quota) + getAccountQuotaStatusText()?.visibility = View.GONE + } + } + + else -> { // Limited storage. Value under 100% + if (userQuota.state == UserQuotaState.NEARING) { // Nearing storage. Value between 75% and 90% + getAccountQuotaBar()?.apply { + isVisible = true + progress = userQuota.getRelative().toInt() + progressTintList = ColorStateList.valueOf(resources.getColor(R.color.quota_exceeded)) + } + getAccountQuotaText()?.apply { + text = String.format( + getString(R.string.drawer_quota), + DisplayUtils.bytesToHumanReadable(userQuota.used, context, true), + DisplayUtils.bytesToHumanReadable(userQuota.getTotal(), context, true), + userQuota.getRelative() + ) + } + getAccountQuotaStatusText()?.apply { + visibility = View.VISIBLE + text = getString(R.string.drawer_nearing_quota) + } + } else if (userQuota.state == UserQuotaState.CRITICAL || + userQuota.state == UserQuotaState.EXCEEDED) { // Critical storage. Value over 90% + getAccountQuotaBar()?.apply { + isVisible = true + progress = userQuota.getRelative().toInt() + progressTintList = ColorStateList.valueOf(resources.getColor(R.color.quota_exceeded)) + } + getAccountQuotaText()?.apply { + text = String.format( + getString(R.string.drawer_quota), + DisplayUtils.bytesToHumanReadable(userQuota.used, context, true), + DisplayUtils.bytesToHumanReadable(userQuota.getTotal(), context, true), + userQuota.getRelative() + ) + } + getAccountQuotaStatusText()?.apply { + visibility = View.VISIBLE + text = getString(R.string.drawer_critical_quota) + } + } else { // Normal storage. Value under 75% + getAccountQuotaBar()?.apply { + progress = userQuota.getRelative().toInt() + isVisible = true + progressTintList = ColorStateList.valueOf(resources.getColor(R.color.color_accent)) } + getAccountQuotaText()?.text = String.format( + getString(R.string.drawer_quota), + DisplayUtils.bytesToHumanReadable(userQuota.used, this, true), + DisplayUtils.bytesToHumanReadable(userQuota.getTotal(), this, true), + userQuota.getRelative() + ) + getAccountQuotaStatusText()?.visibility = View.GONE } - is UIResult.Loading -> getAccountQuotaText()?.text = getString(R.string.drawer_loading_quota) - is UIResult.Error -> getAccountQuotaText()?.text = getString(R.string.drawer_unavailable_used_storage) } } } @@ -496,7 +504,8 @@ abstract class DrawerActivity : ToolbarActivity() { findItem(R.id.nav_settings)?.contentDescription = "${getString(R.string.actionbar_settings)} $roleAccessibilityDescription" findItem(R.id.drawer_menu_feedback)?.contentDescription = "${getString(R.string.drawer_feedback)} $roleAccessibilityDescription" findItem(R.id.drawer_menu_help)?.contentDescription = "${getString(R.string.prefs_help)} $roleAccessibilityDescription" - findItem(R.id.drawer_menu_privacy_policy)?.contentDescription = "${getString(R.string.prefs_privacy_policy)} $roleAccessibilityDescription" + findItem(R.id.drawer_menu_privacy_policy)?.contentDescription = + "${getString(R.string.prefs_privacy_policy)} $roleAccessibilityDescription" } } } diff --git a/owncloudApp/src/test/java/com/owncloud/android/presentation/viewmodels/DrawerViewModelTest.kt b/owncloudApp/src/test/java/com/owncloud/android/presentation/viewmodels/DrawerViewModelTest.kt index f199ef6fab7..877df446be5 100644 --- a/owncloudApp/src/test/java/com/owncloud/android/presentation/viewmodels/DrawerViewModelTest.kt +++ b/owncloudApp/src/test/java/com/owncloud/android/presentation/viewmodels/DrawerViewModelTest.kt @@ -21,16 +21,11 @@ package com.owncloud.android.presentation.viewmodels import com.owncloud.android.data.providers.LocalStorageProvider -import com.owncloud.android.domain.UseCaseResult -import com.owncloud.android.domain.user.model.UserQuota -import com.owncloud.android.domain.user.usecases.GetStoredQuotaUseCase +import com.owncloud.android.domain.user.usecases.GetStoredQuotaAsStreamUseCase import com.owncloud.android.domain.user.usecases.GetUserQuotasUseCase -import com.owncloud.android.domain.utils.Event import com.owncloud.android.presentation.common.DrawerViewModel -import com.owncloud.android.presentation.common.UIResult import com.owncloud.android.providers.ContextProvider import com.owncloud.android.testutil.OC_ACCOUNT_NAME -import com.owncloud.android.testutil.OC_USER_QUOTA import com.owncloud.android.usecases.accounts.RemoveAccountUseCase import io.mockk.every import io.mockk.mockk @@ -39,7 +34,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.setMain import org.junit.After import org.junit.Before -import org.junit.Test import org.koin.core.context.startKoin import org.koin.core.context.stopKoin import org.koin.dsl.module @@ -47,7 +41,7 @@ import org.koin.dsl.module @ExperimentalCoroutinesApi class DrawerViewModelTest : ViewModelTest() { private lateinit var drawerViewModel: DrawerViewModel - private lateinit var getStoredQuotaUseCase: GetStoredQuotaUseCase + private lateinit var getStoredQuotaAsStreamUseCase: GetStoredQuotaAsStreamUseCase private lateinit var removeAccountUseCase: RemoveAccountUseCase private lateinit var getUserQuotasUseCase: GetUserQuotasUseCase private lateinit var localStorageProvider: LocalStorageProvider @@ -73,7 +67,7 @@ class DrawerViewModelTest : ViewModelTest() { }) } - getStoredQuotaUseCase = mockk() + getStoredQuotaAsStreamUseCase = mockk() removeAccountUseCase = mockk() getUserQuotasUseCase = mockk() localStorageProvider = mockk() @@ -81,12 +75,13 @@ class DrawerViewModelTest : ViewModelTest() { testCoroutineDispatcher.pauseDispatcher() drawerViewModel = DrawerViewModel( - getStoredQuotaUseCase = getStoredQuotaUseCase, + getStoredQuotaAsStreamUseCase = getStoredQuotaAsStreamUseCase, removeAccountUseCase = removeAccountUseCase, getUserQuotasUseCase = getUserQuotasUseCase, localStorageProvider = localStorageProvider, coroutinesDispatcherProvider = coroutineDispatcherProvider, contextProvider = contextProvider, + accountName = OC_ACCOUNT_NAME ) } @@ -96,28 +91,4 @@ class DrawerViewModelTest : ViewModelTest() { stopKoin() } - @Test - fun getStoredQuotaOk() { - every { getStoredQuotaUseCase(any()) } returns UseCaseResult.Success(OC_USER_QUOTA) - drawerViewModel.getStoredQuota(OC_ACCOUNT_NAME) - - assertEmittedValues( - expectedValues = listOf>>( - Event(UIResult.Loading()), Event(UIResult.Success(OC_USER_QUOTA)) - ), - liveData = drawerViewModel.userQuota - ) - } - - @Test - fun getStoredQuotaException() { - every { getStoredQuotaUseCase(any()) } returns UseCaseResult.Error(commonException) - drawerViewModel.getStoredQuota(OC_ACCOUNT_NAME) - - assertEmittedValues( - expectedValues = listOf>> - (Event(UIResult.Loading()), Event(UIResult.Error(commonException))), - liveData = drawerViewModel.userQuota - ) - } } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/user/datasources/LocalUserDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/user/datasources/LocalUserDataSource.kt index 9c6dca9187f..06167dcb833 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/user/datasources/LocalUserDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/user/datasources/LocalUserDataSource.kt @@ -2,7 +2,9 @@ * ownCloud Android client application * * @author Abel García de Prada - * Copyright (C) 2020 ownCloud GmbH. + * @author Jorge Aguado Recio + * + * Copyright (C) 2024 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -32,6 +34,10 @@ interface LocalUserDataSource { accountName: String ): UserQuota? + fun getQuotaForAccountAsFlow( + accountName: String + ): Flow + fun getAllUserQuotas(): List fun getAllUserQuotasAsFlow(): Flow> diff --git a/owncloudData/src/main/java/com/owncloud/android/data/user/datasources/implementation/OCLocalUserDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/user/datasources/implementation/OCLocalUserDataSource.kt index 0cca77b3f87..098e0e9883b 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/user/datasources/implementation/OCLocalUserDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/user/datasources/implementation/OCLocalUserDataSource.kt @@ -41,6 +41,9 @@ class OCLocalUserDataSource( override fun getQuotaForAccount(accountName: String): UserQuota? = userDao.getQuotaForAccount(accountName = accountName)?.toModel() + override fun getQuotaForAccountAsFlow(accountName: String): Flow = + userDao.getQuotaForAccountAsFlow(accountName = accountName).map { it?.toModel() } + override fun getAllUserQuotas(): List { return userDao.getAllUserQuotas().map { userQuotaEntity -> userQuotaEntity.toModel() diff --git a/owncloudData/src/main/java/com/owncloud/android/data/user/db/UserDao.kt b/owncloudData/src/main/java/com/owncloud/android/data/user/db/UserDao.kt index 5e78bca38e7..a8d2649ebb0 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/user/db/UserDao.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/user/db/UserDao.kt @@ -2,7 +2,9 @@ * ownCloud Android client application * * @author Abel García de Prada - * Copyright (C) 2020 ownCloud GmbH. + * @author Jorge Aguado Recio + * + * Copyright (C) 2024 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -33,6 +35,11 @@ interface UserDao { accountName: String ): UserQuotaEntity? + @Query(SELECT_QUOTA) + fun getQuotaForAccountAsFlow( + accountName: String + ): Flow + @Query(SELECT_ALL_QUOTAS) fun getAllUserQuotas(): List diff --git a/owncloudData/src/main/java/com/owncloud/android/data/user/repository/OCUserRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/user/repository/OCUserRepository.kt index 0cb97b6cc06..bc65fa73131 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/user/repository/OCUserRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/user/repository/OCUserRepository.kt @@ -3,8 +3,9 @@ * * @author Abel García de Prada * @author Juan Carlos Garrote Gascón + * @author Jorge Aguado Recio * - * Copyright (C) 2022 ownCloud GmbH. + * Copyright (C) 2024 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -42,6 +43,9 @@ class OCUserRepository( override fun getStoredUserQuota(accountName: String): UserQuota? = localUserDataSource.getQuotaForAccount(accountName) + override fun getStoredUserQuotaAsFlow(accountName: String): Flow = + localUserDataSource.getQuotaForAccountAsFlow(accountName) + override fun getAllUserQuotas(): List = localUserDataSource.getAllUserQuotas() diff --git a/owncloudData/src/test/java/com/owncloud/android/data/user/datasources/implementation/OCLocalUserDataSourceTest.kt b/owncloudData/src/test/java/com/owncloud/android/data/user/datasources/implementation/OCLocalUserDataSourceTest.kt index b5e62e38412..0488f8f1aee 100644 --- a/owncloudData/src/test/java/com/owncloud/android/data/user/datasources/implementation/OCLocalUserDataSourceTest.kt +++ b/owncloudData/src/test/java/com/owncloud/android/data/user/datasources/implementation/OCLocalUserDataSourceTest.kt @@ -3,8 +3,9 @@ * * @author Abel García de Prada * @author Aitor Ballesteros Pavón + * @author Jorge Aguado Recio * - * Copyright (C) 2023 ownCloud GmbH. + * Copyright (C) 2024 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -29,6 +30,9 @@ import com.owncloud.android.testutil.OC_USER_QUOTA import io.mockk.every import io.mockk.mockk import io.mockk.verify +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Before @@ -82,12 +86,16 @@ class OCLocalUserDataSourceTest { } @Test - fun `deleteQuotaForAccount removes user quota correctly`() { + fun `getQuotaForAccountAsFlow returns a Flow with an UserQuota`() = runTest { + every { + ocUserQuotaDao.getQuotaForAccountAsFlow(OC_ACCOUNT_NAME) + } returns flowOf(userQuotaEntity) - ocLocalUserDataSource.deleteQuotaForAccount(OC_ACCOUNT_NAME) + val userQuota = ocLocalUserDataSource.getQuotaForAccountAsFlow(OC_ACCOUNT_NAME).first() + assertEquals(userQuotaEntity.toModel(), userQuota) verify(exactly = 1) { - ocUserQuotaDao.deleteQuotaForAccount(OC_ACCOUNT_NAME) + ocUserQuotaDao.getQuotaForAccountAsFlow(OC_ACCOUNT_NAME) } } @@ -98,10 +106,35 @@ class OCLocalUserDataSourceTest { val resultActual = ocLocalUserDataSource.getAllUserQuotas() - assertEquals(listOf(userQuotaEntity.toModel()), resultActual) + assertEquals(listOf(OC_USER_QUOTA), resultActual) verify(exactly = 1) { ocUserQuotaDao.getAllUserQuotas() } } + + @Test + fun `getAllUserQuotasAsFlow returns a Flow with a list of UserQuota`() = runTest { + every { + ocUserQuotaDao.getAllUserQuotasAsFlow() + } returns flowOf(listOf(userQuotaEntity)) + + val listOfUserQuotas = ocLocalUserDataSource.getAllUserQuotasAsFlow().first() + assertEquals(listOf(OC_USER_QUOTA), listOfUserQuotas) + + verify(exactly = 1) { + ocUserQuotaDao.getAllUserQuotasAsFlow() + } + } + + @Test + fun `deleteQuotaForAccount removes user quota correctly`() { + + ocLocalUserDataSource.deleteQuotaForAccount(OC_ACCOUNT_NAME) + + verify(exactly = 1) { + ocUserQuotaDao.deleteQuotaForAccount(OC_ACCOUNT_NAME) + } + } + } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/user/UserRepository.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/user/UserRepository.kt index 3c7ddf48278..38dd48bb86f 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/user/UserRepository.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/user/UserRepository.kt @@ -3,8 +3,9 @@ * * @author Abel García de Prada * @author Juan Carlos Garrote Gascón + * @author Jorge Aguado Recio * - * Copyright (C) 2022 ownCloud GmbH. + * Copyright (C) 2024 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -30,6 +31,7 @@ interface UserRepository { fun getUserInfo(accountName: String): UserInfo fun getUserQuota(accountName: String): UserQuota fun getStoredUserQuota(accountName: String): UserQuota? + fun getStoredUserQuotaAsFlow(accountName: String): Flow fun getAllUserQuotas(): List fun getAllUserQuotasAsFlow(): Flow> fun getUserAvatar(accountName: String): UserAvatar diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/user/usecases/GetStoredQuotaAsStreamUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/user/usecases/GetStoredQuotaAsStreamUseCase.kt new file mode 100644 index 00000000000..b514833ce6b --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/user/usecases/GetStoredQuotaAsStreamUseCase.kt @@ -0,0 +1,36 @@ +/** + * ownCloud Android client application + * + * @author Jorge Aguado Recio + * + * Copyright (C) 2024 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.domain.user.usecases + +import com.owncloud.android.domain.BaseUseCaseWithResult +import com.owncloud.android.domain.user.UserRepository +import com.owncloud.android.domain.user.model.UserQuota +import kotlinx.coroutines.flow.Flow + +class GetStoredQuotaAsStreamUseCase( + private val userRepository: UserRepository +) : BaseUseCaseWithResult, GetStoredQuotaAsStreamUseCase.Params>() { + + override fun run(params: Params): Flow = + userRepository.getStoredUserQuotaAsFlow(params.accountName) + + data class Params(val accountName: String) +}