Skip to content

Commit

Permalink
Merge pull request #4527 from owncloud/feature/multi_personal1
Browse files Browse the repository at this point in the history
[FEATURE REQUEST] Multi-Personal (1st round)
  • Loading branch information
JuancaG05 authored Jan 14, 2025
2 parents 35edcb3 + 4c87cab commit 9e49b1a
Show file tree
Hide file tree
Showing 24 changed files with 1,303 additions and 39 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 - Multi-Personal (1st round): [#4514](https://github.com/owncloud/android/issues/4514)
* Enhancement - Technical improvements for user quota: [#4521](https://github.com/owncloud/android/issues/4521)

## Details
Expand Down Expand Up @@ -125,6 +126,15 @@ ownCloud admins and users.
https://github.com/owncloud/android/issues/4500
https://github.com/owncloud/android/pull/4516

* Enhancement - Multi-Personal (1st round): [#4514](https://github.com/owncloud/android/issues/4514)

Support for multi-personal accounts has been added. This first approach displays
all personal spaces in the Spaces tab, not showing project spaces. In addition,
the Personal tab shows an empty view since there is not a single personal space.

https://github.com/owncloud/android/issues/4514
https://github.com/owncloud/android/pull/4527/files

* 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
Expand Down
8 changes: 8 additions & 0 deletions changelog/unreleased/4527
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Enhancement: Multi-Personal (1st round)

Support for multi-personal accounts has been added. This first approach displays all personal
spaces in the Spaces tab, not showing project spaces. In addition, the Personal tab shows
an empty view since there is not a single personal space.

https://github.com/owncloud/android/issues/4514
https://github.com/owncloud/android/pull/4527/files
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import com.owncloud.android.domain.sharing.shares.usecases.RefreshSharesFromServ
import com.owncloud.android.domain.spaces.usecases.GetPersonalAndProjectSpacesForAccountUseCase
import com.owncloud.android.domain.spaces.usecases.GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase
import com.owncloud.android.domain.spaces.usecases.GetPersonalSpaceForAccountUseCase
import com.owncloud.android.domain.spaces.usecases.GetPersonalSpacesWithSpecialsForAccountAsStreamUseCase
import com.owncloud.android.domain.spaces.usecases.GetProjectSpacesWithSpecialsForAccountAsStreamUseCase
import com.owncloud.android.domain.spaces.usecases.GetSpaceByIdForAccountUseCase
import com.owncloud.android.domain.spaces.usecases.GetSpaceWithSpecialsByIdForAccountUseCase
Expand Down Expand Up @@ -220,6 +221,7 @@ val useCaseModule = module {
factoryOf(::GetPersonalAndProjectSpacesForAccountUseCase)
factoryOf(::GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase)
factoryOf(::GetPersonalSpaceForAccountUseCase)
factoryOf(::GetPersonalSpacesWithSpecialsForAccountAsStreamUseCase)
factoryOf(::GetProjectSpacesWithSpecialsForAccountAsStreamUseCase)
factoryOf(::GetSpaceWithSpecialsByIdForAccountUseCase)
factoryOf(::GetSpacesFromEveryAccountUseCaseAsStream)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ val viewModelModule = module {
viewModelOf(::SettingsViewModel)
viewModelOf(::FileOperationsViewModel)

viewModel { (accountName: String) -> CapabilityViewModel(accountName, get(), get(), get()) }
viewModel { (accountName: String) -> CapabilityViewModel(accountName, get(), get(), get(), get()) }
viewModel { (action: PasscodeAction) -> PassCodeViewModel(get(), get(), action) }
viewModel { (filePath: String, accountName: String) ->
ShareViewModel(filePath, accountName, get(), get(), get(), get(), get(), get(), get(), get(), get(), get())
Expand All @@ -99,6 +99,6 @@ val viewModelModule = module {
viewModel { TransfersViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) }
viewModel { ReceiveExternalFilesViewModel(get(), get(), get(), get()) }
viewModel { (accountName: String, showPersonalSpace: Boolean) ->
SpacesListViewModel(get(), get(), get(), get(), get(), accountName, showPersonalSpace)
SpacesListViewModel(get(), get(), get(), get(), get(), get(), get(), accountName, showPersonalSpace)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
*
* @author David González Verdugo
* @author Abel García de Prada
* Copyright (C) 2020 ownCloud GmbH.
* @author Juan Carlos Garrote Gascón
*
* 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,
Expand All @@ -25,11 +27,14 @@ import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.ViewModel
import com.owncloud.android.domain.capabilities.model.OCCapability
import com.owncloud.android.domain.capabilities.usecases.GetCapabilitiesAsLiveDataUseCase
import com.owncloud.android.domain.capabilities.usecases.GetStoredCapabilitiesUseCase
import com.owncloud.android.domain.capabilities.usecases.RefreshCapabilitiesFromServerAsyncUseCase
import com.owncloud.android.domain.utils.Event
import com.owncloud.android.extensions.ViewModelExt.runUseCaseWithResultAndUseCachedData
import com.owncloud.android.presentation.common.UIResult
import com.owncloud.android.providers.CoroutinesDispatcherProvider
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext

/**
* View Model to keep a reference to the capability repository and an up-to-date capability
Expand All @@ -38,6 +43,7 @@ class CapabilityViewModel(
private val accountName: String,
getCapabilitiesAsLiveDataUseCase: GetCapabilitiesAsLiveDataUseCase,
private val refreshCapabilitiesFromServerAsyncUseCase: RefreshCapabilitiesFromServerAsyncUseCase,
private val getStoredCapabilitiesUseCase: GetStoredCapabilitiesUseCase,
private val coroutineDispatcherProvider: CoroutinesDispatcherProvider
) : ViewModel() {

Expand Down Expand Up @@ -67,4 +73,11 @@ class CapabilityViewModel(
accountName = accountName
)
)

fun checkMultiPersonal(): Boolean = runBlocking(CoroutinesDispatcherProvider().io) {
val capabilities = withContext(CoroutinesDispatcherProvider().io) {
getStoredCapabilitiesUseCase(GetStoredCapabilitiesUseCase.Params(accountName))
}
capabilities?.spaces?.hasMultiplePersonalSpaces == true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ import com.owncloud.android.extensions.toStringResId
import com.owncloud.android.extensions.toSubtitleStringRes
import com.owncloud.android.extensions.toTitleStringRes
import com.owncloud.android.presentation.authentication.AccountUtils
import com.owncloud.android.presentation.capabilities.CapabilityViewModel
import com.owncloud.android.presentation.common.BottomSheetFragmentItemView
import com.owncloud.android.presentation.common.UIResult
import com.owncloud.android.presentation.files.SortBottomSheetFragment
Expand Down Expand Up @@ -152,6 +153,11 @@ class MainFileListFragment : Fragment(),
false,
)
}
private val capabilityViewModel: CapabilityViewModel by viewModel {
parametersOf(
requireArguments().getString(ARG_ACCOUNT_NAME),
)
}

private var _binding: MainFileListFragmentBinding? = null
private val binding get() = _binding!!
Expand All @@ -173,6 +179,8 @@ class MainFileListFragment : Fragment(),

private var openInWebProviders: Map<String, Int> = hashMapOf()

private var isMultiPersonal = false

private var menu: Menu? = null
private var checkedFiles: List<OCFile> = emptyList()
private var filesToRemove: List<OCFile> = emptyList()
Expand All @@ -191,6 +199,7 @@ class MainFileListFragment : Fragment(),

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
isMultiPersonal = capabilityViewModel.checkMultiPersonal()
initViews()
subscribeToViewModels()
}
Expand Down Expand Up @@ -610,9 +619,9 @@ class MainFileListFragment : Fragment(),
)
showOrHideEmptyView(fileListUiState)


binding.spaceHeader.root.apply {
if (fileListUiState.space?.isProject == true && fileListUiState.folderToDisplay?.remotePath == ROOT_PATH) {
if ((fileListUiState.space?.isProject == true || (fileListUiState.space?.isPersonal == true && isMultiPersonal)) &&
fileListUiState.folderToDisplay?.remotePath == ROOT_PATH && fileListUiState.fileListOption != FileListOption.AV_OFFLINE) {
isVisible = true
animate().translationY(0f).duration = 100
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class SpacesListAdapter(
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

private val spacesList = mutableListOf<OCSpace>()
private var isMultiPersonal = false

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val binding = SpacesListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
Expand All @@ -59,7 +60,7 @@ class SpacesListAdapter(
}
spacesListItemCard.setAccessibilityRole(className = Button::class.java)

if (space.isPersonal) {
if (space.isPersonal && !isMultiPersonal) {
spacesListItemName.text = holder.itemView.context.getString(R.string.bottom_nav_personal)
spacesListItemImage.apply {
dispose()
Expand Down Expand Up @@ -90,7 +91,8 @@ class SpacesListAdapter(
}
}

fun setData(spaces: List<OCSpace>) {
fun setData(spaces: List<OCSpace>, hasMultiplePersonalSpaces: Boolean) {
isMultiPersonal = hasMultiplePersonalSpaces
val diffCallback = SpacesListDiffUtil(spacesList, spaces)
val diffResult = DiffUtil.calculateDiff(diffCallback)
spacesList.clear()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,27 @@ import com.owncloud.android.extensions.showErrorInSnackbar
import com.owncloud.android.extensions.toDrawableRes
import com.owncloud.android.extensions.toSubtitleStringRes
import com.owncloud.android.extensions.toTitleStringRes
import com.owncloud.android.presentation.capabilities.CapabilityViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf

class SpacesListFragment : SpacesListAdapter.SpacesListAdapterListener, Fragment(), SearchView.OnQueryTextListener {
private var _binding: SpacesListFragmentBinding? = null
private val binding get() = _binding!!

private var isMultiPersonal = false

private val spacesListViewModel: SpacesListViewModel by viewModel {
parametersOf(
requireArguments().getString(BUNDLE_ACCOUNT_NAME),
requireArguments().getBoolean(BUNDLE_SHOW_PERSONAL_SPACE),
)
}
private val capabilityViewModel: CapabilityViewModel by viewModel {
parametersOf(
requireArguments().getString(BUNDLE_ACCOUNT_NAME),
)
}

private lateinit var spacesListAdapter: SpacesListAdapter

Expand All @@ -69,6 +77,7 @@ class SpacesListFragment : SpacesListAdapter.SpacesListAdapterListener, Fragment
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
isMultiPersonal = capabilityViewModel.checkMultiPersonal()
initViews()
subscribeToViewModels()
}
Expand Down Expand Up @@ -99,10 +108,10 @@ class SpacesListFragment : SpacesListAdapter.SpacesListAdapterListener, Fragment
}
}
showOrHideEmptyView(spacesToListFiltered)
spacesListAdapter.setData(spacesToListFiltered)
spacesListAdapter.setData(spacesToListFiltered, isMultiPersonal)
} else {
showOrHideEmptyView(uiState.spaces)
spacesListAdapter.setData(uiState.spaces.filter { !it.isDisabled })
spacesListAdapter.setData(uiState.spaces.filter { !it.isDisabled }, isMultiPersonal)
}
binding.swipeRefreshSpacesList.isRefreshing = uiState.refreshing
uiState.error?.let { showErrorInSnackbar(R.string.spaces_sync_failed, it) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@

package com.owncloud.android.presentation.spaces


import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.owncloud.android.domain.UseCaseResult
import com.owncloud.android.domain.capabilities.usecases.GetStoredCapabilitiesUseCase
import com.owncloud.android.domain.files.model.OCFile
import com.owncloud.android.domain.files.model.OCFile.Companion.ROOT_PATH
import com.owncloud.android.domain.files.usecases.GetFileByRemotePathUseCase
import com.owncloud.android.domain.spaces.model.OCSpace
import com.owncloud.android.domain.spaces.usecases.GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase
import com.owncloud.android.domain.spaces.usecases.GetPersonalSpacesWithSpecialsForAccountAsStreamUseCase
import com.owncloud.android.domain.spaces.usecases.GetProjectSpacesWithSpecialsForAccountAsStreamUseCase
import com.owncloud.android.domain.spaces.usecases.RefreshSpacesFromServerAsyncUseCase
import com.owncloud.android.providers.CoroutinesDispatcherProvider
Expand All @@ -40,9 +41,11 @@ import kotlinx.coroutines.launch

class SpacesListViewModel(
private val refreshSpacesFromServerAsyncUseCase: RefreshSpacesFromServerAsyncUseCase,
private val getPersonalSpacesWithSpecialsForAccountAsStreamUseCase: GetPersonalSpacesWithSpecialsForAccountAsStreamUseCase,
private val getPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase: GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase,
private val getProjectSpacesWithSpecialsForAccountAsStreamUseCase: GetProjectSpacesWithSpecialsForAccountAsStreamUseCase,
private val getFileByRemotePathUseCase: GetFileByRemotePathUseCase,
private val getStoredCapabilitiesUseCase: GetStoredCapabilitiesUseCase,
private val coroutinesDispatcherProvider: CoroutinesDispatcherProvider,
private val accountName: String,
private val showPersonalSpace: Boolean,
Expand All @@ -55,7 +58,11 @@ class SpacesListViewModel(
init {
viewModelScope.launch(coroutinesDispatcherProvider.io) {
refreshSpacesFromServer()
val spacesListFlow = if (showPersonalSpace) getPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase(
val capabilities = getStoredCapabilitiesUseCase(GetStoredCapabilitiesUseCase.Params(accountName))
val isMultiPersonal = capabilities?.spaces?.hasMultiplePersonalSpaces
val spacesListFlow = if (isMultiPersonal == true) getPersonalSpacesWithSpecialsForAccountAsStreamUseCase(
GetPersonalSpacesWithSpecialsForAccountAsStreamUseCase.Params(accountName = accountName)
) else if (showPersonalSpace) getPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase(
GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase.Params(accountName = accountName)
) else getProjectSpacesWithSpecialsForAccountAsStreamUseCase(
GetProjectSpacesWithSpecialsForAccountAsStreamUseCase.Params(accountName = accountName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ class FileDisplayActivity : FileActivity(),
private lateinit var binding: ActivityMainBinding

private var isLightUser = false
private var isMultiPersonal = false

override fun onCreate(savedInstanceState: Bundle?) {
Timber.v("onCreate() start")
Expand Down Expand Up @@ -324,6 +325,7 @@ class FileDisplayActivity : FileActivity(),
onCapabilitiesOperationFinish(it)
})
isLightUser = manageAccountsViewModel.checkUserLight(account.name)
isMultiPersonal = capabilitiesViewModel.checkMultiPersonal()
navigateTo(fileListOption, initialState = true)

}
Expand Down Expand Up @@ -762,8 +764,9 @@ class FileDisplayActivity : FileActivity(),
}
// If current file is root folder
else if (currentDirDisplayed.parentId == ROOT_PARENT_ID) {
// If current space is a project space (not personal, not shares), navigate back to the spaces list
if (mainFileListFragment?.getCurrentSpace()?.isProject == true) {
// If current space is a project space or personal in a multi-personal account, navigate back to the spaces list
if (mainFileListFragment?.getCurrentSpace()?.isProject == true ||
(mainFileListFragment?.getCurrentSpace()?.isPersonal == true && isMultiPersonal)) {
navigateTo(FileListOption.SPACES_LIST)
}
// If current space is not a project space (personal or shares) or it is null ("Files" in oC10), close the app
Expand Down Expand Up @@ -965,7 +968,8 @@ class FileDisplayActivity : FileActivity(),
// If we come from a preview activity (image or video), not updating toolbar when initializing this activity or it will show the root folder one
if (intent.action == ACTION_DETAILS && chosenFile?.remotePath == OCFile.ROOT_PATH && secondFragment is FileDetailsFragment) return

if (chosenFile == null || (chosenFile.remotePath == OCFile.ROOT_PATH && (space == null || !space.isProject))) {
if (chosenFile == null || (chosenFile.remotePath == OCFile.ROOT_PATH && (space == null ||
(!space.isProject && !(space.isPersonal && isMultiPersonal))))) {
val title =
when (fileListOption) {
FileListOption.AV_OFFLINE -> getString(R.string.drawer_item_only_available_offline)
Expand All @@ -980,7 +984,7 @@ class FileDisplayActivity : FileActivity(),
}
setTitle(title)
setupRootToolbar(title = title, isSearchEnabled = true, isAvatarRequested = false)
} else if (space?.isProject == true && chosenFile.remotePath == OCFile.ROOT_PATH) {
} else if ((space?.isProject == true || (space?.isPersonal == true && isMultiPersonal)) && chosenFile.remotePath == OCFile.ROOT_PATH) {
updateStandardToolbar(title = space.name, displayHomeAsUpEnabled = true, homeButtonEnabled = true)
} else {
updateStandardToolbar(title = chosenFile.fileName, displayHomeAsUpEnabled = true, homeButtonEnabled = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import androidx.lifecycle.MutableLiveData
import com.owncloud.android.domain.UseCaseResult
import com.owncloud.android.domain.capabilities.model.OCCapability
import com.owncloud.android.domain.capabilities.usecases.GetCapabilitiesAsLiveDataUseCase
import com.owncloud.android.domain.capabilities.usecases.GetStoredCapabilitiesUseCase
import com.owncloud.android.domain.capabilities.usecases.RefreshCapabilitiesFromServerAsyncUseCase
import com.owncloud.android.domain.utils.Event
import com.owncloud.android.presentation.common.UIResult
Expand Down Expand Up @@ -62,6 +63,7 @@ class CapabilityViewModelTest {

private lateinit var getCapabilitiesAsLiveDataUseCase: GetCapabilitiesAsLiveDataUseCase
private lateinit var refreshCapabilitiesFromServerUseCase: RefreshCapabilitiesFromServerAsyncUseCase
private lateinit var getStoredCapabilitiesUseCase: GetStoredCapabilitiesUseCase
private lateinit var ocContextProvider: ContextProvider

private val capabilitiesLiveData = MutableLiveData<OCCapability>()
Expand Down Expand Up @@ -111,13 +113,15 @@ class CapabilityViewModelTest {
private fun initTest() {
getCapabilitiesAsLiveDataUseCase = spyk(mockkClass(GetCapabilitiesAsLiveDataUseCase::class))
refreshCapabilitiesFromServerUseCase = spyk(mockkClass(RefreshCapabilitiesFromServerAsyncUseCase::class))
getStoredCapabilitiesUseCase = spyk(mockkClass(GetStoredCapabilitiesUseCase::class))

every { getCapabilitiesAsLiveDataUseCase(any()) } returns capabilitiesLiveData

capabilityViewModel = CapabilityViewModel(
accountName = testAccountName,
getCapabilitiesAsLiveDataUseCase = getCapabilitiesAsLiveDataUseCase,
refreshCapabilitiesFromServerAsyncUseCase = refreshCapabilitiesFromServerUseCase,
getStoredCapabilitiesUseCase = getStoredCapabilitiesUseCase,
coroutineDispatcherProvider = coroutineDispatcherProvider
)
}
Expand Down
Loading

0 comments on commit 9e49b1a

Please sign in to comment.