Skip to content

Commit 374e4dd

Browse files
authored
Merge pull request #3990 from owncloud/feature/app_registry
[Feature] Open in specific web provider
2 parents 2163744 + ec7d81d commit 374e4dd

35 files changed

Lines changed: 1744 additions & 65 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Summary
1414
* Enhancement - Authenticated WebFinger: [#3943](https://github.com/owncloud/android/issues/3943)
1515
* Enhancement - Link in drawer menu: [#3949](https://github.com/owncloud/android/pull/3949)
1616
* Enhancement - Send language header in all requests: [#3980](https://github.com/owncloud/android/issues/3980)
17+
* Enhancement - Open in specific web provider: [#3994](https://github.com/owncloud/android/issues/3994)
1718
* Enhancement - Updated WebFinger flow: [#3998](https://github.com/owncloud/android/issues/3998)
1819

1920
Details
@@ -72,6 +73,18 @@ Details
7273
https://github.com/owncloud/android/pull/3982
7374
https://github.com/owncloud/android-library/pull/551
7475

76+
* Enhancement - Open in specific web provider: [#3994](https://github.com/owncloud/android/issues/3994)
77+
78+
We've added the specific web app providers instead of opening the file with the default web
79+
provider.
80+
81+
The user can open their files with any of the available specific web app providers from the
82+
server. Previously, file was opened with the default one.
83+
84+
https://github.com/owncloud/android/issues/3994
85+
https://github.com/owncloud/android/pull/3990
86+
https://owncloud.dev/services/app-registry/apps/#app-registry
87+
7588
* Enhancement - Updated WebFinger flow: [#3998](https://github.com/owncloud/android/issues/3998)
7689

7790
WebFinger call won't follow redirections. WebFinger will be requested first and will skip

build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ buildscript {
2121
// Koin
2222
ioInsertKoin = "3.3.3"
2323

24+
// Moshi
25+
comSquareupMoshi = '1.14.0'
26+
2427
// Testing
2528
ioMockk = "1.13.3"
2629
junitVersion = "4.13.2"

changelog/unreleased/3990

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Enhancement: Open in specific web provider
2+
3+
We've added the specific web app providers instead of opening the file with the default web provider.
4+
5+
The user can open their files with any of the available specific web app providers from the server.
6+
Previously, file was opened with the default one.
7+
8+
https://github.com/owncloud/android/issues/3994
9+
https://github.com/owncloud/android/pull/3990
10+
https://owncloud.dev/services/app-registry/apps/#app-registry

owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/LocalDataSourceModule.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import android.accounts.AccountManager
2626
import com.owncloud.android.MainApp.Companion.accountType
2727
import com.owncloud.android.MainApp.Companion.dataFolder
2828
import com.owncloud.android.data.OwncloudDatabase
29+
import com.owncloud.android.data.appregistry.datasources.LocalAppRegistryDataSource
30+
import com.owncloud.android.data.appregistry.datasources.implementation.OCLocalAppRegistryDataSource
2931
import com.owncloud.android.data.authentication.datasources.LocalAuthenticationDataSource
3032
import com.owncloud.android.data.authentication.datasources.implementation.OCLocalAuthenticationDataSource
3133
import com.owncloud.android.data.capabilities.datasources.LocalCapabilitiesDataSource
@@ -52,6 +54,7 @@ import org.koin.dsl.module
5254
val localDataSourceModule = module {
5355
single { AccountManager.get(androidContext()) }
5456

57+
single { OwncloudDatabase.getDatabase(androidContext()).appRegistryDao() }
5558
single { OwncloudDatabase.getDatabase(androidContext()).capabilityDao() }
5659
single { OwncloudDatabase.getDatabase(androidContext()).fileDao() }
5760
single { OwncloudDatabase.getDatabase(androidContext()).shareDao() }
@@ -63,6 +66,7 @@ val localDataSourceModule = module {
6366
single<SharedPreferencesProvider> { OCSharedPreferencesProvider(get()) }
6467
single<LocalStorageProvider> { ScopedStorageProvider(dataFolder, androidContext()) }
6568

69+
factory<LocalAppRegistryDataSource> { OCLocalAppRegistryDataSource(get()) }
6670
factory<LocalAuthenticationDataSource> { OCLocalAuthenticationDataSource(androidContext(), get(), get(), accountType) }
6771
factory<LocalCapabilitiesDataSource> { OCLocalCapabilitiesDataSource(get()) }
6872
factory<LocalFileDataSource> { OCLocalFileDataSource(get()) }

owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RemoteDataSourceModule.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ package com.owncloud.android.dependecyinjection
2222
import com.owncloud.android.MainApp
2323
import com.owncloud.android.R
2424
import com.owncloud.android.data.ClientManager
25+
import com.owncloud.android.data.appregistry.datasources.RemoteAppRegistryDataSource
26+
import com.owncloud.android.data.appregistry.datasources.implementation.OCRemoteAppRegistryDataSource
2527
import com.owncloud.android.data.authentication.datasources.RemoteAuthenticationDataSource
2628
import com.owncloud.android.data.authentication.datasources.implementation.OCRemoteAuthenticationDataSource
2729
import com.owncloud.android.data.capabilities.datasources.RemoteCapabilitiesDataSource
@@ -63,6 +65,7 @@ val remoteDataSourceModule = module {
6365
single<OIDCService> { OCOIDCService() }
6466
single<WebFingerService> { OCWebFingerService() }
6567

68+
single<RemoteAppRegistryDataSource> { OCRemoteAppRegistryDataSource(get()) }
6669
single<RemoteAuthenticationDataSource> { OCRemoteAuthenticationDataSource(get()) }
6770
single<RemoteCapabilitiesDataSource> { OCRemoteCapabilitiesDataSource(get(), get()) }
6871
single<RemoteFileDataSource> { OCRemoteFileDataSource(get()) }

owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
package com.owncloud.android.dependecyinjection
2424

25+
import com.owncloud.android.data.appregistry.OCAppRegistryRepository
2526
import com.owncloud.android.data.authentication.repository.OCAuthenticationRepository
2627
import com.owncloud.android.data.capabilities.repository.OCCapabilityRepository
2728
import com.owncloud.android.data.files.repository.OCFileRepository
@@ -34,6 +35,7 @@ import com.owncloud.android.data.spaces.repository.OCSpacesRepository
3435
import com.owncloud.android.data.transfers.repository.OCTransferRepository
3536
import com.owncloud.android.data.user.repository.OCUserRepository
3637
import com.owncloud.android.data.webfinger.repository.OCWebFingerRepository
38+
import com.owncloud.android.domain.appregistry.AppRegistryRepository
3739
import com.owncloud.android.domain.authentication.AuthenticationRepository
3840
import com.owncloud.android.domain.authentication.oauth.OAuthRepository
3941
import com.owncloud.android.domain.camerauploads.FolderBackupRepository
@@ -49,8 +51,9 @@ import com.owncloud.android.domain.webfinger.WebFingerRepository
4951
import org.koin.dsl.module
5052

5153
val repositoryModule = module {
54+
factory<AppRegistryRepository> { OCAppRegistryRepository(get(), get()) }
5255
factory<AuthenticationRepository> { OCAuthenticationRepository(get(), get()) }
53-
factory<CapabilityRepository> { OCCapabilityRepository(get(), get()) }
56+
factory<CapabilityRepository> { OCCapabilityRepository(get(), get(), get()) }
5457
factory<FileRepository> { OCFileRepository(get(), get(), get(), get()) }
5558
factory<ServerInfoRepository> { OCServerInfoRepository(get(), get(), get()) }
5659
factory<ShareRepository> { OCShareRepository(get(), get()) }

owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222

2323
package com.owncloud.android.dependecyinjection
2424

25+
import com.owncloud.android.domain.appregistry.usecases.GetAppRegistryForMimeTypeAsStreamUseCase
26+
import com.owncloud.android.domain.appregistry.usecases.GetUrlToOpenInWebUseCase
2527
import com.owncloud.android.domain.authentication.oauth.OIDCDiscoveryUseCase
2628
import com.owncloud.android.domain.authentication.oauth.RegisterClientUseCase
2729
import com.owncloud.android.domain.authentication.oauth.RequestTokenUseCase
@@ -44,7 +46,6 @@ import com.owncloud.android.domain.camerauploads.usecases.SaveVideoUploadsConfig
4446
import com.owncloud.android.domain.capabilities.usecases.GetCapabilitiesAsLiveDataUseCase
4547
import com.owncloud.android.domain.capabilities.usecases.GetStoredCapabilitiesUseCase
4648
import com.owncloud.android.domain.capabilities.usecases.RefreshCapabilitiesFromServerAsyncUseCase
47-
import com.owncloud.android.domain.files.GetUrlToOpenInWebUseCase
4849
import com.owncloud.android.domain.files.usecases.CleanConflictUseCase
4950
import com.owncloud.android.domain.files.usecases.CleanWorkersUUIDUseCase
5051
import com.owncloud.android.domain.files.usecases.CopyFileUseCase
@@ -57,9 +58,9 @@ import com.owncloud.android.domain.files.usecases.GetFolderContentAsStreamUseCas
5758
import com.owncloud.android.domain.files.usecases.GetFolderContentUseCase
5859
import com.owncloud.android.domain.files.usecases.GetFolderImagesUseCase
5960
import com.owncloud.android.domain.files.usecases.GetPersonalRootFolderForAccountUseCase
60-
import com.owncloud.android.domain.files.usecases.GetSharesRootFolderForAccount
6161
import com.owncloud.android.domain.files.usecases.GetSearchFolderContentUseCase
6262
import com.owncloud.android.domain.files.usecases.GetSharedByLinkForAccountAsStreamUseCase
63+
import com.owncloud.android.domain.files.usecases.GetSharesRootFolderForAccount
6364
import com.owncloud.android.domain.files.usecases.GetWebDavUrlForSpaceUseCase
6465
import com.owncloud.android.domain.files.usecases.MoveFileUseCase
6566
import com.owncloud.android.domain.files.usecases.RemoveFileUseCase
@@ -80,11 +81,11 @@ import com.owncloud.android.domain.sharing.shares.usecases.EditPublicShareAsyncU
8081
import com.owncloud.android.domain.sharing.shares.usecases.GetShareAsLiveDataUseCase
8182
import com.owncloud.android.domain.sharing.shares.usecases.GetSharesAsLiveDataUseCase
8283
import com.owncloud.android.domain.sharing.shares.usecases.RefreshSharesFromServerAsyncUseCase
83-
import com.owncloud.android.domain.spaces.usecases.GetSpacesFromEveryAccountUseCaseAsStream
8484
import com.owncloud.android.domain.spaces.usecases.GetPersonalAndProjectSpacesForAccountUseCase
8585
import com.owncloud.android.domain.spaces.usecases.GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase
8686
import com.owncloud.android.domain.spaces.usecases.GetProjectSpacesWithSpecialsForAccountAsStreamUseCase
8787
import com.owncloud.android.domain.spaces.usecases.GetSpaceWithSpecialsByIdForAccountUseCase
88+
import com.owncloud.android.domain.spaces.usecases.GetSpacesFromEveryAccountUseCaseAsStream
8889
import com.owncloud.android.domain.spaces.usecases.RefreshSpacesFromServerAsyncUseCase
8990
import com.owncloud.android.domain.transfers.usecases.ClearSuccessfulTransfersUseCase
9091
import com.owncloud.android.domain.transfers.usecases.GetAllTransfersAsStreamUseCase
@@ -95,8 +96,8 @@ import com.owncloud.android.domain.user.usecases.GetUserAvatarAsyncUseCase
9596
import com.owncloud.android.domain.user.usecases.GetUserInfoAsyncUseCase
9697
import com.owncloud.android.domain.user.usecases.GetUserQuotasUseCase
9798
import com.owncloud.android.domain.user.usecases.RefreshUserQuotaFromServerAsyncUseCase
98-
import com.owncloud.android.domain.webfinger.usecases.GetOwnCloudInstancesFromAuthenticatedWebFingerUseCase
9999
import com.owncloud.android.domain.webfinger.usecases.GetOwnCloudInstanceFromWebFingerUseCase
100+
import com.owncloud.android.domain.webfinger.usecases.GetOwnCloudInstancesFromAuthenticatedWebFingerUseCase
100101
import com.owncloud.android.usecases.accounts.RemoveAccountUseCase
101102
import com.owncloud.android.usecases.synchronization.SynchronizeFileUseCase
102103
import com.owncloud.android.usecases.synchronization.SynchronizeFolderUseCase
@@ -167,6 +168,10 @@ val useCaseModule = module {
167168
factory { SaveDownloadWorkerUUIDUseCase(get()) }
168169
factory { CleanWorkersUUIDUseCase(get()) }
169170

171+
// Open in web
172+
factory { GetUrlToOpenInWebUseCase(get(), get()) }
173+
factory { GetAppRegistryForMimeTypeAsStreamUseCase(get()) }
174+
170175
// Av Offline
171176
factory { GetFilesAvailableOfflineFromAccountUseCase(get()) }
172177
factory { GetFilesAvailableOfflineFromAccountAsStreamUseCase(get()) }
@@ -239,9 +244,6 @@ val useCaseModule = module {
239244
factory { GetPictureUploadsConfigurationStreamUseCase(get()) }
240245
factory { GetVideoUploadsConfigurationStreamUseCase(get()) }
241246

242-
// Files
243-
factory { GetUrlToOpenInWebUseCase(get()) }
244-
245247
// Accounts
246-
factory { RemoveAccountUseCase(get(), get(), get(), get(), get(), get(), get(), get(), get()) }
248+
factory { RemoveAccountUseCase(get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) }
247249
}

owncloudApp/src/main/java/com/owncloud/android/presentation/files/details/FileDetailsFragment.kt

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ import com.owncloud.android.MainApp
3838
import com.owncloud.android.R
3939
import com.owncloud.android.databinding.FileDetailsFragmentBinding
4040
import com.owncloud.android.datamodel.ThumbnailsCacheManager
41-
import com.owncloud.android.domain.capabilities.model.OCCapability
4241
import com.owncloud.android.domain.exceptions.InstanceNotConfiguredException
4342
import com.owncloud.android.domain.exceptions.TooEarlyException
4443
import com.owncloud.android.domain.files.model.OCFile
@@ -94,6 +93,8 @@ class FileDetailsFragment : FileFragment() {
9493
private var _binding: FileDetailsFragmentBinding? = null
9594
private val binding get() = _binding!!
9695

96+
private val mutableOpenInWebProviders: MutableMap<String, MenuItem> = hashMapOf()
97+
9798
override fun onCreateView(
9899
inflater: LayoutInflater, container: ViewGroup?,
99100
savedInstanceState: Bundle?
@@ -118,9 +119,9 @@ class FileDetailsFragment : FileFragment() {
118119
}
119120
}
120121

121-
fileDetailsViewModel.capabilities.observe(viewLifecycleOwner) { ocCapability: OCCapability? ->
122-
if (ocCapability != null && ocCapability.isOpenInWebAllowed()) {
123-
// Show or hide openInWeb option. Hidden by default.
122+
collectLatestLifecycleFlow(fileDetailsViewModel.appRegistryMimeType) { appRegistryMimeType ->
123+
if (appRegistryMimeType != null) {
124+
// Show or hide open in web options. Hidden by default.
124125
requireActivity().invalidateOptionsMenu()
125126
}
126127
}
@@ -230,14 +231,30 @@ class FileDetailsFragment : FileFragment() {
230231
isEnabled = false
231232
}
232233

233-
menu.findItem(R.id.action_open_in_web)?.apply {
234-
isVisible = fileDetailsViewModel.isOpenInWebAvailable()
235-
isEnabled = fileDetailsViewModel.isOpenInWebAvailable()
234+
// Remove items and then add them again. Otherwise we can get duplications or missing some app providers...
235+
mutableOpenInWebProviders.forEach { (_, menuItem) ->
236+
menu.removeItem(menuItem.itemId)
237+
}
238+
239+
val appRegistryProviders = fileDetailsViewModel.appRegistryMimeType.value?.appProviders
240+
appRegistryProviders?.forEachIndexed { index, appRegistryProvider ->
241+
menu.add(Menu.NONE, index, 0, getString(R.string.ic_action_open_with_web, appRegistryProvider.name)).also {
242+
mutableOpenInWebProviders[appRegistryProvider.name] = it
243+
}
236244
}
237245
}
238246

239247
override fun onOptionsItemSelected(item: MenuItem): Boolean {
240248
val safeFile = fileDetailsViewModel.getCurrentFile() ?: return false
249+
250+
// Let's match the ones that are dynamic first.
251+
mutableOpenInWebProviders.forEach { (openInWebProviderName, menuItem) ->
252+
if (menuItem == item) {
253+
fileDetailsViewModel.openInWeb(safeFile.remoteId!!, openInWebProviderName)
254+
return true
255+
}
256+
}
257+
241258
return when (item.itemId) {
242259
R.id.action_share_file -> {
243260
mContainerActivity.fileOperationsHelper.showShareFile(fileDetailsViewModel.getCurrentFile())
@@ -288,10 +305,6 @@ class FileDetailsFragment : FileFragment() {
288305
fileOperationsViewModel.performOperation(UnsetFilesAsAvailableOffline(listOf(safeFile)))
289306
true
290307
}
291-
R.id.action_open_in_web -> {
292-
fileDetailsViewModel.openInWeb(file.remoteId!!)
293-
true
294-
}
295308
else -> super.onOptionsItemSelected(item)
296309
}
297310
}

owncloudApp/src/main/java/com/owncloud/android/presentation/files/details/FileDetailsViewModel.kt

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ import androidx.lifecycle.map
2828
import androidx.lifecycle.viewModelScope
2929
import androidx.work.WorkInfo
3030
import androidx.work.WorkManager
31-
import com.owncloud.android.domain.capabilities.model.OCCapability
32-
import com.owncloud.android.domain.capabilities.usecases.GetCapabilitiesAsLiveDataUseCase
31+
import com.owncloud.android.domain.appregistry.model.AppRegistryMimeType
32+
import com.owncloud.android.domain.appregistry.usecases.GetAppRegistryForMimeTypeAsStreamUseCase
33+
import com.owncloud.android.domain.appregistry.usecases.GetUrlToOpenInWebUseCase
3334
import com.owncloud.android.domain.capabilities.usecases.RefreshCapabilitiesFromServerAsyncUseCase
3435
import com.owncloud.android.domain.extensions.isOneOf
35-
import com.owncloud.android.domain.files.GetUrlToOpenInWebUseCase
3636
import com.owncloud.android.domain.files.model.OCFile
3737
import com.owncloud.android.domain.files.usecases.GetFileByIdAsStreamUseCase
3838
import com.owncloud.android.domain.utils.Event
@@ -59,7 +59,7 @@ import java.util.UUID
5959
class FileDetailsViewModel(
6060
private val openInWebUseCase: GetUrlToOpenInWebUseCase,
6161
refreshCapabilitiesFromServerAsyncUseCase: RefreshCapabilitiesFromServerAsyncUseCase,
62-
getCapabilitiesAsLiveDataUseCase: GetCapabilitiesAsLiveDataUseCase,
62+
getAppRegistryForMimeTypeAsStreamUseCase: GetAppRegistryForMimeTypeAsStreamUseCase,
6363
val contextProvider: ContextProvider,
6464
private val cancelDownloadForFileUseCase: CancelDownloadForFileUseCase,
6565
getFileByIdAsStreamUseCase: GetFileByIdAsStreamUseCase,
@@ -74,8 +74,14 @@ class FileDetailsViewModel(
7474
private val _openInWebUriLiveData: MediatorLiveData<Event<UIResult<String?>>> = MediatorLiveData()
7575
val openInWebUriLiveData: LiveData<Event<UIResult<String?>>> = _openInWebUriLiveData
7676

77-
var capabilities: LiveData<OCCapability?> =
78-
getCapabilitiesAsLiveDataUseCase.execute(GetCapabilitiesAsLiveDataUseCase.Params(account.name))
77+
val appRegistryMimeType: StateFlow<AppRegistryMimeType?> =
78+
getAppRegistryForMimeTypeAsStreamUseCase.execute(
79+
GetAppRegistryForMimeTypeAsStreamUseCase.Params(accountName = account.name, ocFile.mimeType)
80+
).stateIn(
81+
viewModelScope,
82+
started = SharingStarted.WhileSubscribed(5_000),
83+
initialValue = null
84+
)
7985

8086
private val account: StateFlow<Account> = MutableStateFlow(account)
8187
val currentFile: StateFlow<OCFile?> =
@@ -135,14 +141,16 @@ class FileDetailsViewModel(
135141
}
136142
}
137143

138-
fun isOpenInWebAvailable(): Boolean = capabilities.value?.isOpenInWebAllowed() ?: false
139-
140-
fun openInWeb(fileId: String) {
144+
fun openInWeb(fileId: String, appName: String) {
141145
runUseCaseWithResult(
142146
coroutineDispatcher = coroutinesDispatcherProvider.io,
143147
liveData = _openInWebUriLiveData,
144148
useCase = openInWebUseCase,
145-
useCaseParams = GetUrlToOpenInWebUseCase.Params(openWebEndpoint = capabilities.value?.filesAppProviders?.openWebUrl!!, fileId = fileId),
149+
useCaseParams = GetUrlToOpenInWebUseCase.Params(
150+
fileId = fileId,
151+
accountName = getAccount().name,
152+
appName = appName,
153+
),
146154
showLoading = false,
147155
requiresConnection = true,
148156
)

0 commit comments

Comments
 (0)