From 5f06639e22e82f44d3bc7018201f360538a8d890 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 15 Jan 2026 14:39:22 +0100 Subject: [PATCH 1/5] ecosystem link handling Signed-off-by: alperozturk96 --- app/build.gradle.kts | 3 +- app/src/main/AndroidManifest.xml | 7 ++ .../java/com/nextcloud/utils/LinkHelper.kt | 64 +------------------ .../android/ui/activity/DrawerActivity.java | 18 +++++- .../ui/activity/FileDisplayActivity.kt | 22 +++++++ .../android/ui/adapter/OCFileListAdapter.java | 10 ++- gradle/libs.versions.toml | 3 +- gradle/verification-metadata.xml | 29 +++++++++ 8 files changed, 87 insertions(+), 69 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a665df2c229a..66e67a745f93 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -504,8 +504,9 @@ dependencies { "gplayImplementation"(libs.bundles.gplay) // endregion - // region UI + // region common implementation(libs.ui) + implementation(libs.common.core) // endregion // region Image loading diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6a210d519cb1..b9589dd3854c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -147,6 +147,13 @@ android:exported="true" android:launchMode="singleTop" android:theme="@style/Theme.ownCloud.Launcher"> + + + + + + + diff --git a/app/src/main/java/com/nextcloud/utils/LinkHelper.kt b/app/src/main/java/com/nextcloud/utils/LinkHelper.kt index 294c98d7b748..75ee63098667 100644 --- a/app/src/main/java/com/nextcloud/utils/LinkHelper.kt +++ b/app/src/main/java/com/nextcloud/utils/LinkHelper.kt @@ -10,53 +10,17 @@ package com.nextcloud.utils import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent -import android.net.Uri import androidx.core.net.toUri -import com.nextcloud.client.account.User import com.owncloud.android.lib.common.utils.Log_OC -import com.owncloud.android.ui.activity.FileDisplayActivity import java.util.Locale -import java.util.Optional -import kotlin.jvm.optionals.getOrNull object LinkHelper { - const val APP_NEXTCLOUD_NOTES = "it.niedermann.owncloud.notes" - const val APP_NEXTCLOUD_TALK = "com.nextcloud.talk2" private const val TAG = "LinkHelper" fun isHttpOrHttpsLink(link: String?): Boolean = link?.lowercase(Locale.getDefault())?.let { it.startsWith("http://") || it.startsWith("https://") } == true - /** - * Open specified app and, if not installed redirect to corresponding download. - * - * @param packageName of app to be opened - * @param user to pass in intent - */ - fun openAppOrStore(packageName: String, user: Optional, context: Context) { - openAppOrStore(packageName, user.getOrNull(), context) - } - - /** - * Open specified app and, if not installed redirect to corresponding download. - * - * @param packageName of app to be opened - * @param user to pass in intent - */ - fun openAppOrStore(packageName: String, user: User?, context: Context) { - val intent = context.packageManager.getLaunchIntentForPackage(packageName) - if (intent != null) { - // app installed - open directly - // TODO handle null user? - intent.putExtra(FileDisplayActivity.KEY_ACCOUNT, user.hashCode()) - context.startActivity(intent) - } else { - // app not found - open market (Google Play Store, F-Droid, etc.) - openAppStore(packageName, false, context) - } - } - /** * Open app store page of specified app or search for specified string. Will attempt to open browser when no app * store is available. @@ -69,7 +33,7 @@ object LinkHelper { val intent = Intent(Intent.ACTION_VIEW, "market://$suffix".toUri()) try { context.startActivity(intent) - } catch (activityNotFoundException1: ActivityNotFoundException) { + } catch (_: ActivityNotFoundException) { // all is lost: open google play store web page for app if (!search) { suffix = "apps/$suffix" @@ -82,32 +46,6 @@ object LinkHelper { // region Validation private const val HTTP = "http" private const val HTTPS = "https" - private const val FILE = "file" - private const val CONTENT = "content" - - /** - * Validates if a string can be converted to a valid URI - */ - @Suppress("TooGenericExceptionCaught", "ReturnCount") - fun validateAndGetURI(uriString: String?): Uri? { - if (uriString.isNullOrBlank()) { - Log_OC.w(TAG, "Given uriString is null or blank") - return null - } - - return try { - val uri = uriString.toUri() - if (uri.scheme == null) { - return null - } - - val validSchemes = listOf(HTTP, HTTPS, FILE, CONTENT) - if (uri.scheme in validSchemes) uri else null - } catch (e: Exception) { - Log_OC.e(TAG, "Invalid URI string: $uriString -- $e") - null - } - } /** * Validates if a URL string is valid diff --git a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java index 29849c71f0c5..13d4cdca534a 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java @@ -49,6 +49,8 @@ import com.google.android.material.button.MaterialButton; import com.google.android.material.navigation.NavigationView; import com.google.android.material.progressindicator.LinearProgressIndicator; +import com.nextcloud.android.common.core.utils.ecosystem.EcosystemApp; +import com.nextcloud.android.common.core.utils.ecosystem.EcosystemManager; import com.nextcloud.android.common.ui.theme.utils.ColorRole; import com.nextcloud.client.account.User; import com.nextcloud.client.di.Injectable; @@ -205,6 +207,8 @@ public abstract class DrawerActivity extends ToolbarActivity private BottomNavigationView bottomNavigationView; + private EcosystemManager ecosystemManager; + @Inject AppPreferences preferences; @@ -429,8 +433,13 @@ private void showTopBanner(ConstraintLayout banner) { LinearLayout moreView = banner.findViewById(R.id.drawer_ecosystem_more); LinearLayout assistantView = banner.findViewById(R.id.drawer_ecosystem_assistant); - notesView.setOnClickListener(v -> LinkHelper.INSTANCE.openAppOrStore(LinkHelper.APP_NEXTCLOUD_NOTES, getUser(), this)); - talkView.setOnClickListener(v -> LinkHelper.INSTANCE.openAppOrStore(LinkHelper.APP_NEXTCLOUD_TALK, getUser(), this)); + final var optionalUser = getUser(); + if (optionalUser.isPresent()) { + final var accountName = optionalUser.get().getAccountName(); + notesView.setOnClickListener(v -> ecosystemManager.openApp(EcosystemApp.NOTES, accountName)); + talkView.setOnClickListener(v -> ecosystemManager.openApp(EcosystemApp.TALK, accountName)); + } + moreView.setOnClickListener(v -> LinkHelper.INSTANCE.openAppStore("Nextcloud", true, this)); assistantView.setOnClickListener(v -> { DrawerActivity.menuItemId = Menu.NONE; @@ -727,6 +736,10 @@ private void launchActivityForSearch(SearchEvent searchEvent, int menuItemId) { startActivity(intent); } + public EcosystemManager getEcosystemManager() { + return ecosystemManager; + } + /** * sets the new/current account and restarts. In case the given account equals the actual/current account the call * will be ignored. @@ -1136,6 +1149,7 @@ protected void onCreate(Bundle savedInstanceState) { externalLinksProvider = new ExternalLinksProvider(getContentResolver()); arbitraryDataProvider = new ArbitraryDataProviderImpl(this); + ecosystemManager = new EcosystemManager(this); } @Override diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index cd383b4241da..6bb0e8e19987 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -52,6 +52,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager import com.google.android.material.appbar.AppBarLayout import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar +import com.nextcloud.android.common.core.utils.ecosystem.AccountReceiverCallback import com.nextcloud.appReview.InAppReviewHelper import com.nextcloud.client.account.User import com.nextcloud.client.appinfo.AppInfo @@ -547,6 +548,7 @@ class FileDisplayActivity : handleCommonIntents(intent) handleSpecialIntents(intent) handleRestartIntent(intent) + handleEcosystemIntent(intent) } private fun handleSpecialIntents(intent: Intent) { @@ -3073,6 +3075,26 @@ class FileDisplayActivity : }) } + private fun handleEcosystemIntent(intent: Intent?) { + ecosystemManager.receiveAccount( + intent, + object : AccountReceiverCallback { + override fun onAccountReceived(accountName: String) { + val user = accountManager.getUser(accountName) + if (user.isPresent) { + accountClicked(user.get()) + } else { + Log_OC.e(TAG, "user is not present") + } + } + + override fun onAccountError(reason: String) { + Log_OC.w(TAG, "handleEcosystemIntent: $reason") + } + } + ) + } + // region MetadataSyncJob private fun startMetadataSyncForRoot() { backgroundJobManager.startMetadataSyncJob(OCFile.ROOT_PATH) diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java index 08f690ccbe93..603a4f864ec0 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java @@ -26,13 +26,13 @@ import com.elyeproj.loaderviewlibrary.LoaderImageView; import com.google.android.material.chip.Chip; +import com.nextcloud.android.common.core.utils.ecosystem.EcosystemApp; import com.nextcloud.android.common.ui.theme.utils.ColorRole; import com.nextcloud.client.account.User; import com.nextcloud.client.database.entity.OfflineOperationEntity; import com.nextcloud.client.jobs.upload.FileUploadHelper; import com.nextcloud.client.preferences.AppPreferences; import com.nextcloud.model.OfflineOperationType; -import com.nextcloud.utils.LinkHelper; import com.nextcloud.utils.extensions.OCFileExtensionsKt; import com.nextcloud.utils.extensions.ViewExtensionsKt; import com.nextcloud.utils.mdm.MDMConfig; @@ -55,6 +55,7 @@ import com.owncloud.android.lib.resources.status.OCCapability; import com.owncloud.android.lib.resources.tags.Tag; import com.owncloud.android.ui.activity.ComponentsGetter; +import com.owncloud.android.ui.activity.DrawerActivity; import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.adapter.helper.OCFileListAdapterDataProvider; import com.owncloud.android.ui.adapter.helper.OCFileListAdapterHelper; @@ -449,7 +450,12 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi listHeaderOpenInBinding.openInButton.setText(String.format(activity.getString(R.string.open_in_app), activity.getString(R.string.ecosystem_apps_display_notes))); - listHeaderOpenInBinding.openInButton.setOnClickListener(v -> LinkHelper.INSTANCE.openAppOrStore(LinkHelper.APP_NEXTCLOUD_NOTES, user, activity)); + if (activity instanceof DrawerActivity drawerActivity) { + final var ecosystemManager = drawerActivity.getEcosystemManager(); + if (ecosystemManager != null) { + listHeaderOpenInBinding.openInButton.setOnClickListener(v -> ecosystemManager.openApp(EcosystemApp.NOTES, user.getAccountName())); + } + } } } else { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ad93c15c00dd..927ecf29ef31 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later [versions] -androidCommonLibraryVersion = "0.31.0" +androidCommonLibraryVersion = "c7da76323d" androidGifDrawableVersion = "1.2.30" androidImageCropperVersion = "4.7.0" androidLibraryVersion = "c112fd86c76f429db250e6abca711348e5534c0a" @@ -232,6 +232,7 @@ prism4j-bundler = { module = "io.noties:prism4j-bundler", version.ref = "prismVe # Nextcloud libraries ui = { module = "com.github.nextcloud.android-common:ui", version.ref = "androidCommonLibraryVersion" } +common-core = { module = "com.github.nextcloud.android-common:core", version.ref = "androidCommonLibraryVersion" } qrcodescanner = { module = "com.github.nextcloud-deps:qrcodescanner", version.ref = "qrcodescannerVersion" } # Worker diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 3ece291a172e..3fba0bb8e867 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -1449,6 +1449,11 @@ + + + + + @@ -20760,6 +20765,14 @@ + + + + + + + + @@ -20940,6 +20953,14 @@ + + + + + + + + @@ -21116,6 +21137,14 @@ + + + + + + + + From dfd33151fa7f46e7f35af998fae885ed04e5bbff Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 15 Jan 2026 15:24:40 +0100 Subject: [PATCH 2/5] ecosystem link handling Signed-off-by: alperozturk96 --- .../ui/activity/FileDisplayActivity.kt | 1 + gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 24 +++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 6bb0e8e19987..25d6a53b7120 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -257,6 +257,7 @@ class FileDisplayActivity : intent?.let { handleCommonIntents(it) + handleEcosystemIntent(it) } loadSavedInstanceState(savedInstanceState) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 927ecf29ef31..975e2be24db5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later [versions] -androidCommonLibraryVersion = "c7da76323d" +androidCommonLibraryVersion = "3babd42636" androidGifDrawableVersion = "1.2.30" androidImageCropperVersion = "4.7.0" androidLibraryVersion = "c112fd86c76f429db250e6abca711348e5534c0a" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 3fba0bb8e867..9f5a0ecd0041 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -20733,6 +20733,14 @@ + + + + + + + + @@ -20921,6 +20929,14 @@ + + + + + + + + @@ -21105,6 +21121,14 @@ + + + + + + + + From e7aeefcf55ea07a3587465bea35e3e474fe1acb9 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 16 Jan 2026 09:10:17 +0100 Subject: [PATCH 3/5] inform user if account not exists Signed-off-by: alperozturk96 --- .../owncloud/android/ui/activity/FileDisplayActivity.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 25d6a53b7120..de665a064c47 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -3082,10 +3082,14 @@ class FileDisplayActivity : object : AccountReceiverCallback { override fun onAccountReceived(accountName: String) { val user = accountManager.getUser(accountName) + if (user.isEmpty) { + Log_OC.e(TAG, "user is not present") + DisplayUtils.showSnackMessage(this@FileDisplayActivity, R.string.account_not_found) + return + } + if (user.isPresent) { accountClicked(user.get()) - } else { - Log_OC.e(TAG, "user is not present") } } From 78eaa46599df00bc0e1b2737710aa266aa4b59a8 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 16 Jan 2026 09:12:43 +0100 Subject: [PATCH 4/5] inform user if account not exists Signed-off-by: alperozturk96 --- .../android/ui/activity/FileDisplayActivity.kt | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index de665a064c47..cc27ee6c40d7 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -3081,16 +3081,14 @@ class FileDisplayActivity : intent, object : AccountReceiverCallback { override fun onAccountReceived(accountName: String) { - val user = accountManager.getUser(accountName) - if (user.isEmpty) { - Log_OC.e(TAG, "user is not present") - DisplayUtils.showSnackMessage(this@FileDisplayActivity, R.string.account_not_found) - return - } + val account = accountManager.getUser(accountName).orElse(null) + ?: run { + Log_OC.w(TAG, "user is not present") + DisplayUtils.showSnackMessage(this@FileDisplayActivity, R.string.account_not_found) + return + } - if (user.isPresent) { - accountClicked(user.get()) - } + accountClicked(account) } override fun onAccountError(reason: String) { From c969f3492337c68ccfbcd33e40cd42725d0ffecf Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 16 Jan 2026 09:55:37 +0100 Subject: [PATCH 5/5] update lib Signed-off-by: alperozturk96 --- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 975e2be24db5..c01d38c6305b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later [versions] -androidCommonLibraryVersion = "3babd42636" +androidCommonLibraryVersion = "30a17d42f4" androidGifDrawableVersion = "1.2.30" androidImageCropperVersion = "4.7.0" androidLibraryVersion = "c112fd86c76f429db250e6abca711348e5534c0a" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 9f5a0ecd0041..475880d21ff4 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -20733,6 +20733,14 @@ + + + + + + + + @@ -20929,6 +20937,14 @@ + + + + + + + + @@ -21121,6 +21137,14 @@ + + + + + + + +