Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/Theme.ownCloud.Launcher">

<intent-filter>
<action android:name="com.nextcloud.intent.OPEN_ECOSYSTEM_APP" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>


<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
Expand Down
64 changes: 1 addition & 63 deletions app/src/main/java/com/nextcloud/utils/LinkHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<User>, 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.
Expand All @@ -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"
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -205,6 +207,8 @@ public abstract class DrawerActivity extends ToolbarActivity

private BottomNavigationView bottomNavigationView;

private EcosystemManager ecosystemManager;

@Inject
AppPreferences preferences;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -1136,6 +1149,7 @@ protected void onCreate(Bundle savedInstanceState) {

externalLinksProvider = new ExternalLinksProvider(getContentResolver());
arbitraryDataProvider = new ArbitraryDataProviderImpl(this);
ecosystemManager = new EcosystemManager(this);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -256,6 +257,7 @@ class FileDisplayActivity :

intent?.let {
handleCommonIntents(it)
handleEcosystemIntent(it)
}

loadSavedInstanceState(savedInstanceState)
Expand Down Expand Up @@ -547,6 +549,7 @@ class FileDisplayActivity :
handleCommonIntents(intent)
handleSpecialIntents(intent)
handleRestartIntent(intent)
handleEcosystemIntent(intent)
}

private fun handleSpecialIntents(intent: Intent) {
Expand Down Expand Up @@ -3073,6 +3076,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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# SPDX-License-Identifier: AGPL-3.0-or-later

[versions]
androidCommonLibraryVersion = "0.31.0"
androidCommonLibraryVersion = "3babd42636"
androidGifDrawableVersion = "1.2.30"
androidImageCropperVersion = "4.7.0"
androidLibraryVersion = "c112fd86c76f429db250e6abca711348e5534c0a"
Expand Down Expand Up @@ -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
Expand Down
53 changes: 53 additions & 0 deletions gradle/verification-metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1449,6 +1449,11 @@
<sha256 value="11c8654511c3926c5e8f32b909d7d5e6785a7282b5d01e1f6071ec3afa36e3a0" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="androidx.compose" name="compose-bom" version="2026.01.00">
<artifact name="compose-bom-2026.01.00.pom">
<sha256 value="9bc8c321324e7492f74ce7de4b6a2a12f8f37cafc4a7a2a882ab75da972779ce" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="androidx.compose.animation" name="animation" version="1.10.0">
<artifact name="animation-1.10.0.module">
<sha256 value="2158c1b57ce292ac2fd0d1541ece2cd08bbfbbd80aa6417a0668076693855458" origin="Generated by Gradle"/>
Expand Down Expand Up @@ -20728,6 +20733,14 @@
<sha256 value="5130e40763194ddcb4fcdcca2502bef47e754a283a5fae2822dc01c4a7a4c1dc" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud.android-common" name="core" version="3babd42636">
<artifact name="core-3babd42636.aar">
<sha256 value="fe5ef0dbfec2fbf82d0e4c8944b5f277215a2d55d264f76e4838b8d99ea2a974" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="core-3babd42636.module">
<sha256 value="6fce6f83dc52faa20469e2476f1fc226c20dea5f5a1f6f77ffda1bf8bffda774" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud.android-common" name="core" version="48ed8e86d9">
<artifact name="core-48ed8e86d9.aar">
<sha256 value="933d612d1324b21d2e7e04a890bb2c7afda4ffdbe91b76fdea625976f14ba1fb" origin="Generated by Gradle" reason="Artifact is not signed"/>
Expand Down Expand Up @@ -20760,6 +20773,14 @@
<sha256 value="3fc98a0ab817e61f711b15cc6be5fd145f58aea4840f6b7985c73557f9cc0810" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.nextcloud.android-common" name="core" version="c7da76323d">
<artifact name="core-c7da76323d.aar">
<sha256 value="1fa9233ff3bc96416c7408db4ba1ba30f9b320e2868597e7de32563e8eb6feff" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="core-c7da76323d.module">
<sha256 value="bdbb738048b801f8da697844997ca6169909b8b239f71b70b7cddbe522668de1" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud.android-common" name="core" version="df2f116806">
<artifact name="core-df2f116806.aar">
<sha256 value="38011f3adff2f68c6c99cb121a5def63cc44a66e393373203687b59c5fb82ce8" origin="Generated by Gradle"/>
Expand Down Expand Up @@ -20908,6 +20929,14 @@
<sha256 value="e72cd08918b3c81c899497c786ef99cd77ee155a18657c4ef67891d2e79017e5" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud.android-common" name="material-color-utilities" version="3babd42636">
<artifact name="material-color-utilities-3babd42636.jar">
<sha256 value="61568a8c8f0466aea4fec621653ca8d2c08c9c687ffd6e364f909a2d19185992" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="material-color-utilities-3babd42636.module">
<sha256 value="568a2583c132d898d9ebc481bb052e414894348abfbec19b7f6195dcd39b5246" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud.android-common" name="material-color-utilities" version="48ed8e86d9">
<artifact name="material-color-utilities-48ed8e86d9.jar">
<sha256 value="d4357ec309eb321f6d281cfef414005c72ebdd492a9df44bee68392f7b150f8d" origin="Generated by Gradle" reason="Artifact is not signed"/>
Expand Down Expand Up @@ -20940,6 +20969,14 @@
<sha256 value="3d947236ced64bd6071adb8a610d7640b5dd469e4dac933306d0e01268131a27" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.nextcloud.android-common" name="material-color-utilities" version="c7da76323d">
<artifact name="material-color-utilities-c7da76323d.jar">
<sha256 value="61568a8c8f0466aea4fec621653ca8d2c08c9c687ffd6e364f909a2d19185992" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="material-color-utilities-c7da76323d.module">
<sha256 value="815aaf811e7efd48171f25ec57aea023b8f74b21db5a4c3abc82b815e0c91d37" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud.android-common" name="material-color-utilities" version="df2f116806">
<artifact name="material-color-utilities-df2f116806.jar">
<sha256 value="56547ff6e6201d788fe85b11ca8d9185104f95ae001412d903fb1cf700ffddca" origin="Generated by Gradle"/>
Expand Down Expand Up @@ -21084,6 +21121,14 @@
<sha256 value="08ac68b537486118fea670c2c43326a7ff1411a511b6ffb95ce46033622e71e8" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud.android-common" name="ui" version="3babd42636">
<artifact name="ui-3babd42636.aar">
<sha256 value="041d67ffbb102dfa828719e35f1d416997706793c2674b9da4f135b3667c002c" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="ui-3babd42636.module">
<sha256 value="efa78f295387fe99b00f016a2465bf6b604e291801b7a1cca2766aa758786e04" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud.android-common" name="ui" version="48ed8e86d9">
<artifact name="ui-48ed8e86d9.aar">
<sha256 value="2c6d44febd77a3620a73b803bb577aed086af280276698736a81c26a4af292af" origin="Generated by Gradle" reason="Artifact is not signed"/>
Expand Down Expand Up @@ -21116,6 +21161,14 @@
<sha256 value="2154d71ba506b1de2e2645cc42accdbfaec67162c713e118a2bc4ecb5cd2196f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.nextcloud.android-common" name="ui" version="c7da76323d">
<artifact name="ui-c7da76323d.aar">
<sha256 value="041d67ffbb102dfa828719e35f1d416997706793c2674b9da4f135b3667c002c" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="ui-c7da76323d.module">
<sha256 value="26d0b02f74a82ec0f512e14006fd674d6773b65147e56b232cddba111b6d656c" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud.android-common" name="ui" version="df2f116806">
<artifact name="ui-df2f116806.aar">
<sha256 value="9eaf161c0535e87fec9585d97b5d5244229c7fdbde348bfebf7747f5bcd3f952" origin="Generated by Gradle"/>
Expand Down
Loading