Skip to content

Commit

Permalink
Merge pull request #3990 from owncloud/feature/app_registry
Browse files Browse the repository at this point in the history
[Feature] Open in specific web provider
  • Loading branch information
JuancaG05 authored Apr 10, 2023
2 parents 2163744 + ec7d81d commit 374e4dd
Show file tree
Hide file tree
Showing 35 changed files with 1,744 additions and 65 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Summary
* Enhancement - Authenticated WebFinger: [#3943](https://github.com/owncloud/android/issues/3943)
* Enhancement - Link in drawer menu: [#3949](https://github.com/owncloud/android/pull/3949)
* Enhancement - Send language header in all requests: [#3980](https://github.com/owncloud/android/issues/3980)
* Enhancement - Open in specific web provider: [#3994](https://github.com/owncloud/android/issues/3994)
* Enhancement - Updated WebFinger flow: [#3998](https://github.com/owncloud/android/issues/3998)

Details
Expand Down Expand Up @@ -72,6 +73,18 @@ Details
https://github.com/owncloud/android/pull/3982
https://github.com/owncloud/android-library/pull/551

* Enhancement - Open in specific web provider: [#3994](https://github.com/owncloud/android/issues/3994)

We've added the specific web app providers instead of opening the file with the default web
provider.

The user can open their files with any of the available specific web app providers from the
server. Previously, file was opened with the default one.

https://github.com/owncloud/android/issues/3994
https://github.com/owncloud/android/pull/3990
https://owncloud.dev/services/app-registry/apps/#app-registry

* Enhancement - Updated WebFinger flow: [#3998](https://github.com/owncloud/android/issues/3998)

WebFinger call won't follow redirections. WebFinger will be requested first and will skip
Expand Down
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ buildscript {
// Koin
ioInsertKoin = "3.3.3"

// Moshi
comSquareupMoshi = '1.14.0'

// Testing
ioMockk = "1.13.3"
junitVersion = "4.13.2"
Expand Down
10 changes: 10 additions & 0 deletions changelog/unreleased/3990
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Enhancement: Open in specific web provider

We've added the specific web app providers instead of opening the file with the default web provider.

The user can open their files with any of the available specific web app providers from the server.
Previously, file was opened with the default one.

https://github.com/owncloud/android/issues/3994
https://github.com/owncloud/android/pull/3990
https://owncloud.dev/services/app-registry/apps/#app-registry
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import android.accounts.AccountManager
import com.owncloud.android.MainApp.Companion.accountType
import com.owncloud.android.MainApp.Companion.dataFolder
import com.owncloud.android.data.OwncloudDatabase
import com.owncloud.android.data.appregistry.datasources.LocalAppRegistryDataSource
import com.owncloud.android.data.appregistry.datasources.implementation.OCLocalAppRegistryDataSource
import com.owncloud.android.data.authentication.datasources.LocalAuthenticationDataSource
import com.owncloud.android.data.authentication.datasources.implementation.OCLocalAuthenticationDataSource
import com.owncloud.android.data.capabilities.datasources.LocalCapabilitiesDataSource
Expand All @@ -52,6 +54,7 @@ import org.koin.dsl.module
val localDataSourceModule = module {
single { AccountManager.get(androidContext()) }

single { OwncloudDatabase.getDatabase(androidContext()).appRegistryDao() }
single { OwncloudDatabase.getDatabase(androidContext()).capabilityDao() }
single { OwncloudDatabase.getDatabase(androidContext()).fileDao() }
single { OwncloudDatabase.getDatabase(androidContext()).shareDao() }
Expand All @@ -63,6 +66,7 @@ val localDataSourceModule = module {
single<SharedPreferencesProvider> { OCSharedPreferencesProvider(get()) }
single<LocalStorageProvider> { ScopedStorageProvider(dataFolder, androidContext()) }

factory<LocalAppRegistryDataSource> { OCLocalAppRegistryDataSource(get()) }
factory<LocalAuthenticationDataSource> { OCLocalAuthenticationDataSource(androidContext(), get(), get(), accountType) }
factory<LocalCapabilitiesDataSource> { OCLocalCapabilitiesDataSource(get()) }
factory<LocalFileDataSource> { OCLocalFileDataSource(get()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ package com.owncloud.android.dependecyinjection
import com.owncloud.android.MainApp
import com.owncloud.android.R
import com.owncloud.android.data.ClientManager
import com.owncloud.android.data.appregistry.datasources.RemoteAppRegistryDataSource
import com.owncloud.android.data.appregistry.datasources.implementation.OCRemoteAppRegistryDataSource
import com.owncloud.android.data.authentication.datasources.RemoteAuthenticationDataSource
import com.owncloud.android.data.authentication.datasources.implementation.OCRemoteAuthenticationDataSource
import com.owncloud.android.data.capabilities.datasources.RemoteCapabilitiesDataSource
Expand Down Expand Up @@ -63,6 +65,7 @@ val remoteDataSourceModule = module {
single<OIDCService> { OCOIDCService() }
single<WebFingerService> { OCWebFingerService() }

single<RemoteAppRegistryDataSource> { OCRemoteAppRegistryDataSource(get()) }
single<RemoteAuthenticationDataSource> { OCRemoteAuthenticationDataSource(get()) }
single<RemoteCapabilitiesDataSource> { OCRemoteCapabilitiesDataSource(get(), get()) }
single<RemoteFileDataSource> { OCRemoteFileDataSource(get()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

package com.owncloud.android.dependecyinjection

import com.owncloud.android.data.appregistry.OCAppRegistryRepository
import com.owncloud.android.data.authentication.repository.OCAuthenticationRepository
import com.owncloud.android.data.capabilities.repository.OCCapabilityRepository
import com.owncloud.android.data.files.repository.OCFileRepository
Expand All @@ -34,6 +35,7 @@ import com.owncloud.android.data.spaces.repository.OCSpacesRepository
import com.owncloud.android.data.transfers.repository.OCTransferRepository
import com.owncloud.android.data.user.repository.OCUserRepository
import com.owncloud.android.data.webfinger.repository.OCWebFingerRepository
import com.owncloud.android.domain.appregistry.AppRegistryRepository
import com.owncloud.android.domain.authentication.AuthenticationRepository
import com.owncloud.android.domain.authentication.oauth.OAuthRepository
import com.owncloud.android.domain.camerauploads.FolderBackupRepository
Expand All @@ -49,8 +51,9 @@ import com.owncloud.android.domain.webfinger.WebFingerRepository
import org.koin.dsl.module

val repositoryModule = module {
factory<AppRegistryRepository> { OCAppRegistryRepository(get(), get()) }
factory<AuthenticationRepository> { OCAuthenticationRepository(get(), get()) }
factory<CapabilityRepository> { OCCapabilityRepository(get(), get()) }
factory<CapabilityRepository> { OCCapabilityRepository(get(), get(), get()) }
factory<FileRepository> { OCFileRepository(get(), get(), get(), get()) }
factory<ServerInfoRepository> { OCServerInfoRepository(get(), get(), get()) }
factory<ShareRepository> { OCShareRepository(get(), get()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

package com.owncloud.android.dependecyinjection

import com.owncloud.android.domain.appregistry.usecases.GetAppRegistryForMimeTypeAsStreamUseCase
import com.owncloud.android.domain.appregistry.usecases.GetUrlToOpenInWebUseCase
import com.owncloud.android.domain.authentication.oauth.OIDCDiscoveryUseCase
import com.owncloud.android.domain.authentication.oauth.RegisterClientUseCase
import com.owncloud.android.domain.authentication.oauth.RequestTokenUseCase
Expand All @@ -44,7 +46,6 @@ import com.owncloud.android.domain.camerauploads.usecases.SaveVideoUploadsConfig
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.files.GetUrlToOpenInWebUseCase
import com.owncloud.android.domain.files.usecases.CleanConflictUseCase
import com.owncloud.android.domain.files.usecases.CleanWorkersUUIDUseCase
import com.owncloud.android.domain.files.usecases.CopyFileUseCase
Expand All @@ -57,9 +58,9 @@ import com.owncloud.android.domain.files.usecases.GetFolderContentAsStreamUseCas
import com.owncloud.android.domain.files.usecases.GetFolderContentUseCase
import com.owncloud.android.domain.files.usecases.GetFolderImagesUseCase
import com.owncloud.android.domain.files.usecases.GetPersonalRootFolderForAccountUseCase
import com.owncloud.android.domain.files.usecases.GetSharesRootFolderForAccount
import com.owncloud.android.domain.files.usecases.GetSearchFolderContentUseCase
import com.owncloud.android.domain.files.usecases.GetSharedByLinkForAccountAsStreamUseCase
import com.owncloud.android.domain.files.usecases.GetSharesRootFolderForAccount
import com.owncloud.android.domain.files.usecases.GetWebDavUrlForSpaceUseCase
import com.owncloud.android.domain.files.usecases.MoveFileUseCase
import com.owncloud.android.domain.files.usecases.RemoveFileUseCase
Expand All @@ -80,11 +81,11 @@ import com.owncloud.android.domain.sharing.shares.usecases.EditPublicShareAsyncU
import com.owncloud.android.domain.sharing.shares.usecases.GetShareAsLiveDataUseCase
import com.owncloud.android.domain.sharing.shares.usecases.GetSharesAsLiveDataUseCase
import com.owncloud.android.domain.sharing.shares.usecases.RefreshSharesFromServerAsyncUseCase
import com.owncloud.android.domain.spaces.usecases.GetSpacesFromEveryAccountUseCaseAsStream
import com.owncloud.android.domain.spaces.usecases.GetPersonalAndProjectSpacesForAccountUseCase
import com.owncloud.android.domain.spaces.usecases.GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase
import com.owncloud.android.domain.spaces.usecases.GetProjectSpacesWithSpecialsForAccountAsStreamUseCase
import com.owncloud.android.domain.spaces.usecases.GetSpaceWithSpecialsByIdForAccountUseCase
import com.owncloud.android.domain.spaces.usecases.GetSpacesFromEveryAccountUseCaseAsStream
import com.owncloud.android.domain.spaces.usecases.RefreshSpacesFromServerAsyncUseCase
import com.owncloud.android.domain.transfers.usecases.ClearSuccessfulTransfersUseCase
import com.owncloud.android.domain.transfers.usecases.GetAllTransfersAsStreamUseCase
Expand All @@ -95,8 +96,8 @@ import com.owncloud.android.domain.user.usecases.GetUserAvatarAsyncUseCase
import com.owncloud.android.domain.user.usecases.GetUserInfoAsyncUseCase
import com.owncloud.android.domain.user.usecases.GetUserQuotasUseCase
import com.owncloud.android.domain.user.usecases.RefreshUserQuotaFromServerAsyncUseCase
import com.owncloud.android.domain.webfinger.usecases.GetOwnCloudInstancesFromAuthenticatedWebFingerUseCase
import com.owncloud.android.domain.webfinger.usecases.GetOwnCloudInstanceFromWebFingerUseCase
import com.owncloud.android.domain.webfinger.usecases.GetOwnCloudInstancesFromAuthenticatedWebFingerUseCase
import com.owncloud.android.usecases.accounts.RemoveAccountUseCase
import com.owncloud.android.usecases.synchronization.SynchronizeFileUseCase
import com.owncloud.android.usecases.synchronization.SynchronizeFolderUseCase
Expand Down Expand Up @@ -167,6 +168,10 @@ val useCaseModule = module {
factory { SaveDownloadWorkerUUIDUseCase(get()) }
factory { CleanWorkersUUIDUseCase(get()) }

// Open in web
factory { GetUrlToOpenInWebUseCase(get(), get()) }
factory { GetAppRegistryForMimeTypeAsStreamUseCase(get()) }

// Av Offline
factory { GetFilesAvailableOfflineFromAccountUseCase(get()) }
factory { GetFilesAvailableOfflineFromAccountAsStreamUseCase(get()) }
Expand Down Expand Up @@ -239,9 +244,6 @@ val useCaseModule = module {
factory { GetPictureUploadsConfigurationStreamUseCase(get()) }
factory { GetVideoUploadsConfigurationStreamUseCase(get()) }

// Files
factory { GetUrlToOpenInWebUseCase(get()) }

// Accounts
factory { RemoveAccountUseCase(get(), get(), get(), get(), get(), get(), get(), get(), get()) }
factory { RemoveAccountUseCase(get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import com.owncloud.android.MainApp
import com.owncloud.android.R
import com.owncloud.android.databinding.FileDetailsFragmentBinding
import com.owncloud.android.datamodel.ThumbnailsCacheManager
import com.owncloud.android.domain.capabilities.model.OCCapability
import com.owncloud.android.domain.exceptions.InstanceNotConfiguredException
import com.owncloud.android.domain.exceptions.TooEarlyException
import com.owncloud.android.domain.files.model.OCFile
Expand Down Expand Up @@ -94,6 +93,8 @@ class FileDetailsFragment : FileFragment() {
private var _binding: FileDetailsFragmentBinding? = null
private val binding get() = _binding!!

private val mutableOpenInWebProviders: MutableMap<String, MenuItem> = hashMapOf()

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
Expand All @@ -118,9 +119,9 @@ class FileDetailsFragment : FileFragment() {
}
}

fileDetailsViewModel.capabilities.observe(viewLifecycleOwner) { ocCapability: OCCapability? ->
if (ocCapability != null && ocCapability.isOpenInWebAllowed()) {
// Show or hide openInWeb option. Hidden by default.
collectLatestLifecycleFlow(fileDetailsViewModel.appRegistryMimeType) { appRegistryMimeType ->
if (appRegistryMimeType != null) {
// Show or hide open in web options. Hidden by default.
requireActivity().invalidateOptionsMenu()
}
}
Expand Down Expand Up @@ -230,14 +231,30 @@ class FileDetailsFragment : FileFragment() {
isEnabled = false
}

menu.findItem(R.id.action_open_in_web)?.apply {
isVisible = fileDetailsViewModel.isOpenInWebAvailable()
isEnabled = fileDetailsViewModel.isOpenInWebAvailable()
// Remove items and then add them again. Otherwise we can get duplications or missing some app providers...
mutableOpenInWebProviders.forEach { (_, menuItem) ->
menu.removeItem(menuItem.itemId)
}

val appRegistryProviders = fileDetailsViewModel.appRegistryMimeType.value?.appProviders
appRegistryProviders?.forEachIndexed { index, appRegistryProvider ->
menu.add(Menu.NONE, index, 0, getString(R.string.ic_action_open_with_web, appRegistryProvider.name)).also {
mutableOpenInWebProviders[appRegistryProvider.name] = it
}
}
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
val safeFile = fileDetailsViewModel.getCurrentFile() ?: return false

// Let's match the ones that are dynamic first.
mutableOpenInWebProviders.forEach { (openInWebProviderName, menuItem) ->
if (menuItem == item) {
fileDetailsViewModel.openInWeb(safeFile.remoteId!!, openInWebProviderName)
return true
}
}

return when (item.itemId) {
R.id.action_share_file -> {
mContainerActivity.fileOperationsHelper.showShareFile(fileDetailsViewModel.getCurrentFile())
Expand Down Expand Up @@ -288,10 +305,6 @@ class FileDetailsFragment : FileFragment() {
fileOperationsViewModel.performOperation(UnsetFilesAsAvailableOffline(listOf(safeFile)))
true
}
R.id.action_open_in_web -> {
fileDetailsViewModel.openInWeb(file.remoteId!!)
true
}
else -> super.onOptionsItemSelected(item)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ import androidx.lifecycle.map
import androidx.lifecycle.viewModelScope
import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.owncloud.android.domain.capabilities.model.OCCapability
import com.owncloud.android.domain.capabilities.usecases.GetCapabilitiesAsLiveDataUseCase
import com.owncloud.android.domain.appregistry.model.AppRegistryMimeType
import com.owncloud.android.domain.appregistry.usecases.GetAppRegistryForMimeTypeAsStreamUseCase
import com.owncloud.android.domain.appregistry.usecases.GetUrlToOpenInWebUseCase
import com.owncloud.android.domain.capabilities.usecases.RefreshCapabilitiesFromServerAsyncUseCase
import com.owncloud.android.domain.extensions.isOneOf
import com.owncloud.android.domain.files.GetUrlToOpenInWebUseCase
import com.owncloud.android.domain.files.model.OCFile
import com.owncloud.android.domain.files.usecases.GetFileByIdAsStreamUseCase
import com.owncloud.android.domain.utils.Event
Expand All @@ -59,7 +59,7 @@ import java.util.UUID
class FileDetailsViewModel(
private val openInWebUseCase: GetUrlToOpenInWebUseCase,
refreshCapabilitiesFromServerAsyncUseCase: RefreshCapabilitiesFromServerAsyncUseCase,
getCapabilitiesAsLiveDataUseCase: GetCapabilitiesAsLiveDataUseCase,
getAppRegistryForMimeTypeAsStreamUseCase: GetAppRegistryForMimeTypeAsStreamUseCase,
val contextProvider: ContextProvider,
private val cancelDownloadForFileUseCase: CancelDownloadForFileUseCase,
getFileByIdAsStreamUseCase: GetFileByIdAsStreamUseCase,
Expand All @@ -74,8 +74,14 @@ class FileDetailsViewModel(
private val _openInWebUriLiveData: MediatorLiveData<Event<UIResult<String?>>> = MediatorLiveData()
val openInWebUriLiveData: LiveData<Event<UIResult<String?>>> = _openInWebUriLiveData

var capabilities: LiveData<OCCapability?> =
getCapabilitiesAsLiveDataUseCase.execute(GetCapabilitiesAsLiveDataUseCase.Params(account.name))
val appRegistryMimeType: StateFlow<AppRegistryMimeType?> =
getAppRegistryForMimeTypeAsStreamUseCase.execute(
GetAppRegistryForMimeTypeAsStreamUseCase.Params(accountName = account.name, ocFile.mimeType)
).stateIn(
viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = null
)

private val account: StateFlow<Account> = MutableStateFlow(account)
val currentFile: StateFlow<OCFile?> =
Expand Down Expand Up @@ -135,14 +141,16 @@ class FileDetailsViewModel(
}
}

fun isOpenInWebAvailable(): Boolean = capabilities.value?.isOpenInWebAllowed() ?: false

fun openInWeb(fileId: String) {
fun openInWeb(fileId: String, appName: String) {
runUseCaseWithResult(
coroutineDispatcher = coroutinesDispatcherProvider.io,
liveData = _openInWebUriLiveData,
useCase = openInWebUseCase,
useCaseParams = GetUrlToOpenInWebUseCase.Params(openWebEndpoint = capabilities.value?.filesAppProviders?.openWebUrl!!, fileId = fileId),
useCaseParams = GetUrlToOpenInWebUseCase.Params(
fileId = fileId,
accountName = getAccount().name,
appName = appName,
),
showLoading = false,
requiresConnection = true,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

package com.owncloud.android.usecases.accounts

import com.owncloud.android.data.appregistry.datasources.LocalAppRegistryDataSource
import com.owncloud.android.data.capabilities.datasources.LocalCapabilitiesDataSource
import com.owncloud.android.data.files.datasources.LocalFileDataSource
import com.owncloud.android.data.sharing.shares.datasources.LocalShareDataSource
Expand All @@ -46,6 +47,7 @@ class RemoveAccountUseCase(
private val localShareDataSource: LocalShareDataSource,
private val localUserDataSource: LocalUserDataSource,
private val localSpacesDataSource: LocalSpacesDataSource,
private val localAppRegistryDataSource: LocalAppRegistryDataSource,
) : BaseUseCase<Unit, RemoveAccountUseCase.Params>() {

override fun run(params: Params) {
Expand Down Expand Up @@ -77,6 +79,9 @@ class RemoveAccountUseCase(

// Delete spaces for the removed account in database
localSpacesDataSource.deleteSpacesForAccount(params.accountName)

// Delete app registry for the removed account in database
localAppRegistryDataSource.deleteAppRegistryForAccount(params.accountName)
}

data class Params(
Expand Down
6 changes: 0 additions & 6 deletions owncloudApp/src/main/res/menu/file_actions_menu.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,6 @@
app:showAsAction="never"
android:icon="@drawable/ic_action_unset_available_offline"
android:orderInCategory="1" />
<item
android:id="@+id/action_open_in_web"
android:orderInCategory="1"
android:title="@string/ic_action_open_in_web"
android:visible="false"
app:showAsAction="never" />
<item
android:id="@+id/action_open_file_with"
android:title="@string/actionbar_open_with"
Expand Down
Loading

0 comments on commit 374e4dd

Please sign in to comment.