From 073194ffee6a0cd9a154d8e389f25deb944d8eae Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 2 Jul 2025 10:40:57 +0200 Subject: [PATCH 01/13] wip Signed-off-by: tobiasKaminsky wip Signed-off-by: tobiasKaminsky wip Signed-off-by: tobiasKaminsky wip Signed-off-by: tobiasKaminsky wip Signed-off-by: tobiasKaminsky wip Signed-off-by: tobiasKaminsky --- .../client/database/NextcloudDatabase.kt | 3 +- .../database/entity/CapabilityEntity.kt | 4 +- .../nextcloud/ui/ClientIntegrationScreen.kt | 155 +++++++++++++ .../ui/composeActivity/ComposeActivity.kt | 57 ++++- .../ui/composeActivity/ComposeDestination.kt | 1 + .../nextcloud/ui/fileactions/FileAction.kt | 2 + .../ui/fileactions/FileActionsBottomSheet.kt | 214 +++++++++++++++++- .../ui/fileactions/FileActionsViewModel.kt | 1 + .../datamodel/FileDataStorageManager.java | 4 + .../com/owncloud/android/db/ProviderMeta.java | 3 +- .../operations/GetCapabilitiesOperation.java | 2 +- .../ui/fragment/OCFileListFragment.java | 7 +- .../layout/file_actions_bottom_sheet_item.xml | 2 - app/src/main/res/values/strings.xml | 2 + 14 files changed, 442 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt diff --git a/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt b/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt index a5bafa686668..1e95ae009ac5 100644 --- a/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt +++ b/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt @@ -91,7 +91,8 @@ import com.owncloud.android.db.ProviderMeta AutoMigration(from = 92, to = 93, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class), AutoMigration(from = 93, to = 94, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class), AutoMigration(from = 94, to = 95, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class), - AutoMigration(from = 95, to = 96) + AutoMigration(from = 95, to = 96), + AutoMigration(from = 96, to = 97, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class) ], exportSchema = true ) diff --git a/app/src/main/java/com/nextcloud/client/database/entity/CapabilityEntity.kt b/app/src/main/java/com/nextcloud/client/database/entity/CapabilityEntity.kt index 56f33b18f0bd..a2712c7b04f7 100644 --- a/app/src/main/java/com/nextcloud/client/database/entity/CapabilityEntity.kt +++ b/app/src/main/java/com/nextcloud/client/database/entity/CapabilityEntity.kt @@ -146,5 +146,7 @@ data class CapabilityEntity( @ColumnInfo(name = ProviderTableMeta.CAPABILITIES_WINDOWS_COMPATIBLE_FILENAMES) val isWCFEnabled: Int?, @ColumnInfo(name = ProviderTableMeta.CAPABILITIES_HAS_VALID_SUBSCRIPTION) - val hasValidSubscription: Int? + val hasValidSubscription: Int?, + @ColumnInfo(name = ProviderTableMeta.CAPABILITIES_CLIENT_INTEGRATION_JSON) + val clientIntegrationJson: String? ) diff --git a/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt b/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt new file mode 100644 index 000000000000..becfdd4215e1 --- /dev/null +++ b/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt @@ -0,0 +1,155 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Tobias Kaminsky + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.ui + +import android.app.Activity +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import com.nextcloud.android.lib.resources.clientintegration.Button +import com.nextcloud.android.lib.resources.clientintegration.ClientIntegrationUI +import com.nextcloud.android.lib.resources.clientintegration.Element +import com.nextcloud.android.lib.resources.clientintegration.Layout +import com.nextcloud.android.lib.resources.clientintegration.Orientation +import com.nextcloud.android.lib.resources.clientintegration.Text +import com.nextcloud.android.lib.resources.clientintegration.URL +import com.nextcloud.utils.extensions.getActivity +import com.owncloud.android.utils.DisplayUtils + +@Composable +fun ClientIntegrationScreen(clientIntegrationUI: ClientIntegrationUI, baseUrl: String) { + val activity = LocalContext.current.getActivity() + + Column { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { + TextButton({ close(activity) }) { + androidx.compose.material3.Text("X") + } + } + + Row { + if (clientIntegrationUI.root.orientation == Orientation.VERTICAL) { + Column { + clientIntegrationUI.root.rows.forEach { row -> + Row { + row.children.forEach { element -> + DisplayElement(element, baseUrl, activity) + } + } + } + } + } else { + Row { + clientIntegrationUI.root.rows.forEach { row -> + Column { + row.children.forEach { element -> + DisplayElement(element, baseUrl, activity) + } + } + } + } + } + } + } +} + +@Composable +private fun DisplayElement(element: Element, baseUrl: String, activity: Activity?) { + when (element) { + is Button -> androidx.compose.material3.Button({ }) { + androidx.compose.material3.Text(element.label) + } + + is URL -> TextButton({ + openLink(activity, baseUrl, element.url) + }) { androidx.compose.material3.Text(element.text) } + + is Text -> androidx.compose.material3.Text(element.text) + } +} + +private fun openLink(activity: Activity?, baseUrl: String, relativeUrl: String) { + activity?.let { + DisplayUtils.startLinkIntent(activity, baseUrl + relativeUrl) + } +} + +private fun close(activity: Activity?) { + activity?.finish() +} + +@Composable +@Preview +private fun ClientIntegrationScreenPreviewVertical() { + val clientIntegrationUI = ClientIntegrationUI( + 0.1, + Layout( + Orientation.VERTICAL, + mutableListOf( + com.nextcloud.android.lib.resources.clientintegration.Row( + listOf(Button("Click", "Primary"), Text("123")) + ), + com.nextcloud.android.lib.resources.clientintegration.Row( + listOf(Button("Click2", "Primary")) + ), + com.nextcloud.android.lib.resources.clientintegration.Row( + listOf(URL("Analytics report created", "https://nextcloud.com")) + ) + ) + ) + ) + + ClientIntegrationScreen( + clientIntegrationUI, + "http://nextcloud.local" + ) +} + +@Composable +@Preview +private fun ClientIntegrationScreenPreviewHorizontal() { + val clientIntegrationUI = ClientIntegrationUI( + 0.1, + Layout( + Orientation.HORIZONTAL, + mutableListOf( + com.nextcloud.android.lib.resources.clientintegration.Row( + listOf(Button("Click", "Primary"), Text("123")) + ), + com.nextcloud.android.lib.resources.clientintegration.Row( + listOf(Button("Click2", "Primary")) + ), + com.nextcloud.android.lib.resources.clientintegration.Row( + listOf(URL("Analytics report created", "https://nextcloud.com")) + ) + ) + ) + ) + + ClientIntegrationScreen(clientIntegrationUI, "http://nextcloud.local") +} + +@Composable +@Preview +private fun ClientIntegrationScreenPreviewEmpty() { + val clientIntegrationUI = ClientIntegrationUI( + 0.1, + Layout( + Orientation.HORIZONTAL, + emptyList() + ) + ) + + ClientIntegrationScreen(clientIntegrationUI, "http://nextcloud.local") +} diff --git a/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt index 27b295bac9c3..d352b9ce631c 100644 --- a/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt +++ b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt @@ -7,8 +7,11 @@ */ package com.nextcloud.ui.composeActivity +import android.annotation.SuppressLint +import android.content.Intent import android.os.Bundle import android.view.MenuItem +import android.view.View import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -17,6 +20,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import com.nextcloud.android.lib.resources.clientintegration.ClientIntegrationUI import com.nextcloud.client.assistant.AssistantScreen import com.nextcloud.client.assistant.AssistantViewModel import com.nextcloud.client.assistant.conversation.ConversationViewModel @@ -25,6 +29,8 @@ import com.nextcloud.client.assistant.repository.local.AssistantLocalRepositoryI import com.nextcloud.client.assistant.repository.remote.AssistantRemoteRepositoryImpl import com.nextcloud.client.database.NextcloudDatabase import com.nextcloud.common.NextcloudClient +import com.nextcloud.ui.ClientIntegrationScreen +import com.nextcloud.utils.extensions.getSerializableArgument import com.owncloud.android.R import com.owncloud.android.databinding.ActivityComposeBinding import com.owncloud.android.ui.activity.DrawerActivity @@ -36,6 +42,8 @@ class ComposeActivity : DrawerActivity() { companion object { const val DESTINATION = "DESTINATION" const val TITLE = "TITLE" + const val TITLE_STRING = "TITLE_STRING" + const val ARGS_CLIENT_INTEGRATION_UI = "ARGS_ClIENT_INTEGRATION_UI" } override fun onCreate(savedInstanceState: Bundle?) { @@ -46,12 +54,41 @@ class ComposeActivity : DrawerActivity() { val destinationId = intent.getIntExtra(DESTINATION, -1) val titleId = intent.getIntExtra(TITLE, R.string.empty) - setupDrawer() + if (title == null || title.isEmpty()) { + title = getString(intent.getIntExtra(TITLE, R.string.empty)) + } + + if (destination == ComposeDestination.AssistantScreen) { + setupDrawer() - setupToolbarShowOnlyMenuButtonAndTitle(getString(titleId)) { - openDrawer() + setupToolbarShowOnlyMenuButtonAndTitle(title) { + openDrawer() + } + } else { + setSupportActionBar(null) + if (findViewById(R.id.appbar) != null) { + findViewById(R.id.appbar)?.visibility = View.GONE + } } + // if (false) { + // val actionBar = getDelegate().supportActionBar + // actionBar?.setDisplayHomeAsUpEnabled(true) + // actionBar?.setDisplayShowTitleEnabled(true) + // + // val menuIcon = ResourcesCompat.getDrawable( + // getResources(), + // R.drawable.ic_arrow_back, + // null + // ) + // viewThemeUtils.androidx.themeActionBar( + // this, + // actionBar!!, + // title!!, + // menuIcon!! + // ) + // } + binding.composeView.setContent { MaterialTheme( colorScheme = viewThemeUtils.getColorScheme(this), @@ -64,12 +101,14 @@ class ComposeActivity : DrawerActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { android.R.id.home -> { - toggleDrawer() + super.onBackPressed() true } + else -> super.onOptionsItemSelected(item) } + @SuppressLint("CoroutineCreationDuringComposition") @Composable private fun Content(destination: ComposeDestination) { val currentScreen by ComposeNavigation.currentScreen.collectAsState() @@ -104,7 +143,15 @@ class ComposeActivity : DrawerActivity() { capability = capabilities ) } - else -> Unit + } else if (destination == ComposeDestination.ClientIntegrationScreen) { + binding.bottomNavigation.visibility = View.GONE + + val clientIntegrationUI: ClientIntegrationUI? = intent.getParcelableExtra(ARGS_CLIENT_INTEGRATION_UI) + + clientIntegrationUI?.let { ClientIntegrationScreen(it, nextcloudClient?.baseUri.toString()) } + + } else { + Unit } } } diff --git a/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeDestination.kt b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeDestination.kt index 050c8e5a2848..dd1cdba6af8e 100644 --- a/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeDestination.kt +++ b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeDestination.kt @@ -13,6 +13,7 @@ sealed class ComposeDestination(val id: Int) { companion object { fun fromId(id: Int): ComposeDestination = when (id) { 0 -> AssistantScreen(null) + 1 -> ClientIntegrationScreen() else -> throw IllegalArgumentException("Unknown destination: $id") } } diff --git a/app/src/main/java/com/nextcloud/ui/fileactions/FileAction.kt b/app/src/main/java/com/nextcloud/ui/fileactions/FileAction.kt index 6e265a2a7cd8..43f1edc09109 100644 --- a/app/src/main/java/com/nextcloud/ui/fileactions/FileAction.kt +++ b/app/src/main/java/com/nextcloud/ui/fileactions/FileAction.kt @@ -64,6 +64,8 @@ enum class FileAction( // Retry for offline operation RETRY(R.id.action_retry, R.string.retry, R.drawable.ic_retry); + constructor(id: Int, title: Int) : this(id, title, null) + companion object { /** * All file actions, in the order they should be displayed diff --git a/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt b/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt index 7a67b8345b1c..10727248fb0c 100644 --- a/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt +++ b/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt @@ -8,16 +8,24 @@ package com.nextcloud.ui.fileactions import android.content.Context +import android.content.Intent import android.content.res.ColorStateList +import android.graphics.Canvas import android.graphics.Typeface import android.graphics.drawable.Drawable +import android.graphics.drawable.PictureDrawable +import android.os.Build import android.os.Bundle import android.text.style.StyleSpan import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.annotation.IdRes +import androidx.annotation.RequiresApi import androidx.appcompat.content.res.AppCompatResources +import androidx.core.graphics.createBitmap +import androidx.core.graphics.drawable.toDrawable +import androidx.core.net.toUri import androidx.core.os.bundleOf import androidx.core.view.isEmpty import androidx.core.view.isVisible @@ -25,14 +33,33 @@ import androidx.fragment.app.FragmentManager import androidx.fragment.app.setFragmentResult import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.coroutineScope import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.JsonElement +import com.google.gson.JsonParser +import com.google.gson.reflect.TypeToken import com.nextcloud.android.common.ui.theme.utils.ColorRole +import com.nextcloud.android.lib.resources.clientintegration.ClientIntegrationUI +import com.nextcloud.android.lib.resources.clientintegration.Element +import com.nextcloud.android.lib.resources.clientintegration.ElementTypeAdapter +import com.nextcloud.android.lib.resources.clientintegration.Endpoint +import com.nextcloud.android.lib.resources.clientintegration.TooltipResponse import com.nextcloud.client.account.CurrentAccountProvider import com.nextcloud.client.di.Injectable import com.nextcloud.client.di.ViewModelFactory +import com.nextcloud.common.JSONRequestBody +import com.nextcloud.operations.GetMethod +import com.nextcloud.operations.PostMethod +import com.nextcloud.ui.composeActivity.ComposeActivity +import com.nextcloud.ui.composeActivity.ComposeDestination +import com.nextcloud.ui.fileactions.FileActionsViewModel.Companion.ARG_ENDPOINTS +import com.nextcloud.utils.GlideHelper import com.nextcloud.utils.extensions.setVisibleIf +import com.nextcloud.utils.extensions.showToast import com.owncloud.android.R import com.owncloud.android.databinding.FileActionsBottomSheetBinding import com.owncloud.android.databinding.FileActionsBottomSheetItemBinding @@ -40,12 +67,20 @@ import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile import com.owncloud.android.datamodel.SyncedFolderProvider import com.owncloud.android.datamodel.ThumbnailsCacheManager +import com.owncloud.android.lib.common.OwnCloudClientManagerFactory +import com.owncloud.android.lib.ocs.ServerResponse import com.owncloud.android.lib.resources.files.model.FileLockType +import com.owncloud.android.lib.resources.status.Method import com.owncloud.android.ui.activity.ComponentsGetter import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener import com.owncloud.android.utils.FileStorageUtils import com.owncloud.android.utils.theme.ViewThemeUtils +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import okhttp3.RequestBody import javax.inject.Inject class FileActionsBottomSheet : @@ -77,6 +112,8 @@ class FileActionsBottomSheet : private val thumbnailAsyncTasks = mutableListOf() + private var endpoints: List? = mutableListOf() + fun interface ResultListener { fun onResult(@IdRes actionId: Int) } @@ -93,6 +130,8 @@ class FileActionsBottomSheet : viewModel.load(requireArguments(), componentsGetter) + endpoints = arguments?.getParcelableArrayList(ARG_ENDPOINTS) + val bottomSheetDialog = dialog as BottomSheetDialog bottomSheetDialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED bottomSheetDialog.behavior.skipCollapsed = true @@ -199,6 +238,13 @@ class FileActionsBottomSheet : val view = inflateActionView(action) binding.fileActionsList.addView(view) } + // add client integration + if (endpoints != null) { + for (val e in endpoints) { + val ui = inflateClientIntegrationActionView(e) + binding.fileActionsList.addView(ui) + } + } } } @@ -302,6 +348,165 @@ class FileActionsBottomSheet : return itemBinding.root } + @RequiresApi(Build.VERSION_CODES.Q) + private fun inflateClientIntegrationActionView(endpoint: Endpoint): View { + val itemBinding = FileActionsBottomSheetItemBinding.inflate(layoutInflater, binding.fileActionsList, false) + .apply { + root.setOnClickListener { + if (viewModel.uiState.value is FileActionsViewModel.UiState.LoadedForSingleFile) { + val singleFile = (viewModel.uiState.value as FileActionsViewModel.UiState.LoadedForSingleFile) + + val fileId = singleFile.titleFile?.localId.toString() + val filePath = singleFile.titleFile?.remotePath.toString() + + requestClientIntegration(endpoint, fileId, filePath) + } else { + requestClientIntegration(endpoint, "", "") + } + } + text.text = endpoint.name + + if (endpoint.icon != null) { + CoroutineScope(Dispatchers.IO).launch { + val client = OwnCloudClientManagerFactory.getDefaultSingleton() + .getNextcloudClientFor(currentUserProvider.user.toOwnCloudAccount(), context) + + val drawable = + GlideHelper.getDrawable(requireContext(), client, client.baseUri.toString() + endpoint.icon) + ?.mutate() + + val px = DisplayUtils.convertDpToPixel( + requireContext().resources.getDimension(R.dimen.iconized_single_line_item_icon_size), + requireContext() + ) + val returnedBitmap = + createBitmap(drawable?.intrinsicWidth ?: px, drawable?.intrinsicHeight ?: px) + + val canvas = Canvas(returnedBitmap) + canvas.drawPicture((drawable as PictureDrawable).picture) + + val d = returnedBitmap.toDrawable(requireContext().resources); + + val tintedDrawable = viewThemeUtils.platform.tintDrawable( + requireContext(), + d + ) + + withContext(Dispatchers.Main) { + icon.setImageDrawable(tintedDrawable) + } + } + } else { + val tintedDrawable = viewThemeUtils.platform.tintDrawable( + requireContext(), + AppCompatResources.getDrawable(requireContext(), R.drawable.ic_activity)!! + ) + + icon.setImageDrawable(tintedDrawable) + } + } + return itemBinding.root + } + + private fun requestClientIntegration(endpoint: Endpoint, fileId: String, filePath: String) { + lifecycle.coroutineScope.launch(Dispatchers.IO) { + val client = OwnCloudClientManagerFactory.getDefaultSingleton() + .getNextcloudClientFor(currentUserProvider.user.toOwnCloudAccount(), context) + + // construct url + var url = (client.baseUri.toString() + endpoint.url).toUri() + .buildUpon() + .appendQueryParameter("format", "json") + .build() + .toString() + + // Always replace known placeholder in url + url = url.replace("{filePath}", filePath, false) + url = url.replace("{fileId}", fileId, false) + + val method = when (endpoint.method) { + Method.POST -> { + val requestBody = if (endpoint.params?.isNotEmpty() == true) { + val jsonRequestBody = JSONRequestBody() + endpoint.params!!.forEach { + when (it.value) { + "{filePath}" -> jsonRequestBody.put(it.key, filePath) + "{fileId}" -> jsonRequestBody.put(it.key, fileId) + } + } + + jsonRequestBody.get() + } else { + RequestBody.EMPTY + } + + PostMethod(url, true, requestBody) + } + + else -> GetMethod(url, true) + } + + val result = try { + client.execute(method) + } catch (exception: Exception) { + activity?.showToast(getString(R.string.failed_to_start_action)) + } + val response = method.getResponseBodyAsString() + + var output: ClientIntegrationUI? + try { + output = parseClientIntegrationResult(response) + if (output.root != null) { + startClientIntegration(endpoint, output) + } else { + val tooltipResponse = parseTooltipResult(response) + + activity?.showToast(tooltipResponse.tooltip) + } + } catch (e: Exception) { + if (result == 200) { + activity?.showToast(getString(R.string.action_triggered)) + } else { + activity?.showToast(getString(R.string.failed_to_start_action)) + } + } + dismiss() + } + } + + private fun startClientIntegration(endpoint: Endpoint, clientIntegrationUI: ClientIntegrationUI) { + CoroutineScope(Dispatchers.IO).launch { + val composeActivity = Intent(context, ComposeActivity::class.java) + composeActivity.putExtra(ComposeActivity.DESTINATION, ComposeDestination.ClientIntegrationScreen) + composeActivity.putExtra(ComposeActivity.ARGS_CLIENT_INTEGRATION_UI, clientIntegrationUI) + + composeActivity.putExtra(ComposeActivity.TITLE, endpoint.name) + startActivity(composeActivity) + dismiss() + } + } + + private fun parseClientIntegrationResult(response: String?): ClientIntegrationUI { + val gson = + GsonBuilder() + .registerTypeHierarchyAdapter(Element::class.java, ElementTypeAdapter()) + .create() + + val element: JsonElement = JsonParser.parseString(response) + return gson + .fromJson(element, object : TypeToken>() {}) + .ocs + .data + } + + private fun parseTooltipResult(response: String?): TooltipResponse { + val element: JsonElement = JsonParser.parseString(response) + return Gson() + .fromJson(element, object : TypeToken>() {}) + .ocs + .data + } + private fun dispatchActionClick(id: Int?) { if (id != null) { setFragmentResult(REQUEST_KEY, bundleOf(RESULT_KEY_ACTION_ID to id)) @@ -314,6 +519,7 @@ class FileActionsBottomSheet : private const val REQUEST_KEY = "REQUEST_KEY_ACTION" private const val RESULT_KEY_ACTION_ID = "RESULT_KEY_ACTION_ID" private const val FILENAME_MAX_WIDTH_PERCENTAGE = 0.6 + private const val JSON_FORMAT = "?format=json" @JvmStatic @JvmOverloads @@ -322,7 +528,7 @@ class FileActionsBottomSheet : isOverflow: Boolean, @IdRes additionalToHide: List? = null - ): FileActionsBottomSheet = newInstance(1, listOf(file), isOverflow, additionalToHide, true) + ): FileActionsBottomSheet = newInstance(1, listOf(file), isOverflow, additionalToHide, true, emptyList()) @JvmStatic @JvmOverloads @@ -332,13 +538,15 @@ class FileActionsBottomSheet : isOverflow: Boolean, @IdRes additionalToHide: List? = null, - inSingleFileFragment: Boolean = false + inSingleFileFragment: Boolean = false, + endpoints: List ): FileActionsBottomSheet = FileActionsBottomSheet().apply { val argsBundle = bundleOf( FileActionsViewModel.ARG_ALL_FILES_COUNT to numberOfAllFiles, FileActionsViewModel.ARG_FILES to ArrayList(files), FileActionsViewModel.ARG_IS_OVERFLOW to isOverflow, - FileActionsViewModel.ARG_IN_SINGLE_FILE_FRAGMENT to inSingleFileFragment + FileActionsViewModel.ARG_IN_SINGLE_FILE_FRAGMENT to inSingleFileFragment, + FileActionsViewModel.ARG_ENDPOINTS to endpoints ) additionalToHide?.let { argsBundle.putIntArray(FileActionsViewModel.ARG_ADDITIONAL_FILTER, additionalToHide.toIntArray()) diff --git a/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsViewModel.kt b/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsViewModel.kt index f42015b99518..62b310b010c2 100644 --- a/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsViewModel.kt +++ b/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsViewModel.kt @@ -140,6 +140,7 @@ class FileActionsViewModel @Inject constructor( const val ARG_IS_OVERFLOW = "OVERFLOW" const val ARG_ADDITIONAL_FILTER = "ADDITIONAL_FILTER" const val ARG_IN_SINGLE_FILE_FRAGMENT = "IN_SINGLE_FILE_FRAGMENT" + const val ARG_ENDPOINTS = "ENDPOINTS" private val TAG = FileActionsViewModel::class.simpleName!! } diff --git a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java index 0b0bba6c6812..497776f6babf 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -2318,6 +2318,8 @@ private ContentValues createContentValues(String accountName, OCCapability capab contentValues.put(ProviderTableMeta.CAPABILITIES_HAS_VALID_SUBSCRIPTION, capability.getHasValidSubscription().getValue()); + contentValues.put(ProviderTableMeta.CAPABILITIES_CLIENT_INTEGRATION_JSON, capability.getClientIntegrationJson()); + return contentValues; } @@ -2503,6 +2505,8 @@ private OCCapability createCapabilityInstance(Cursor cursor) { capability.setDefaultPermissions(getInt(cursor, ProviderTableMeta.CAPABILITIES_DEFAULT_PERMISSIONS)); capability.setHasValidSubscription(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_HAS_VALID_SUBSCRIPTION)); + + capability.setClientIntegrationJson(getString(cursor, ProviderTableMeta.CAPABILITIES_CLIENT_INTEGRATION_JSON)); } return capability; diff --git a/app/src/main/java/com/owncloud/android/db/ProviderMeta.java b/app/src/main/java/com/owncloud/android/db/ProviderMeta.java index 46905d69231d..6168709a8b08 100644 --- a/app/src/main/java/com/owncloud/android/db/ProviderMeta.java +++ b/app/src/main/java/com/owncloud/android/db/ProviderMeta.java @@ -23,7 +23,7 @@ */ public class ProviderMeta { public static final String DB_NAME = "filelist"; - public static final int DB_VERSION = 96; + public static final int DB_VERSION = 97; private ProviderMeta() { // No instance @@ -292,6 +292,7 @@ static public class ProviderTableMeta implements BaseColumns { public static final String CAPABILITIES_NOTES_FOLDER_PATH = "notes_folder_path"; public static final String CAPABILITIES_DEFAULT_PERMISSIONS = "default_permissions"; public static final String CAPABILITIES_HAS_VALID_SUBSCRIPTION = "has_valid_subscription"; + public static final String CAPABILITIES_CLIENT_INTEGRATION_JSON = "client_integration_json"; //Columns of Uploads table public static final String UPLOADS_LOCAL_PATH = "local_path"; diff --git a/app/src/main/java/com/owncloud/android/operations/GetCapabilitiesOperation.java b/app/src/main/java/com/owncloud/android/operations/GetCapabilitiesOperation.java index 9e86d161a6d0..e895f3b36aa6 100644 --- a/app/src/main/java/com/owncloud/android/operations/GetCapabilitiesOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/GetCapabilitiesOperation.java @@ -36,7 +36,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { currentCapability = storageManager.getCapability(storageManager.getUser().getAccountName()); } - RemoteOperationResult result = new GetCapabilitiesRemoteOperation(currentCapability).execute(client); + RemoteOperationResult result = new GetCapabilitiesRemoteOperation(null).execute(client); if (result.isSuccess() && result.getData() != null && result.getData().size() > 0) { diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 981a234510b0..ae81d56b0427 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -38,6 +38,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.snackbar.Snackbar; +import com.nextcloud.android.lib.resources.clientintegration.Endpoint; import com.nextcloud.android.lib.resources.files.ToggleFileLockRemoteOperation; import com.nextcloud.client.account.User; import com.nextcloud.client.device.DeviceInfo; @@ -79,6 +80,7 @@ import com.owncloud.android.lib.resources.files.ToggleFavoriteRemoteOperation; import com.owncloud.android.lib.resources.status.E2EVersion; import com.owncloud.android.lib.resources.status.OCCapability; +import com.owncloud.android.lib.resources.status.Type; import com.owncloud.android.ui.activity.DrawerActivity; import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.activity.FileDisplayActivity; @@ -643,8 +645,11 @@ public void onOverflowIconClicked(OCFile file, View view) { public void openActionsMenu(final int filesCount, final Set checkedFiles, final boolean isOverflow) { throttler.run("overflowClick", () -> { final var actionsToHide = FileAction.Companion.getFileListActionsToHide(checkedFiles); + + List endpoints = getCapabilities().getClientIntegrationEndpoints(Type.CONTEXT_MENU, checkedFiles.iterator().next().getMimeType()); + final var childFragmentManager = getChildFragmentManager(); - final var actionBottomSheet = FileActionsBottomSheet.newInstance(filesCount, checkedFiles, isOverflow, actionsToHide) + final var actionBottomSheet = FileActionsBottomSheet.newInstance(filesCount, checkedFiles, isOverflow, actionsToHide, endpoints) .setResultListener(childFragmentManager, this, (id) -> onFileActionChosen(id, checkedFiles)); if (FragmentExtensionsKt.isDialogFragmentReady(this)) { diff --git a/app/src/main/res/layout/file_actions_bottom_sheet_item.xml b/app/src/main/res/layout/file_actions_bottom_sheet_item.xml index 77b245fee53b..b2a7a30738ee 100644 --- a/app/src/main/res/layout/file_actions_bottom_sheet_item.xml +++ b/app/src/main/res/layout/file_actions_bottom_sheet_item.xml @@ -7,7 +7,6 @@ ~ SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only --> Failed to create conflict dialog Cannot open file chooser + Failed to start action! + Action triggered From ff42bded8286055ae19f99747ae23e59a5c38abb Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 10 Dec 2025 13:00:40 +0100 Subject: [PATCH 02/13] wip Signed-off-by: tobiasKaminsky # Conflicts: # gradle/libs.versions.toml --- .../97.json | 1298 +++++++++++++++++ .../client/database/NextcloudDatabase.kt | 2 +- .../nextcloud/ui/ClientIntegrationScreen.kt | 7 +- .../ui/composeActivity/ComposeActivity.kt | 62 +- .../ui/composeActivity/ComposeDestination.kt | 3 +- .../ui/fileactions/FileActionsBottomSheet.kt | 15 +- gradle/verification-metadata.xml | 22 + 7 files changed, 1358 insertions(+), 51 deletions(-) create mode 100644 app/schemas/com.nextcloud.client.database.NextcloudDatabase/97.json diff --git a/app/schemas/com.nextcloud.client.database.NextcloudDatabase/97.json b/app/schemas/com.nextcloud.client.database.NextcloudDatabase/97.json new file mode 100644 index 000000000000..27f506701672 --- /dev/null +++ b/app/schemas/com.nextcloud.client.database.NextcloudDatabase/97.json @@ -0,0 +1,1298 @@ +{ + "formatVersion": 1, + "database": { + "version": 97, + "identityHash": "1c5a77152bf79ee80f9e6eb2677d75a7", + "entities": [ + { + "tableName": "arbitrary_data", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `cloud_id` TEXT, `key` TEXT, `value` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "cloudId", + "columnName": "cloud_id", + "affinity": "TEXT" + }, + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT" + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "capabilities", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `assistant` INTEGER, `account` TEXT, `version_mayor` INTEGER, `version_minor` INTEGER, `version_micro` INTEGER, `version_string` TEXT, `version_edition` TEXT, `extended_support` INTEGER, `core_pollinterval` INTEGER, `sharing_api_enabled` INTEGER, `sharing_public_enabled` INTEGER, `sharing_public_password_enforced` INTEGER, `sharing_public_expire_date_enabled` INTEGER, `sharing_public_expire_date_days` INTEGER, `sharing_public_expire_date_enforced` INTEGER, `sharing_public_send_mail` INTEGER, `sharing_public_upload` INTEGER, `sharing_user_send_mail` INTEGER, `sharing_resharing` INTEGER, `sharing_federation_outgoing` INTEGER, `sharing_federation_incoming` INTEGER, `files_bigfilechunking` INTEGER, `files_undelete` INTEGER, `files_versioning` INTEGER, `external_links` INTEGER, `server_name` TEXT, `server_color` TEXT, `server_text_color` TEXT, `server_element_color` TEXT, `server_slogan` TEXT, `server_logo` TEXT, `background_url` TEXT, `end_to_end_encryption` INTEGER, `end_to_end_encryption_keys_exist` INTEGER, `end_to_end_encryption_api_version` TEXT, `activity` INTEGER, `background_default` INTEGER, `background_plain` INTEGER, `richdocument` INTEGER, `richdocument_mimetype_list` TEXT, `richdocument_direct_editing` INTEGER, `richdocument_direct_templates` INTEGER, `richdocument_optional_mimetype_list` TEXT, `sharing_public_ask_for_optional_password` INTEGER, `richdocument_product_name` TEXT, `direct_editing_etag` TEXT, `user_status` INTEGER, `user_status_supports_emoji` INTEGER, `etag` TEXT, `files_locking_version` TEXT, `groupfolders` INTEGER, `drop_account` INTEGER, `security_guard` INTEGER, `forbidden_filename_characters` INTEGER, `forbidden_filenames` INTEGER, `forbidden_filename_extensions` INTEGER, `forbidden_filename_basenames` INTEGER, `files_download_limit` INTEGER, `files_download_limit_default` INTEGER, `recommendation` INTEGER, `notes_folder_path` TEXT, `default_permissions` INTEGER, `user_status_supports_busy` INTEGER, `windows_compatible_filenames` INTEGER, `has_valid_subscription` INTEGER, `client_integration_json` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "assistant", + "columnName": "assistant", + "affinity": "INTEGER" + }, + { + "fieldPath": "accountName", + "columnName": "account", + "affinity": "TEXT" + }, + { + "fieldPath": "versionMajor", + "columnName": "version_mayor", + "affinity": "INTEGER" + }, + { + "fieldPath": "versionMinor", + "columnName": "version_minor", + "affinity": "INTEGER" + }, + { + "fieldPath": "versionMicro", + "columnName": "version_micro", + "affinity": "INTEGER" + }, + { + "fieldPath": "versionString", + "columnName": "version_string", + "affinity": "TEXT" + }, + { + "fieldPath": "versionEditor", + "columnName": "version_edition", + "affinity": "TEXT" + }, + { + "fieldPath": "extendedSupport", + "columnName": "extended_support", + "affinity": "INTEGER" + }, + { + "fieldPath": "corePollinterval", + "columnName": "core_pollinterval", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingApiEnabled", + "columnName": "sharing_api_enabled", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicEnabled", + "columnName": "sharing_public_enabled", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicPasswordEnforced", + "columnName": "sharing_public_password_enforced", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicExpireDateEnabled", + "columnName": "sharing_public_expire_date_enabled", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicExpireDateDays", + "columnName": "sharing_public_expire_date_days", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicExpireDateEnforced", + "columnName": "sharing_public_expire_date_enforced", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicSendMail", + "columnName": "sharing_public_send_mail", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingPublicUpload", + "columnName": "sharing_public_upload", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingUserSendMail", + "columnName": "sharing_user_send_mail", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingResharing", + "columnName": "sharing_resharing", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingFederationOutgoing", + "columnName": "sharing_federation_outgoing", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharingFederationIncoming", + "columnName": "sharing_federation_incoming", + "affinity": "INTEGER" + }, + { + "fieldPath": "filesBigfilechunking", + "columnName": "files_bigfilechunking", + "affinity": "INTEGER" + }, + { + "fieldPath": "filesUndelete", + "columnName": "files_undelete", + "affinity": "INTEGER" + }, + { + "fieldPath": "filesVersioning", + "columnName": "files_versioning", + "affinity": "INTEGER" + }, + { + "fieldPath": "externalLinks", + "columnName": "external_links", + "affinity": "INTEGER" + }, + { + "fieldPath": "serverName", + "columnName": "server_name", + "affinity": "TEXT" + }, + { + "fieldPath": "serverColor", + "columnName": "server_color", + "affinity": "TEXT" + }, + { + "fieldPath": "serverTextColor", + "columnName": "server_text_color", + "affinity": "TEXT" + }, + { + "fieldPath": "serverElementColor", + "columnName": "server_element_color", + "affinity": "TEXT" + }, + { + "fieldPath": "serverSlogan", + "columnName": "server_slogan", + "affinity": "TEXT" + }, + { + "fieldPath": "serverLogo", + "columnName": "server_logo", + "affinity": "TEXT" + }, + { + "fieldPath": "serverBackgroundUrl", + "columnName": "background_url", + "affinity": "TEXT" + }, + { + "fieldPath": "endToEndEncryption", + "columnName": "end_to_end_encryption", + "affinity": "INTEGER" + }, + { + "fieldPath": "endToEndEncryptionKeysExist", + "columnName": "end_to_end_encryption_keys_exist", + "affinity": "INTEGER" + }, + { + "fieldPath": "endToEndEncryptionApiVersion", + "columnName": "end_to_end_encryption_api_version", + "affinity": "TEXT" + }, + { + "fieldPath": "activity", + "columnName": "activity", + "affinity": "INTEGER" + }, + { + "fieldPath": "serverBackgroundDefault", + "columnName": "background_default", + "affinity": "INTEGER" + }, + { + "fieldPath": "serverBackgroundPlain", + "columnName": "background_plain", + "affinity": "INTEGER" + }, + { + "fieldPath": "richdocument", + "columnName": "richdocument", + "affinity": "INTEGER" + }, + { + "fieldPath": "richdocumentMimetypeList", + "columnName": "richdocument_mimetype_list", + "affinity": "TEXT" + }, + { + "fieldPath": "richdocumentDirectEditing", + "columnName": "richdocument_direct_editing", + "affinity": "INTEGER" + }, + { + "fieldPath": "richdocumentTemplates", + "columnName": "richdocument_direct_templates", + "affinity": "INTEGER" + }, + { + "fieldPath": "richdocumentOptionalMimetypeList", + "columnName": "richdocument_optional_mimetype_list", + "affinity": "TEXT" + }, + { + "fieldPath": "sharingPublicAskForOptionalPassword", + "columnName": "sharing_public_ask_for_optional_password", + "affinity": "INTEGER" + }, + { + "fieldPath": "richdocumentProductName", + "columnName": "richdocument_product_name", + "affinity": "TEXT" + }, + { + "fieldPath": "directEditingEtag", + "columnName": "direct_editing_etag", + "affinity": "TEXT" + }, + { + "fieldPath": "userStatus", + "columnName": "user_status", + "affinity": "INTEGER" + }, + { + "fieldPath": "userStatusSupportsEmoji", + "columnName": "user_status_supports_emoji", + "affinity": "INTEGER" + }, + { + "fieldPath": "etag", + "columnName": "etag", + "affinity": "TEXT" + }, + { + "fieldPath": "filesLockingVersion", + "columnName": "files_locking_version", + "affinity": "TEXT" + }, + { + "fieldPath": "groupfolders", + "columnName": "groupfolders", + "affinity": "INTEGER" + }, + { + "fieldPath": "dropAccount", + "columnName": "drop_account", + "affinity": "INTEGER" + }, + { + "fieldPath": "securityGuard", + "columnName": "security_guard", + "affinity": "INTEGER" + }, + { + "fieldPath": "forbiddenFileNameCharacters", + "columnName": "forbidden_filename_characters", + "affinity": "INTEGER" + }, + { + "fieldPath": "forbiddenFileNames", + "columnName": "forbidden_filenames", + "affinity": "INTEGER" + }, + { + "fieldPath": "forbiddenFileNameExtensions", + "columnName": "forbidden_filename_extensions", + "affinity": "INTEGER" + }, + { + "fieldPath": "forbiddenFilenameBaseNames", + "columnName": "forbidden_filename_basenames", + "affinity": "INTEGER" + }, + { + "fieldPath": "filesDownloadLimit", + "columnName": "files_download_limit", + "affinity": "INTEGER" + }, + { + "fieldPath": "filesDownloadLimitDefault", + "columnName": "files_download_limit_default", + "affinity": "INTEGER" + }, + { + "fieldPath": "recommendation", + "columnName": "recommendation", + "affinity": "INTEGER" + }, + { + "fieldPath": "notesFolderPath", + "columnName": "notes_folder_path", + "affinity": "TEXT" + }, + { + "fieldPath": "defaultPermissions", + "columnName": "default_permissions", + "affinity": "INTEGER" + }, + { + "fieldPath": "userStatusSupportsBusy", + "columnName": "user_status_supports_busy", + "affinity": "INTEGER" + }, + { + "fieldPath": "isWCFEnabled", + "columnName": "windows_compatible_filenames", + "affinity": "INTEGER" + }, + { + "fieldPath": "hasValidSubscription", + "columnName": "has_valid_subscription", + "affinity": "INTEGER" + }, + { + "fieldPath": "clientIntegrationJson", + "columnName": "client_integration_json", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "external_links", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `icon_url` TEXT, `language` TEXT, `type` INTEGER, `name` TEXT, `url` TEXT, `redirect` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT" + }, + { + "fieldPath": "language", + "columnName": "language", + "affinity": "TEXT" + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT" + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT" + }, + { + "fieldPath": "redirect", + "columnName": "redirect", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "filelist", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `filename` TEXT, `encrypted_filename` TEXT, `path` TEXT, `path_decrypted` TEXT, `parent` INTEGER, `created` INTEGER, `modified` INTEGER, `content_type` TEXT, `content_length` INTEGER, `media_path` TEXT, `file_owner` TEXT, `last_sync_date` INTEGER, `last_sync_date_for_data` INTEGER, `modified_at_last_sync_for_data` INTEGER, `etag` TEXT, `etag_on_server` TEXT, `share_by_link` INTEGER, `permissions` TEXT, `remote_id` TEXT, `local_id` INTEGER NOT NULL DEFAULT -1, `update_thumbnail` INTEGER, `is_downloading` INTEGER, `favorite` INTEGER, `hidden` INTEGER, `is_encrypted` INTEGER, `etag_in_conflict` TEXT, `shared_via_users` INTEGER, `mount_type` INTEGER, `has_preview` INTEGER, `unread_comments_count` INTEGER, `owner_id` TEXT, `owner_display_name` TEXT, `note` TEXT, `sharees` TEXT, `rich_workspace` TEXT, `metadata_size` TEXT, `metadata_live_photo` TEXT, `locked` INTEGER, `lock_type` INTEGER, `lock_owner` TEXT, `lock_owner_display_name` TEXT, `lock_owner_editor` TEXT, `lock_timestamp` INTEGER, `lock_timeout` INTEGER, `lock_token` TEXT, `tags` TEXT, `metadata_gps` TEXT, `e2e_counter` INTEGER, `internal_two_way_sync_timestamp` INTEGER, `internal_two_way_sync_result` TEXT, `uploaded` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "name", + "columnName": "filename", + "affinity": "TEXT" + }, + { + "fieldPath": "encryptedName", + "columnName": "encrypted_filename", + "affinity": "TEXT" + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT" + }, + { + "fieldPath": "pathDecrypted", + "columnName": "path_decrypted", + "affinity": "TEXT" + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "INTEGER" + }, + { + "fieldPath": "creation", + "columnName": "created", + "affinity": "INTEGER" + }, + { + "fieldPath": "modified", + "columnName": "modified", + "affinity": "INTEGER" + }, + { + "fieldPath": "contentType", + "columnName": "content_type", + "affinity": "TEXT" + }, + { + "fieldPath": "contentLength", + "columnName": "content_length", + "affinity": "INTEGER" + }, + { + "fieldPath": "storagePath", + "columnName": "media_path", + "affinity": "TEXT" + }, + { + "fieldPath": "accountOwner", + "columnName": "file_owner", + "affinity": "TEXT" + }, + { + "fieldPath": "lastSyncDate", + "columnName": "last_sync_date", + "affinity": "INTEGER" + }, + { + "fieldPath": "lastSyncDateForData", + "columnName": "last_sync_date_for_data", + "affinity": "INTEGER" + }, + { + "fieldPath": "modifiedAtLastSyncForData", + "columnName": "modified_at_last_sync_for_data", + "affinity": "INTEGER" + }, + { + "fieldPath": "etag", + "columnName": "etag", + "affinity": "TEXT" + }, + { + "fieldPath": "etagOnServer", + "columnName": "etag_on_server", + "affinity": "TEXT" + }, + { + "fieldPath": "sharedViaLink", + "columnName": "share_by_link", + "affinity": "INTEGER" + }, + { + "fieldPath": "permissions", + "columnName": "permissions", + "affinity": "TEXT" + }, + { + "fieldPath": "remoteId", + "columnName": "remote_id", + "affinity": "TEXT" + }, + { + "fieldPath": "localId", + "columnName": "local_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "updateThumbnail", + "columnName": "update_thumbnail", + "affinity": "INTEGER" + }, + { + "fieldPath": "isDownloading", + "columnName": "is_downloading", + "affinity": "INTEGER" + }, + { + "fieldPath": "favorite", + "columnName": "favorite", + "affinity": "INTEGER" + }, + { + "fieldPath": "hidden", + "columnName": "hidden", + "affinity": "INTEGER" + }, + { + "fieldPath": "isEncrypted", + "columnName": "is_encrypted", + "affinity": "INTEGER" + }, + { + "fieldPath": "etagInConflict", + "columnName": "etag_in_conflict", + "affinity": "TEXT" + }, + { + "fieldPath": "sharedWithSharee", + "columnName": "shared_via_users", + "affinity": "INTEGER" + }, + { + "fieldPath": "mountType", + "columnName": "mount_type", + "affinity": "INTEGER" + }, + { + "fieldPath": "hasPreview", + "columnName": "has_preview", + "affinity": "INTEGER" + }, + { + "fieldPath": "unreadCommentsCount", + "columnName": "unread_comments_count", + "affinity": "INTEGER" + }, + { + "fieldPath": "ownerId", + "columnName": "owner_id", + "affinity": "TEXT" + }, + { + "fieldPath": "ownerDisplayName", + "columnName": "owner_display_name", + "affinity": "TEXT" + }, + { + "fieldPath": "note", + "columnName": "note", + "affinity": "TEXT" + }, + { + "fieldPath": "sharees", + "columnName": "sharees", + "affinity": "TEXT" + }, + { + "fieldPath": "richWorkspace", + "columnName": "rich_workspace", + "affinity": "TEXT" + }, + { + "fieldPath": "metadataSize", + "columnName": "metadata_size", + "affinity": "TEXT" + }, + { + "fieldPath": "metadataLivePhoto", + "columnName": "metadata_live_photo", + "affinity": "TEXT" + }, + { + "fieldPath": "locked", + "columnName": "locked", + "affinity": "INTEGER" + }, + { + "fieldPath": "lockType", + "columnName": "lock_type", + "affinity": "INTEGER" + }, + { + "fieldPath": "lockOwner", + "columnName": "lock_owner", + "affinity": "TEXT" + }, + { + "fieldPath": "lockOwnerDisplayName", + "columnName": "lock_owner_display_name", + "affinity": "TEXT" + }, + { + "fieldPath": "lockOwnerEditor", + "columnName": "lock_owner_editor", + "affinity": "TEXT" + }, + { + "fieldPath": "lockTimestamp", + "columnName": "lock_timestamp", + "affinity": "INTEGER" + }, + { + "fieldPath": "lockTimeout", + "columnName": "lock_timeout", + "affinity": "INTEGER" + }, + { + "fieldPath": "lockToken", + "columnName": "lock_token", + "affinity": "TEXT" + }, + { + "fieldPath": "tags", + "columnName": "tags", + "affinity": "TEXT" + }, + { + "fieldPath": "metadataGPS", + "columnName": "metadata_gps", + "affinity": "TEXT" + }, + { + "fieldPath": "e2eCounter", + "columnName": "e2e_counter", + "affinity": "INTEGER" + }, + { + "fieldPath": "internalTwoWaySync", + "columnName": "internal_two_way_sync_timestamp", + "affinity": "INTEGER" + }, + { + "fieldPath": "internalTwoWaySyncResult", + "columnName": "internal_two_way_sync_result", + "affinity": "TEXT" + }, + { + "fieldPath": "uploaded", + "columnName": "uploaded", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "filesystem", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `local_path` TEXT, `is_folder` INTEGER, `found_at` INTEGER, `upload_triggered` INTEGER, `syncedfolder_id` TEXT, `crc32` TEXT, `modified_at` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "localPath", + "columnName": "local_path", + "affinity": "TEXT" + }, + { + "fieldPath": "fileIsFolder", + "columnName": "is_folder", + "affinity": "INTEGER" + }, + { + "fieldPath": "fileFoundRecently", + "columnName": "found_at", + "affinity": "INTEGER" + }, + { + "fieldPath": "fileSentForUpload", + "columnName": "upload_triggered", + "affinity": "INTEGER" + }, + { + "fieldPath": "syncedFolderId", + "columnName": "syncedfolder_id", + "affinity": "TEXT" + }, + { + "fieldPath": "crc32", + "columnName": "crc32", + "affinity": "TEXT" + }, + { + "fieldPath": "fileModified", + "columnName": "modified_at", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "ocshares", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `file_source` INTEGER, `item_source` INTEGER, `share_type` INTEGER, `shate_with` TEXT, `path` TEXT, `permissions` INTEGER, `shared_date` INTEGER, `expiration_date` INTEGER, `token` TEXT, `shared_with_display_name` TEXT, `is_directory` INTEGER, `user_id` TEXT, `id_remote_shared` INTEGER, `owner_share` TEXT, `is_password_protected` INTEGER, `note` TEXT, `hide_download` INTEGER, `share_link` TEXT, `share_label` TEXT, `download_limit_limit` INTEGER, `download_limit_count` INTEGER, `attributes` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "fileSource", + "columnName": "file_source", + "affinity": "INTEGER" + }, + { + "fieldPath": "itemSource", + "columnName": "item_source", + "affinity": "INTEGER" + }, + { + "fieldPath": "shareType", + "columnName": "share_type", + "affinity": "INTEGER" + }, + { + "fieldPath": "shareWith", + "columnName": "shate_with", + "affinity": "TEXT" + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT" + }, + { + "fieldPath": "permissions", + "columnName": "permissions", + "affinity": "INTEGER" + }, + { + "fieldPath": "sharedDate", + "columnName": "shared_date", + "affinity": "INTEGER" + }, + { + "fieldPath": "expirationDate", + "columnName": "expiration_date", + "affinity": "INTEGER" + }, + { + "fieldPath": "token", + "columnName": "token", + "affinity": "TEXT" + }, + { + "fieldPath": "shareWithDisplayName", + "columnName": "shared_with_display_name", + "affinity": "TEXT" + }, + { + "fieldPath": "isDirectory", + "columnName": "is_directory", + "affinity": "INTEGER" + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT" + }, + { + "fieldPath": "idRemoteShared", + "columnName": "id_remote_shared", + "affinity": "INTEGER" + }, + { + "fieldPath": "accountOwner", + "columnName": "owner_share", + "affinity": "TEXT" + }, + { + "fieldPath": "isPasswordProtected", + "columnName": "is_password_protected", + "affinity": "INTEGER" + }, + { + "fieldPath": "note", + "columnName": "note", + "affinity": "TEXT" + }, + { + "fieldPath": "hideDownload", + "columnName": "hide_download", + "affinity": "INTEGER" + }, + { + "fieldPath": "shareLink", + "columnName": "share_link", + "affinity": "TEXT" + }, + { + "fieldPath": "shareLabel", + "columnName": "share_label", + "affinity": "TEXT" + }, + { + "fieldPath": "downloadLimitLimit", + "columnName": "download_limit_limit", + "affinity": "INTEGER" + }, + { + "fieldPath": "downloadLimitCount", + "columnName": "download_limit_count", + "affinity": "INTEGER" + }, + { + "fieldPath": "attributes", + "columnName": "attributes", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "synced_folders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `local_path` TEXT, `remote_path` TEXT, `wifi_only` INTEGER, `charging_only` INTEGER, `existing` INTEGER, `enabled` INTEGER, `enabled_timestamp_ms` INTEGER, `subfolder_by_date` INTEGER, `account` TEXT, `upload_option` INTEGER, `name_collision_policy` INTEGER, `type` INTEGER, `hidden` INTEGER, `sub_folder_rule` INTEGER, `exclude_hidden` INTEGER, `last_scan_timestamp_ms` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "localPath", + "columnName": "local_path", + "affinity": "TEXT" + }, + { + "fieldPath": "remotePath", + "columnName": "remote_path", + "affinity": "TEXT" + }, + { + "fieldPath": "wifiOnly", + "columnName": "wifi_only", + "affinity": "INTEGER" + }, + { + "fieldPath": "chargingOnly", + "columnName": "charging_only", + "affinity": "INTEGER" + }, + { + "fieldPath": "existing", + "columnName": "existing", + "affinity": "INTEGER" + }, + { + "fieldPath": "enabled", + "columnName": "enabled", + "affinity": "INTEGER" + }, + { + "fieldPath": "enabledTimestampMs", + "columnName": "enabled_timestamp_ms", + "affinity": "INTEGER" + }, + { + "fieldPath": "subfolderByDate", + "columnName": "subfolder_by_date", + "affinity": "INTEGER" + }, + { + "fieldPath": "account", + "columnName": "account", + "affinity": "TEXT" + }, + { + "fieldPath": "uploadAction", + "columnName": "upload_option", + "affinity": "INTEGER" + }, + { + "fieldPath": "nameCollisionPolicy", + "columnName": "name_collision_policy", + "affinity": "INTEGER" + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER" + }, + { + "fieldPath": "hidden", + "columnName": "hidden", + "affinity": "INTEGER" + }, + { + "fieldPath": "subFolderRule", + "columnName": "sub_folder_rule", + "affinity": "INTEGER" + }, + { + "fieldPath": "excludeHidden", + "columnName": "exclude_hidden", + "affinity": "INTEGER" + }, + { + "fieldPath": "lastScanTimestampMs", + "columnName": "last_scan_timestamp_ms", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "list_of_uploads", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `local_path` TEXT, `remote_path` TEXT, `account_name` TEXT, `file_size` INTEGER, `status` INTEGER, `local_behaviour` INTEGER, `upload_time` INTEGER, `name_collision_policy` INTEGER, `is_create_remote_folder` INTEGER, `upload_end_timestamp` INTEGER, `last_result` INTEGER, `is_while_charging_only` INTEGER, `is_wifi_only` INTEGER, `created_by` INTEGER, `folder_unlock_token` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "localPath", + "columnName": "local_path", + "affinity": "TEXT" + }, + { + "fieldPath": "remotePath", + "columnName": "remote_path", + "affinity": "TEXT" + }, + { + "fieldPath": "accountName", + "columnName": "account_name", + "affinity": "TEXT" + }, + { + "fieldPath": "fileSize", + "columnName": "file_size", + "affinity": "INTEGER" + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER" + }, + { + "fieldPath": "localBehaviour", + "columnName": "local_behaviour", + "affinity": "INTEGER" + }, + { + "fieldPath": "uploadTime", + "columnName": "upload_time", + "affinity": "INTEGER" + }, + { + "fieldPath": "nameCollisionPolicy", + "columnName": "name_collision_policy", + "affinity": "INTEGER" + }, + { + "fieldPath": "isCreateRemoteFolder", + "columnName": "is_create_remote_folder", + "affinity": "INTEGER" + }, + { + "fieldPath": "uploadEndTimestamp", + "columnName": "upload_end_timestamp", + "affinity": "INTEGER" + }, + { + "fieldPath": "lastResult", + "columnName": "last_result", + "affinity": "INTEGER" + }, + { + "fieldPath": "isWhileChargingOnly", + "columnName": "is_while_charging_only", + "affinity": "INTEGER" + }, + { + "fieldPath": "isWifiOnly", + "columnName": "is_wifi_only", + "affinity": "INTEGER" + }, + { + "fieldPath": "createdBy", + "columnName": "created_by", + "affinity": "INTEGER" + }, + { + "fieldPath": "folderUnlockToken", + "columnName": "folder_unlock_token", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "virtual", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `type` TEXT, `ocfile_id` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT" + }, + { + "fieldPath": "ocFileId", + "columnName": "ocfile_id", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "offline_operations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `offline_operations_parent_oc_file_id` INTEGER, `offline_operations_path` TEXT, `offline_operations_type` TEXT, `offline_operations_file_name` TEXT, `offline_operations_created_at` INTEGER, `offline_operations_modified_at` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "parentOCFileId", + "columnName": "offline_operations_parent_oc_file_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "path", + "columnName": "offline_operations_path", + "affinity": "TEXT" + }, + { + "fieldPath": "type", + "columnName": "offline_operations_type", + "affinity": "TEXT" + }, + { + "fieldPath": "filename", + "columnName": "offline_operations_file_name", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "offline_operations_created_at", + "affinity": "INTEGER" + }, + { + "fieldPath": "modifiedAt", + "columnName": "offline_operations_modified_at", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "recommended_files", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `directory` TEXT NOT NULL, `extension` TEXT NOT NULL, `mime_type` TEXT NOT NULL, `has_preview` INTEGER NOT NULL, `reason` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `account_name` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directory", + "columnName": "directory", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "extension", + "columnName": "extension", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mimeType", + "columnName": "mime_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPreview", + "columnName": "has_preview", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reason", + "columnName": "reason", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountName", + "columnName": "account_name", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "_id" + ] + } + }, + { + "tableName": "assistant", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `accountName` TEXT, `type` TEXT, `status` TEXT, `userId` TEXT, `appId` TEXT, `input` TEXT, `output` TEXT, `completionExpectedAt` INTEGER, `progress` INTEGER, `lastUpdated` INTEGER, `scheduledAt` INTEGER, `endedAt` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountName", + "columnName": "accountName", + "affinity": "TEXT" + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT" + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT" + }, + { + "fieldPath": "userId", + "columnName": "userId", + "affinity": "TEXT" + }, + { + "fieldPath": "appId", + "columnName": "appId", + "affinity": "TEXT" + }, + { + "fieldPath": "input", + "columnName": "input", + "affinity": "TEXT" + }, + { + "fieldPath": "output", + "columnName": "output", + "affinity": "TEXT" + }, + { + "fieldPath": "completionExpectedAt", + "columnName": "completionExpectedAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "progress", + "columnName": "progress", + "affinity": "INTEGER" + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER" + }, + { + "fieldPath": "scheduledAt", + "columnName": "scheduledAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "endedAt", + "columnName": "endedAt", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1c5a77152bf79ee80f9e6eb2677d75a7')" + ] + } +} diff --git a/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt b/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt index 1e95ae009ac5..de3d4911a253 100644 --- a/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt +++ b/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt @@ -91,7 +91,7 @@ import com.owncloud.android.db.ProviderMeta AutoMigration(from = 92, to = 93, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class), AutoMigration(from = 93, to = 94, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class), AutoMigration(from = 94, to = 95, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class), - AutoMigration(from = 95, to = 96), + AutoMigration(from = 95, to = 96), AutoMigration(from = 96, to = 97, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class) ], exportSchema = true diff --git a/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt b/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt index becfdd4215e1..f99dd77e353d 100644 --- a/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt +++ b/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt @@ -25,6 +25,7 @@ import com.nextcloud.android.lib.resources.clientintegration.Orientation import com.nextcloud.android.lib.resources.clientintegration.Text import com.nextcloud.android.lib.resources.clientintegration.URL import com.nextcloud.utils.extensions.getActivity +import com.owncloud.android.lib.resources.status.OCCapability import com.owncloud.android.utils.DisplayUtils @Composable @@ -93,7 +94,7 @@ private fun close(activity: Activity?) { @Preview private fun ClientIntegrationScreenPreviewVertical() { val clientIntegrationUI = ClientIntegrationUI( - 0.1, + OCCapability.CLIENT_INTEGRATION_VERSION, Layout( Orientation.VERTICAL, mutableListOf( @@ -120,7 +121,7 @@ private fun ClientIntegrationScreenPreviewVertical() { @Preview private fun ClientIntegrationScreenPreviewHorizontal() { val clientIntegrationUI = ClientIntegrationUI( - 0.1, + OCCapability.CLIENT_INTEGRATION_VERSION, Layout( Orientation.HORIZONTAL, mutableListOf( @@ -144,7 +145,7 @@ private fun ClientIntegrationScreenPreviewHorizontal() { @Preview private fun ClientIntegrationScreenPreviewEmpty() { val clientIntegrationUI = ClientIntegrationUI( - 0.1, + OCCapability.CLIENT_INTEGRATION_VERSION, Layout( Orientation.HORIZONTAL, emptyList() diff --git a/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt index d352b9ce631c..0e56fb447985 100644 --- a/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt +++ b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt @@ -8,7 +8,6 @@ package com.nextcloud.ui.composeActivity import android.annotation.SuppressLint -import android.content.Intent import android.os.Bundle import android.view.MenuItem import android.view.View @@ -30,7 +29,6 @@ import com.nextcloud.client.assistant.repository.remote.AssistantRemoteRepositor import com.nextcloud.client.database.NextcloudDatabase import com.nextcloud.common.NextcloudClient import com.nextcloud.ui.ClientIntegrationScreen -import com.nextcloud.utils.extensions.getSerializableArgument import com.owncloud.android.R import com.owncloud.android.databinding.ActivityComposeBinding import com.owncloud.android.ui.activity.DrawerActivity @@ -52,41 +50,23 @@ class ComposeActivity : DrawerActivity() { setContentView(binding.root) val destinationId = intent.getIntExtra(DESTINATION, -1) - val titleId = intent.getIntExtra(TITLE, R.string.empty) + var title = intent.getStringExtra(TITLE_STRING) if (title == null || title.isEmpty()) { title = getString(intent.getIntExtra(TITLE, R.string.empty)) } - - if (destination == ComposeDestination.AssistantScreen) { - setupDrawer() - - setupToolbarShowOnlyMenuButtonAndTitle(title) { - openDrawer() - } - } else { - setSupportActionBar(null) - if (findViewById(R.id.appbar) != null) { - findViewById(R.id.appbar)?.visibility = View.GONE - } - } - - // if (false) { - // val actionBar = getDelegate().supportActionBar - // actionBar?.setDisplayHomeAsUpEnabled(true) - // actionBar?.setDisplayShowTitleEnabled(true) // - // val menuIcon = ResourcesCompat.getDrawable( - // getResources(), - // R.drawable.ic_arrow_back, - // null - // ) - // viewThemeUtils.androidx.themeActionBar( - // this, - // actionBar!!, - // title!!, - // menuIcon!! - // ) + // if (destination == ComposeDestination.AssistantScreen) { + // setupDrawer() + // + // setupToolbarShowOnlyMenuButtonAndTitle(title) { + // openDrawer() + // } + // } else { + // setSupportActionBar(null) + // if (findViewById(R.id.appbar) != null) { + // findViewById(R.id.appbar)?.visibility = View.GONE + // } // } binding.composeView.setContent { @@ -104,7 +84,6 @@ class ComposeActivity : DrawerActivity() { super.onBackPressed() true } - else -> super.onOptionsItemSelected(item) } @@ -143,15 +122,18 @@ class ComposeActivity : DrawerActivity() { capability = capabilities ) } - } else if (destination == ComposeDestination.ClientIntegrationScreen) { - binding.bottomNavigation.visibility = View.GONE - val clientIntegrationUI: ClientIntegrationUI? = intent.getParcelableExtra(ARGS_CLIENT_INTEGRATION_UI) + is ComposeDestination.ClientIntegrationScreen -> { + binding.bottomNavigation.visibility = View.GONE + + val clientIntegrationUI: ClientIntegrationUI? = intent.getParcelableExtra(ARGS_CLIENT_INTEGRATION_UI) + + clientIntegrationUI?.let { + ClientIntegrationScreen(clientIntegrationUI, nextcloudClient?.baseUri.toString()) + } + } - clientIntegrationUI?.let { ClientIntegrationScreen(it, nextcloudClient?.baseUri.toString()) } - - } else { - Unit + else -> Unit } } } diff --git a/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeDestination.kt b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeDestination.kt index dd1cdba6af8e..1e092fb8a89c 100644 --- a/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeDestination.kt +++ b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeDestination.kt @@ -9,11 +9,12 @@ package com.nextcloud.ui.composeActivity sealed class ComposeDestination(val id: Int) { data class AssistantScreen(val sessionId: Long?) : ComposeDestination(0) + data class ClientIntegrationScreen(val sessionId: Long?) : ComposeDestination(1) companion object { fun fromId(id: Int): ComposeDestination = when (id) { 0 -> AssistantScreen(null) - 1 -> ClientIntegrationScreen() + 1 -> ClientIntegrationScreen(null) else -> throw IllegalArgumentException("Unknown destination: $id") } } diff --git a/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt b/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt index 10727248fb0c..87c89fe3489d 100644 --- a/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt +++ b/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt @@ -41,6 +41,7 @@ import com.google.gson.Gson import com.google.gson.GsonBuilder import com.google.gson.JsonElement import com.google.gson.JsonParser +import com.google.gson.JsonSyntaxException import com.google.gson.reflect.TypeToken import com.nextcloud.android.common.ui.theme.utils.ColorRole import com.nextcloud.android.lib.resources.clientintegration.ClientIntegrationUI @@ -81,6 +82,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.RequestBody +import org.apache.commons.httpclient.HttpStatus +import java.io.IOException import javax.inject.Inject class FileActionsBottomSheet : @@ -385,7 +388,7 @@ class FileActionsBottomSheet : val canvas = Canvas(returnedBitmap) canvas.drawPicture((drawable as PictureDrawable).picture) - val d = returnedBitmap.toDrawable(requireContext().resources); + val d = returnedBitmap.toDrawable(requireContext().resources) val tintedDrawable = viewThemeUtils.platform.tintDrawable( requireContext(), @@ -423,7 +426,7 @@ class FileActionsBottomSheet : // Always replace known placeholder in url url = url.replace("{filePath}", filePath, false) url = url.replace("{fileId}", fileId, false) - + val method = when (endpoint.method) { Method.POST -> { val requestBody = if (endpoint.params?.isNotEmpty() == true) { @@ -448,7 +451,7 @@ class FileActionsBottomSheet : val result = try { client.execute(method) - } catch (exception: Exception) { + } catch (exception: IOException) { activity?.showToast(getString(R.string.failed_to_start_action)) } val response = method.getResponseBodyAsString() @@ -463,8 +466,8 @@ class FileActionsBottomSheet : activity?.showToast(tooltipResponse.tooltip) } - } catch (e: Exception) { - if (result == 200) { + } catch (e: JsonSyntaxException) { + if (result == HttpStatus.SC_OK) { activity?.showToast(getString(R.string.action_triggered)) } else { activity?.showToast(getString(R.string.failed_to_start_action)) @@ -477,7 +480,7 @@ class FileActionsBottomSheet : private fun startClientIntegration(endpoint: Endpoint, clientIntegrationUI: ClientIntegrationUI) { CoroutineScope(Dispatchers.IO).launch { val composeActivity = Intent(context, ComposeActivity::class.java) - composeActivity.putExtra(ComposeActivity.DESTINATION, ComposeDestination.ClientIntegrationScreen) + composeActivity.putExtra(ComposeActivity.DESTINATION, ComposeDestination.ClientIntegrationScreen(null).id) composeActivity.putExtra(ComposeActivity.ARGS_CLIENT_INTEGRATION_UI, clientIntegrationUI) composeActivity.putExtra(ComposeActivity.TITLE, endpoint.name) diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index c9b892b9944d..bcf788601a28 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -18939,6 +18939,17 @@ + + + + + + + + @@ -19530,6 +19541,17 @@ + + + + + + + + From 6a41db365fd6b37d53353a7b704c8b204a171b27 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 17 Dec 2025 08:27:40 +0100 Subject: [PATCH 03/13] wip Signed-off-by: tobiasKaminsky --- .../ui/composeActivity/ComposeActivity.kt | 30 +-- .../ui/fileactions/ClientIntegration.kt | 225 ++++++++++++++++++ .../ui/fileactions/FileActionsBottomSheet.kt | 212 +---------------- 3 files changed, 251 insertions(+), 216 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt diff --git a/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt index 0e56fb447985..0d920fc6cb0e 100644 --- a/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt +++ b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt @@ -50,24 +50,20 @@ class ComposeActivity : DrawerActivity() { setContentView(binding.root) val destinationId = intent.getIntExtra(DESTINATION, -1) - var title = intent.getStringExtra(TITLE_STRING) + val titleId = intent.getIntExtra(TITLE, R.string.empty) - if (title == null || title.isEmpty()) { - title = getString(intent.getIntExtra(TITLE, R.string.empty)) + if (destinationId == 0) { + setupDrawer() + + setupToolbarShowOnlyMenuButtonAndTitle(getString(titleId)) { + openDrawer() + } + } else { + setSupportActionBar(null) + if (findViewById(R.id.appbar) != null) { + findViewById(R.id.appbar)?.visibility = View.GONE + } } - // - // if (destination == ComposeDestination.AssistantScreen) { - // setupDrawer() - // - // setupToolbarShowOnlyMenuButtonAndTitle(title) { - // openDrawer() - // } - // } else { - // setSupportActionBar(null) - // if (findViewById(R.id.appbar) != null) { - // findViewById(R.id.appbar)?.visibility = View.GONE - // } - // } binding.composeView.setContent { MaterialTheme( @@ -81,7 +77,7 @@ class ComposeActivity : DrawerActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { android.R.id.home -> { - super.onBackPressed() + toggleDrawer() true } else -> super.onOptionsItemSelected(item) diff --git a/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt b/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt new file mode 100644 index 000000000000..f448c05bb92a --- /dev/null +++ b/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt @@ -0,0 +1,225 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Your Name + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.ui.fileactions + +import android.content.Context +import android.content.Intent +import android.graphics.Canvas +import android.graphics.drawable.PictureDrawable +import android.view.LayoutInflater +import android.view.View +import androidx.appcompat.content.res.AppCompatResources +import androidx.core.graphics.createBitmap +import androidx.core.graphics.drawable.toDrawable +import androidx.core.net.toUri +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.JsonElement +import com.google.gson.JsonParser +import com.google.gson.JsonSyntaxException +import com.google.gson.reflect.TypeToken +import com.nextcloud.android.lib.resources.clientintegration.ClientIntegrationUI +import com.nextcloud.android.lib.resources.clientintegration.Element +import com.nextcloud.android.lib.resources.clientintegration.ElementTypeAdapter +import com.nextcloud.android.lib.resources.clientintegration.Endpoint +import com.nextcloud.android.lib.resources.clientintegration.TooltipResponse +import com.nextcloud.client.account.User +import com.nextcloud.common.JSONRequestBody +import com.nextcloud.operations.GetMethod +import com.nextcloud.operations.PostMethod +import com.nextcloud.ui.composeActivity.ComposeActivity +import com.nextcloud.ui.composeActivity.ComposeDestination +import com.nextcloud.utils.GlideHelper +import com.nextcloud.utils.extensions.showToast +import com.owncloud.android.R +import com.owncloud.android.databinding.FileActionsBottomSheetBinding +import com.owncloud.android.databinding.FileActionsBottomSheetItemBinding +import com.owncloud.android.lib.common.OwnCloudClientManagerFactory +import com.owncloud.android.lib.ocs.ServerResponse +import com.owncloud.android.lib.resources.status.Method +import com.owncloud.android.utils.DisplayUtils +import com.owncloud.android.utils.theme.ViewThemeUtils +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import okhttp3.RequestBody +import org.apache.commons.httpclient.HttpStatus +import java.io.IOException + +class ClientIntegration( + private var sheet: FileActionsBottomSheet, + private var user: User, + private var context: Context +) { + + // @RequiresApi(Build.VERSION_CODES.Q) + fun inflateClientIntegrationActionView( + endpoint: Endpoint, + layoutInflater: LayoutInflater, + binding: FileActionsBottomSheetBinding, + viewModel: FileActionsViewModel, + viewThemeUtils: ViewThemeUtils + ): View { + val itemBinding = FileActionsBottomSheetItemBinding.inflate(layoutInflater, binding.fileActionsList, false) + .apply { + root.setOnClickListener { + if (viewModel.uiState.value is FileActionsViewModel.UiState.LoadedForSingleFile) { + val singleFile = (viewModel.uiState.value as FileActionsViewModel.UiState.LoadedForSingleFile) + + val fileId = singleFile.titleFile?.localId.toString() + val filePath = singleFile.titleFile?.remotePath.toString() + + requestClientIntegration(endpoint, fileId, filePath) + } else { + requestClientIntegration(endpoint, "", "") + } + } + text.text = endpoint.name + + if (endpoint.icon != null) { + CoroutineScope(Dispatchers.IO).launch { + val client = OwnCloudClientManagerFactory.getDefaultSingleton() + .getNextcloudClientFor(user.toOwnCloudAccount(), context) + + val drawable = + GlideHelper.getDrawable(context, client, client.baseUri.toString() + endpoint.icon) + ?.mutate() + + val px = DisplayUtils.convertDpToPixel( + context.resources.getDimension(R.dimen.iconized_single_line_item_icon_size), + context + ) + val returnedBitmap = + createBitmap(drawable?.intrinsicWidth ?: px, drawable?.intrinsicHeight ?: px) + + val canvas = Canvas(returnedBitmap) + canvas.drawPicture((drawable as PictureDrawable).picture) + + val d = returnedBitmap.toDrawable(context.resources) + + val tintedDrawable = viewThemeUtils.platform.tintDrawable( + context, + d + ) + + withContext(Dispatchers.Main) { + icon.setImageDrawable(tintedDrawable) + } + } + } else { + val tintedDrawable = viewThemeUtils.platform.tintDrawable( + context, + AppCompatResources.getDrawable(context, R.drawable.ic_activity)!! + ) + + icon.setImageDrawable(tintedDrawable) + } + } + return itemBinding.root + } + + private fun requestClientIntegration(endpoint: Endpoint, fileId: String, filePath: String) { + CoroutineScope(Dispatchers.IO).launch { + val client = OwnCloudClientManagerFactory.getDefaultSingleton() + .getNextcloudClientFor(user.toOwnCloudAccount(), context) + + // construct url + var url = (client.baseUri.toString() + endpoint.url).toUri() + .buildUpon() + .appendQueryParameter("format", "json") + .build() + .toString() + + // Always replace known placeholder in url + url = url.replace("{filePath}", filePath, false) + url = url.replace("{fileId}", fileId, false) + + val method = when (endpoint.method) { + Method.POST -> { + val requestBody = if (endpoint.params?.isNotEmpty() == true) { + val jsonRequestBody = JSONRequestBody() + endpoint.params!!.forEach { + when (it.value) { + "{filePath}" -> jsonRequestBody.put(it.key, filePath) + "{fileId}" -> jsonRequestBody.put(it.key, fileId) + } + } + + jsonRequestBody.get() + } else { + RequestBody.EMPTY + } + + PostMethod(url, true, requestBody) + } + + else -> GetMethod(url, true) + } + + val result = try { + client.execute(method) + } catch (_: IOException) { + context.showToast(context.resources.getString(R.string.failed_to_start_action)) + } + val response = method.getResponseBodyAsString() + + var output: ClientIntegrationUI? + try { + output = parseClientIntegrationResult(response) + if (output.root != null) { + startClientIntegration(endpoint, output) + } else { + val tooltipResponse = parseTooltipResult(response) + + context.showToast(tooltipResponse.tooltip) + } + } catch (e: JsonSyntaxException) { + if (result == HttpStatus.SC_OK) { + context.showToast(context.resources.getString(R.string.action_triggered)) + } else { + context.showToast(context.resources.getString(R.string.failed_to_start_action)) + } + } + sheet.dismiss() + } + } + + private fun startClientIntegration(endpoint: Endpoint, clientIntegrationUI: ClientIntegrationUI) { + CoroutineScope(Dispatchers.IO).launch { + val composeActivity = Intent(context, ComposeActivity::class.java) + composeActivity.putExtra(ComposeActivity.DESTINATION, ComposeDestination.ClientIntegrationScreen(null).id) + composeActivity.putExtra(ComposeActivity.ARGS_CLIENT_INTEGRATION_UI, clientIntegrationUI) + + composeActivity.putExtra(ComposeActivity.TITLE, endpoint.name) + context.startActivity(composeActivity) + sheet.dismiss() + } + } + + private fun parseClientIntegrationResult(response: String?): ClientIntegrationUI { + val gson = + GsonBuilder() + .registerTypeHierarchyAdapter(Element::class.java, ElementTypeAdapter()) + .create() + + val element: JsonElement = JsonParser.parseString(response) + return gson + .fromJson(element, object : TypeToken>() {}) + .ocs + .data + } + + private fun parseTooltipResult(response: String?): TooltipResponse { + val element: JsonElement = JsonParser.parseString(response) + return Gson() + .fromJson(element, object : TypeToken>() {}) + .ocs + .data + } +} diff --git a/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt b/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt index 87c89fe3489d..62467f7d9b0f 100644 --- a/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt +++ b/app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt @@ -8,24 +8,16 @@ package com.nextcloud.ui.fileactions import android.content.Context -import android.content.Intent import android.content.res.ColorStateList -import android.graphics.Canvas import android.graphics.Typeface import android.graphics.drawable.Drawable -import android.graphics.drawable.PictureDrawable -import android.os.Build import android.os.Bundle import android.text.style.StyleSpan import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.annotation.IdRes -import androidx.annotation.RequiresApi import androidx.appcompat.content.res.AppCompatResources -import androidx.core.graphics.createBitmap -import androidx.core.graphics.drawable.toDrawable -import androidx.core.net.toUri import androidx.core.os.bundleOf import androidx.core.view.isEmpty import androidx.core.view.isVisible @@ -33,34 +25,15 @@ import androidx.fragment.app.FragmentManager import androidx.fragment.app.setFragmentResult import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.coroutineScope import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import com.google.gson.JsonElement -import com.google.gson.JsonParser -import com.google.gson.JsonSyntaxException -import com.google.gson.reflect.TypeToken import com.nextcloud.android.common.ui.theme.utils.ColorRole -import com.nextcloud.android.lib.resources.clientintegration.ClientIntegrationUI -import com.nextcloud.android.lib.resources.clientintegration.Element -import com.nextcloud.android.lib.resources.clientintegration.ElementTypeAdapter import com.nextcloud.android.lib.resources.clientintegration.Endpoint -import com.nextcloud.android.lib.resources.clientintegration.TooltipResponse import com.nextcloud.client.account.CurrentAccountProvider import com.nextcloud.client.di.Injectable import com.nextcloud.client.di.ViewModelFactory -import com.nextcloud.common.JSONRequestBody -import com.nextcloud.operations.GetMethod -import com.nextcloud.operations.PostMethod -import com.nextcloud.ui.composeActivity.ComposeActivity -import com.nextcloud.ui.composeActivity.ComposeDestination -import com.nextcloud.ui.fileactions.FileActionsViewModel.Companion.ARG_ENDPOINTS -import com.nextcloud.utils.GlideHelper import com.nextcloud.utils.extensions.setVisibleIf -import com.nextcloud.utils.extensions.showToast import com.owncloud.android.R import com.owncloud.android.databinding.FileActionsBottomSheetBinding import com.owncloud.android.databinding.FileActionsBottomSheetItemBinding @@ -68,22 +41,12 @@ import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile import com.owncloud.android.datamodel.SyncedFolderProvider import com.owncloud.android.datamodel.ThumbnailsCacheManager -import com.owncloud.android.lib.common.OwnCloudClientManagerFactory -import com.owncloud.android.lib.ocs.ServerResponse import com.owncloud.android.lib.resources.files.model.FileLockType -import com.owncloud.android.lib.resources.status.Method import com.owncloud.android.ui.activity.ComponentsGetter import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener import com.owncloud.android.utils.FileStorageUtils import com.owncloud.android.utils.theme.ViewThemeUtils -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import okhttp3.RequestBody -import org.apache.commons.httpclient.HttpStatus -import java.io.IOException import javax.inject.Inject class FileActionsBottomSheet : @@ -117,6 +80,8 @@ class FileActionsBottomSheet : private var endpoints: List? = mutableListOf() + private lateinit var clientIntegration: ClientIntegration + fun interface ResultListener { fun onResult(@IdRes actionId: Int) } @@ -133,7 +98,7 @@ class FileActionsBottomSheet : viewModel.load(requireArguments(), componentsGetter) - endpoints = arguments?.getParcelableArrayList(ARG_ENDPOINTS) + endpoints = arguments?.getParcelableArrayList(FileActionsViewModel.ARG_ENDPOINTS) val bottomSheetDialog = dialog as BottomSheetDialog bottomSheetDialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED @@ -141,6 +106,8 @@ class FileActionsBottomSheet : viewThemeUtils.platform.colorViewBackground(binding.bottomSheet, ColorRole.SURFACE) + clientIntegration = ClientIntegration(this, currentUserProvider.user, requireContext()) + return binding.root } @@ -241,10 +208,17 @@ class FileActionsBottomSheet : val view = inflateActionView(action) binding.fileActionsList.addView(view) } + // add client integration if (endpoints != null) { for (val e in endpoints) { - val ui = inflateClientIntegrationActionView(e) + val ui = clientIntegration.inflateClientIntegrationActionView( + e, + layoutInflater, + binding, + viewModel, + viewThemeUtils + ) binding.fileActionsList.addView(ui) } } @@ -351,165 +325,6 @@ class FileActionsBottomSheet : return itemBinding.root } - @RequiresApi(Build.VERSION_CODES.Q) - private fun inflateClientIntegrationActionView(endpoint: Endpoint): View { - val itemBinding = FileActionsBottomSheetItemBinding.inflate(layoutInflater, binding.fileActionsList, false) - .apply { - root.setOnClickListener { - if (viewModel.uiState.value is FileActionsViewModel.UiState.LoadedForSingleFile) { - val singleFile = (viewModel.uiState.value as FileActionsViewModel.UiState.LoadedForSingleFile) - - val fileId = singleFile.titleFile?.localId.toString() - val filePath = singleFile.titleFile?.remotePath.toString() - - requestClientIntegration(endpoint, fileId, filePath) - } else { - requestClientIntegration(endpoint, "", "") - } - } - text.text = endpoint.name - - if (endpoint.icon != null) { - CoroutineScope(Dispatchers.IO).launch { - val client = OwnCloudClientManagerFactory.getDefaultSingleton() - .getNextcloudClientFor(currentUserProvider.user.toOwnCloudAccount(), context) - - val drawable = - GlideHelper.getDrawable(requireContext(), client, client.baseUri.toString() + endpoint.icon) - ?.mutate() - - val px = DisplayUtils.convertDpToPixel( - requireContext().resources.getDimension(R.dimen.iconized_single_line_item_icon_size), - requireContext() - ) - val returnedBitmap = - createBitmap(drawable?.intrinsicWidth ?: px, drawable?.intrinsicHeight ?: px) - - val canvas = Canvas(returnedBitmap) - canvas.drawPicture((drawable as PictureDrawable).picture) - - val d = returnedBitmap.toDrawable(requireContext().resources) - - val tintedDrawable = viewThemeUtils.platform.tintDrawable( - requireContext(), - d - ) - - withContext(Dispatchers.Main) { - icon.setImageDrawable(tintedDrawable) - } - } - } else { - val tintedDrawable = viewThemeUtils.platform.tintDrawable( - requireContext(), - AppCompatResources.getDrawable(requireContext(), R.drawable.ic_activity)!! - ) - - icon.setImageDrawable(tintedDrawable) - } - } - return itemBinding.root - } - - private fun requestClientIntegration(endpoint: Endpoint, fileId: String, filePath: String) { - lifecycle.coroutineScope.launch(Dispatchers.IO) { - val client = OwnCloudClientManagerFactory.getDefaultSingleton() - .getNextcloudClientFor(currentUserProvider.user.toOwnCloudAccount(), context) - - // construct url - var url = (client.baseUri.toString() + endpoint.url).toUri() - .buildUpon() - .appendQueryParameter("format", "json") - .build() - .toString() - - // Always replace known placeholder in url - url = url.replace("{filePath}", filePath, false) - url = url.replace("{fileId}", fileId, false) - - val method = when (endpoint.method) { - Method.POST -> { - val requestBody = if (endpoint.params?.isNotEmpty() == true) { - val jsonRequestBody = JSONRequestBody() - endpoint.params!!.forEach { - when (it.value) { - "{filePath}" -> jsonRequestBody.put(it.key, filePath) - "{fileId}" -> jsonRequestBody.put(it.key, fileId) - } - } - - jsonRequestBody.get() - } else { - RequestBody.EMPTY - } - - PostMethod(url, true, requestBody) - } - - else -> GetMethod(url, true) - } - - val result = try { - client.execute(method) - } catch (exception: IOException) { - activity?.showToast(getString(R.string.failed_to_start_action)) - } - val response = method.getResponseBodyAsString() - - var output: ClientIntegrationUI? - try { - output = parseClientIntegrationResult(response) - if (output.root != null) { - startClientIntegration(endpoint, output) - } else { - val tooltipResponse = parseTooltipResult(response) - - activity?.showToast(tooltipResponse.tooltip) - } - } catch (e: JsonSyntaxException) { - if (result == HttpStatus.SC_OK) { - activity?.showToast(getString(R.string.action_triggered)) - } else { - activity?.showToast(getString(R.string.failed_to_start_action)) - } - } - dismiss() - } - } - - private fun startClientIntegration(endpoint: Endpoint, clientIntegrationUI: ClientIntegrationUI) { - CoroutineScope(Dispatchers.IO).launch { - val composeActivity = Intent(context, ComposeActivity::class.java) - composeActivity.putExtra(ComposeActivity.DESTINATION, ComposeDestination.ClientIntegrationScreen(null).id) - composeActivity.putExtra(ComposeActivity.ARGS_CLIENT_INTEGRATION_UI, clientIntegrationUI) - - composeActivity.putExtra(ComposeActivity.TITLE, endpoint.name) - startActivity(composeActivity) - dismiss() - } - } - - private fun parseClientIntegrationResult(response: String?): ClientIntegrationUI { - val gson = - GsonBuilder() - .registerTypeHierarchyAdapter(Element::class.java, ElementTypeAdapter()) - .create() - - val element: JsonElement = JsonParser.parseString(response) - return gson - .fromJson(element, object : TypeToken>() {}) - .ocs - .data - } - - private fun parseTooltipResult(response: String?): TooltipResponse { - val element: JsonElement = JsonParser.parseString(response) - return Gson() - .fromJson(element, object : TypeToken>() {}) - .ocs - .data - } - private fun dispatchActionClick(id: Int?) { if (id != null) { setFragmentResult(REQUEST_KEY, bundleOf(RESULT_KEY_ACTION_ID to id)) @@ -522,7 +337,6 @@ class FileActionsBottomSheet : private const val REQUEST_KEY = "REQUEST_KEY_ACTION" private const val RESULT_KEY_ACTION_ID = "RESULT_KEY_ACTION_ID" private const val FILENAME_MAX_WIDTH_PERCENTAGE = 0.6 - private const val JSON_FORMAT = "?format=json" @JvmStatic @JvmOverloads From 721381f6d4891449f5e1a7494a5dbd40de5f109c Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 17 Dec 2025 12:07:40 +0100 Subject: [PATCH 04/13] fix git conflict Signed-off-by: alperozturk --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 827abeebdd08..a28d62d57f2c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ androidCommonLibraryVersion = "0.30.0" androidGifDrawableVersion = "1.2.29" androidImageCropperVersion = "4.7.0" -androidLibraryVersion = "827db94ca661d39ca7fae5c608eab1282b629b84" +androidLibraryVersion = "12510b09e3e2de60b3c8a235fc5f13cc582f5ce4" androidPluginVersion = '8.13.2' androidsvgVersion = "1.4" androidxMediaVersion = "1.5.1" From 107e2ab96321a76f1ff020b5fa5bd3f2c7c885ad Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 17 Dec 2025 12:42:04 +0100 Subject: [PATCH 05/13] code cleanup Signed-off-by: alperozturk --- .../ui/composeActivity/ComposeActivity.kt | 50 +++++++++---------- .../ui/composeActivity/ComposeDestination.kt | 26 +++++++--- .../ui/fileactions/ClientIntegration.kt | 43 ++++++---------- .../android/ui/activity/DrawerActivity.java | 14 +++--- 4 files changed, 65 insertions(+), 68 deletions(-) diff --git a/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt index 0d920fc6cb0e..1428ab363b76 100644 --- a/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt +++ b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt @@ -7,7 +7,6 @@ */ package com.nextcloud.ui.composeActivity -import android.annotation.SuppressLint import android.os.Bundle import android.view.MenuItem import android.view.View @@ -19,7 +18,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import com.nextcloud.android.lib.resources.clientintegration.ClientIntegrationUI import com.nextcloud.client.assistant.AssistantScreen import com.nextcloud.client.assistant.AssistantViewModel import com.nextcloud.client.assistant.conversation.ConversationViewModel @@ -29,6 +27,7 @@ import com.nextcloud.client.assistant.repository.remote.AssistantRemoteRepositor import com.nextcloud.client.database.NextcloudDatabase import com.nextcloud.common.NextcloudClient import com.nextcloud.ui.ClientIntegrationScreen +import com.nextcloud.utils.extensions.getParcelableArgument import com.owncloud.android.R import com.owncloud.android.databinding.ActivityComposeBinding import com.owncloud.android.ui.activity.DrawerActivity @@ -39,9 +38,6 @@ class ComposeActivity : DrawerActivity() { companion object { const val DESTINATION = "DESTINATION" - const val TITLE = "TITLE" - const val TITLE_STRING = "TITLE_STRING" - const val ARGS_CLIENT_INTEGRATION_UI = "ARGS_ClIENT_INTEGRATION_UI" } override fun onCreate(savedInstanceState: Bundle?) { @@ -49,32 +45,37 @@ class ComposeActivity : DrawerActivity() { binding = ActivityComposeBinding.inflate(layoutInflater) setContentView(binding.root) - val destinationId = intent.getIntExtra(DESTINATION, -1) - val titleId = intent.getIntExtra(TITLE, R.string.empty) + val destination = + intent.getParcelableArgument(DESTINATION, ComposeDestination::class.java) ?: throw IllegalArgumentException( + "destination is not exists" + ) - if (destinationId == 0) { - setupDrawer() - - setupToolbarShowOnlyMenuButtonAndTitle(getString(titleId)) { - openDrawer() - } - } else { - setSupportActionBar(null) - if (findViewById(R.id.appbar) != null) { - findViewById(R.id.appbar)?.visibility = View.GONE - } - } + setupActivityUIFor(destination) binding.composeView.setContent { MaterialTheme( colorScheme = viewThemeUtils.getColorScheme(this), content = { - Content(ComposeDestination.fromId(destinationId)) + Content(destination) } ) } } + private fun setupActivityUIFor(destination: ComposeDestination) { + if (destination is ComposeDestination.AssistantScreen) { + setupDrawer() + setupToolbarShowOnlyMenuButtonAndTitle(destination.title) { + openDrawer() + } + } else { + setSupportActionBar(null) + findViewById(R.id.appbar)?.let { + it.visibility = View.GONE + } + } + } + override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { android.R.id.home -> { toggleDrawer() @@ -83,7 +84,6 @@ class ComposeActivity : DrawerActivity() { else -> super.onOptionsItemSelected(item) } - @SuppressLint("CoroutineCreationDuringComposition") @Composable private fun Content(destination: ComposeDestination) { val currentScreen by ComposeNavigation.currentScreen.collectAsState() @@ -121,12 +121,8 @@ class ComposeActivity : DrawerActivity() { is ComposeDestination.ClientIntegrationScreen -> { binding.bottomNavigation.visibility = View.GONE - - val clientIntegrationUI: ClientIntegrationUI? = intent.getParcelableExtra(ARGS_CLIENT_INTEGRATION_UI) - - clientIntegrationUI?.let { - ClientIntegrationScreen(clientIntegrationUI, nextcloudClient?.baseUri.toString()) - } + val integrationScreen = (currentScreen as ComposeDestination.ClientIntegrationScreen) + ClientIntegrationScreen(integrationScreen.data, nextcloudClient?.baseUri.toString()) } else -> Unit diff --git a/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeDestination.kt b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeDestination.kt index 1e092fb8a89c..6564d61cd515 100644 --- a/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeDestination.kt +++ b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeDestination.kt @@ -7,15 +7,25 @@ */ package com.nextcloud.ui.composeActivity -sealed class ComposeDestination(val id: Int) { - data class AssistantScreen(val sessionId: Long?) : ComposeDestination(0) - data class ClientIntegrationScreen(val sessionId: Long?) : ComposeDestination(1) +import android.content.Context +import android.os.Parcelable +import com.nextcloud.android.lib.resources.clientintegration.ClientIntegrationUI +import com.owncloud.android.R +import kotlinx.parcelize.Parcelize + +@Parcelize +sealed class ComposeDestination(val id: Int) : Parcelable { + @Parcelize + data class AssistantScreen(val title: String, val sessionId: Long?) : ComposeDestination(0) + + @Parcelize + data class ClientIntegrationScreen(val title: String, val data: ClientIntegrationUI) : ComposeDestination(1) companion object { - fun fromId(id: Int): ComposeDestination = when (id) { - 0 -> AssistantScreen(null) - 1 -> ClientIntegrationScreen(null) - else -> throw IllegalArgumentException("Unknown destination: $id") - } + /** + * Creates a assistant screen without selected chat + */ + fun getAssistantScreen(context: Context): AssistantScreen = + AssistantScreen(context.getString(R.string.assistant_screen_top_bar_title), null) } } diff --git a/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt b/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt index f448c05bb92a..7e9fec704e0c 100644 --- a/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt +++ b/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt @@ -11,13 +11,14 @@ import android.content.Context import android.content.Intent import android.graphics.Canvas import android.graphics.drawable.PictureDrawable +import android.os.Bundle import android.view.LayoutInflater import android.view.View import androidx.appcompat.content.res.AppCompatResources import androidx.core.graphics.createBitmap import androidx.core.graphics.drawable.toDrawable import androidx.core.net.toUri -import com.google.gson.Gson +import androidx.lifecycle.lifecycleScope import com.google.gson.GsonBuilder import com.google.gson.JsonElement import com.google.gson.JsonParser @@ -27,7 +28,6 @@ import com.nextcloud.android.lib.resources.clientintegration.ClientIntegrationUI import com.nextcloud.android.lib.resources.clientintegration.Element import com.nextcloud.android.lib.resources.clientintegration.ElementTypeAdapter import com.nextcloud.android.lib.resources.clientintegration.Endpoint -import com.nextcloud.android.lib.resources.clientintegration.TooltipResponse import com.nextcloud.client.account.User import com.nextcloud.common.JSONRequestBody import com.nextcloud.operations.GetMethod @@ -44,7 +44,6 @@ import com.owncloud.android.lib.ocs.ServerResponse import com.owncloud.android.lib.resources.status.Method import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.theme.ViewThemeUtils -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -58,7 +57,6 @@ class ClientIntegration( private var context: Context ) { - // @RequiresApi(Build.VERSION_CODES.Q) fun inflateClientIntegrationActionView( endpoint: Endpoint, layoutInflater: LayoutInflater, @@ -83,7 +81,7 @@ class ClientIntegration( text.text = endpoint.name if (endpoint.icon != null) { - CoroutineScope(Dispatchers.IO).launch { + sheet.lifecycleScope.launch { val client = OwnCloudClientManagerFactory.getDefaultSingleton() .getNextcloudClientFor(user.toOwnCloudAccount(), context) @@ -125,7 +123,7 @@ class ClientIntegration( } private fun requestClientIntegration(endpoint: Endpoint, fileId: String, filePath: String) { - CoroutineScope(Dispatchers.IO).launch { + sheet.lifecycleScope.launch { val client = OwnCloudClientManagerFactory.getDefaultSingleton() .getNextcloudClientFor(user.toOwnCloudAccount(), context) @@ -172,14 +170,8 @@ class ClientIntegration( var output: ClientIntegrationUI? try { output = parseClientIntegrationResult(response) - if (output.root != null) { - startClientIntegration(endpoint, output) - } else { - val tooltipResponse = parseTooltipResult(response) - - context.showToast(tooltipResponse.tooltip) - } - } catch (e: JsonSyntaxException) { + startClientIntegration(endpoint, output) + } catch (_: JsonSyntaxException) { if (result == HttpStatus.SC_OK) { context.showToast(context.resources.getString(R.string.action_triggered)) } else { @@ -191,12 +183,17 @@ class ClientIntegration( } private fun startClientIntegration(endpoint: Endpoint, clientIntegrationUI: ClientIntegrationUI) { - CoroutineScope(Dispatchers.IO).launch { - val composeActivity = Intent(context, ComposeActivity::class.java) - composeActivity.putExtra(ComposeActivity.DESTINATION, ComposeDestination.ClientIntegrationScreen(null).id) - composeActivity.putExtra(ComposeActivity.ARGS_CLIENT_INTEGRATION_UI, clientIntegrationUI) + sheet.lifecycleScope.launch { + val integrationScreen = ComposeDestination.ClientIntegrationScreen(endpoint.name, clientIntegrationUI) + + val bundle = Bundle().apply { + putParcelable(ComposeActivity.DESTINATION, integrationScreen) + } + + val composeActivity = Intent(context, ComposeActivity::class.java).apply { + putExtras(bundle) + } - composeActivity.putExtra(ComposeActivity.TITLE, endpoint.name) context.startActivity(composeActivity) sheet.dismiss() } @@ -214,12 +211,4 @@ class ClientIntegration( .ocs .data } - - private fun parseTooltipResult(response: String?): TooltipResponse { - val element: JsonElement = JsonParser.parseString(response) - return Gson() - .fromJson(element, object : TypeToken>() {}) - .ocs - .data - } } 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 f26047d581a3..7a297de12d4c 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 @@ -431,7 +431,7 @@ private void showTopBanner(ConstraintLayout banner) { moreView.setOnClickListener(v -> LinkHelper.INSTANCE.openAppStore("Nextcloud", true, this)); assistantView.setOnClickListener(v -> { DrawerActivity.menuItemId = Menu.NONE; - startComposeActivity(new ComposeDestination.AssistantScreen(null), R.string.assistant_screen_top_bar_title); + startAssistantScreen(); }); if (getCapabilities() != null && getCapabilities().getAssistant().isTrue()) { assistantView.setVisibility(View.VISIBLE); @@ -583,7 +583,7 @@ private void onNavigationItemClicked(final MenuItem menuItem) { startRecentlyModifiedSearch(menuItem); } else if (itemId == R.id.nav_assistant) { resetOnlyPersonalAndOnDevice(); - startComposeActivity(new ComposeDestination.AssistantScreen(null), R.string.assistant_screen_top_bar_title); + startAssistantScreen(); } else if (itemId == R.id.nav_groupfolders) { resetOnlyPersonalAndOnDevice(); Intent intent = new Intent(getApplicationContext(), FileDisplayActivity.class); @@ -621,7 +621,7 @@ private void handleBottomNavigationViewClicks() { } else if (menuItemId == R.id.nav_favorites) { openFavoritesTab(); } else if (menuItemId == R.id.nav_assistant && !(this instanceof ComposeActivity)) { - startComposeActivity(new ComposeDestination.AssistantScreen(null), R.string.assistant_screen_top_bar_title); + startAssistantScreen(); } else if (menuItemId == R.id.nav_gallery) { openMediaTab(menuItem.getItemId()); } @@ -648,10 +648,12 @@ private void resetFileDepthAndConfigureMenuItem() { } } - private void startComposeActivity(ComposeDestination destination, int titleId) { + private void startAssistantScreen() { + final var destination = ComposeDestination.Companion.getAssistantScreen(this); Intent composeActivity = new Intent(getApplicationContext(), ComposeActivity.class); - composeActivity.putExtra(ComposeActivity.DESTINATION, destination.getId()); - composeActivity.putExtra(ComposeActivity.TITLE, titleId); + final Bundle bundle = new Bundle(); + bundle.putParcelable(ComposeActivity.DESTINATION, destination); + composeActivity.putExtras(bundle); startActivity(composeActivity); } From 978ae1a588990e598cf89b54e846fc294d144f31 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 17 Dec 2025 12:53:10 +0100 Subject: [PATCH 06/13] cleanup ui make it lazy Signed-off-by: alperozturk --- .../nextcloud/ui/ClientIntegrationScreen.kt | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt b/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt index f99dd77e353d..58febe6a34a7 100644 --- a/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt +++ b/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt @@ -12,6 +12,10 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Button import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -34,27 +38,28 @@ fun ClientIntegrationScreen(clientIntegrationUI: ClientIntegrationUI, baseUrl: S Column { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { - TextButton({ close(activity) }) { - androidx.compose.material3.Text("X") + TextButton(onClick = { activity?.finish() }) { + Text("X") } } - Row { - if (clientIntegrationUI.root.orientation == Orientation.VERTICAL) { - Column { - clientIntegrationUI.root.rows.forEach { row -> - Row { - row.children.forEach { element -> + when (clientIntegrationUI.root.orientation) { + Orientation.VERTICAL -> { + LazyColumn { + items(clientIntegrationUI.root.rows) { row -> + LazyRow { + items(row.children) { element -> DisplayElement(element, baseUrl, activity) } } } } - } else { - Row { - clientIntegrationUI.root.rows.forEach { row -> - Column { - row.children.forEach { element -> + } + else -> { + LazyRow { + items(clientIntegrationUI.root.rows) { row -> + LazyColumn { + items(row.children) { element -> DisplayElement(element, baseUrl, activity) } } @@ -68,15 +73,15 @@ fun ClientIntegrationScreen(clientIntegrationUI: ClientIntegrationUI, baseUrl: S @Composable private fun DisplayElement(element: Element, baseUrl: String, activity: Activity?) { when (element) { - is Button -> androidx.compose.material3.Button({ }) { - androidx.compose.material3.Text(element.label) + is Button -> Button(onClick = { }) { + Text(element.label) } is URL -> TextButton({ openLink(activity, baseUrl, element.url) - }) { androidx.compose.material3.Text(element.text) } + }) { Text(element.text) } - is Text -> androidx.compose.material3.Text(element.text) + is Text -> Text(element.text) } } @@ -86,10 +91,6 @@ private fun openLink(activity: Activity?, baseUrl: String, relativeUrl: String) } } -private fun close(activity: Activity?) { - activity?.finish() -} - @Composable @Preview private fun ClientIntegrationScreenPreviewVertical() { From b8ae14f93ac82d4fd17a7e208a2ae875e58e58dd Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 17 Dec 2025 15:21:10 +0100 Subject: [PATCH 07/13] fix crash Signed-off-by: alperozturk --- .../ui/fileactions/ClientIntegration.kt | 6 +-- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 42 ++++++++++--------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt b/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt index 7e9fec704e0c..c26948481fef 100644 --- a/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt +++ b/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt @@ -81,7 +81,7 @@ class ClientIntegration( text.text = endpoint.name if (endpoint.icon != null) { - sheet.lifecycleScope.launch { + sheet.lifecycleScope.launch(Dispatchers.IO) { val client = OwnCloudClientManagerFactory.getDefaultSingleton() .getNextcloudClientFor(user.toOwnCloudAccount(), context) @@ -123,7 +123,7 @@ class ClientIntegration( } private fun requestClientIntegration(endpoint: Endpoint, fileId: String, filePath: String) { - sheet.lifecycleScope.launch { + sheet.lifecycleScope.launch(Dispatchers.IO) { val client = OwnCloudClientManagerFactory.getDefaultSingleton() .getNextcloudClientFor(user.toOwnCloudAccount(), context) @@ -183,7 +183,7 @@ class ClientIntegration( } private fun startClientIntegration(endpoint: Endpoint, clientIntegrationUI: ClientIntegrationUI) { - sheet.lifecycleScope.launch { + sheet.lifecycleScope.launch(Dispatchers.IO) { val integrationScreen = ComposeDestination.ClientIntegrationScreen(endpoint.name, clientIntegrationUI) val bundle = Bundle().apply { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a28d62d57f2c..130aa4425a95 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ androidCommonLibraryVersion = "0.30.0" androidGifDrawableVersion = "1.2.29" androidImageCropperVersion = "4.7.0" -androidLibraryVersion = "12510b09e3e2de60b3c8a235fc5f13cc582f5ce4" +androidLibraryVersion = "4a6e0507dd" androidPluginVersion = '8.13.2' androidsvgVersion = "1.4" androidxMediaVersion = "1.5.1" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index bcf788601a28..e3d997d7dcaa 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -18939,17 +18939,14 @@ - - - - - - - - + + + + + + + + @@ -19166,6 +19163,14 @@ + + + + + + + + @@ -19542,15 +19547,12 @@ - - - - - - + + + + + + From d632c9bbb14a583b7f01df4dda72ae0c4c4fd19a Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 17 Dec 2025 15:32:48 +0100 Subject: [PATCH 08/13] fix crash Signed-off-by: alperozturk --- .../ui/fileactions/ClientIntegration.kt | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt b/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt index c26948481fef..a03d4208a639 100644 --- a/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt +++ b/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt @@ -19,6 +19,7 @@ import androidx.core.graphics.createBitmap import androidx.core.graphics.drawable.toDrawable import androidx.core.net.toUri import androidx.lifecycle.lifecycleScope +import com.google.gson.Gson import com.google.gson.GsonBuilder import com.google.gson.JsonElement import com.google.gson.JsonParser @@ -28,6 +29,7 @@ import com.nextcloud.android.lib.resources.clientintegration.ClientIntegrationUI import com.nextcloud.android.lib.resources.clientintegration.Element import com.nextcloud.android.lib.resources.clientintegration.ElementTypeAdapter import com.nextcloud.android.lib.resources.clientintegration.Endpoint +import com.nextcloud.android.lib.resources.clientintegration.TooltipResponse import com.nextcloud.client.account.User import com.nextcloud.common.JSONRequestBody import com.nextcloud.operations.GetMethod @@ -170,7 +172,12 @@ class ClientIntegration( var output: ClientIntegrationUI? try { output = parseClientIntegrationResult(response) - startClientIntegration(endpoint, output) + if (output.root != null) { + startClientIntegration(endpoint, output) + } else { + val tooltipResponse = parseTooltipResult(response) + context.showToast(tooltipResponse.tooltip) + } } catch (_: JsonSyntaxException) { if (result == HttpStatus.SC_OK) { context.showToast(context.resources.getString(R.string.action_triggered)) @@ -182,9 +189,17 @@ class ClientIntegration( } } - private fun startClientIntegration(endpoint: Endpoint, clientIntegrationUI: ClientIntegrationUI) { + private fun parseTooltipResult(response: String?): TooltipResponse { + val element: JsonElement = JsonParser.parseString(response) + return Gson() + .fromJson(element, object : TypeToken>() {}) + .ocs + .data + } + + private fun startClientIntegration(endpoint: Endpoint, data: ClientIntegrationUI) { sheet.lifecycleScope.launch(Dispatchers.IO) { - val integrationScreen = ComposeDestination.ClientIntegrationScreen(endpoint.name, clientIntegrationUI) + val integrationScreen = ComposeDestination.ClientIntegrationScreen(endpoint.name, data) val bundle = Bundle().apply { putParcelable(ComposeActivity.DESTINATION, integrationScreen) From ac1bb85ae424248ccd5704a39f5250a60ab08f1b Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 17 Dec 2025 16:22:21 +0100 Subject: [PATCH 09/13] new lib Signed-off-by: tobiasKaminsky --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 130aa4425a95..d30a0f325ac9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ androidCommonLibraryVersion = "0.30.0" androidGifDrawableVersion = "1.2.29" androidImageCropperVersion = "4.7.0" -androidLibraryVersion = "4a6e0507dd" +androidLibraryVersion = "264b979b78f04ed2885c0c06f589fbf4080be932" androidPluginVersion = '8.13.2' androidsvgVersion = "1.4" androidxMediaVersion = "1.5.1" From 11116dcd941bd8c65d76ad7e8124174c00fe3d07 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 17 Dec 2025 16:28:34 +0100 Subject: [PATCH 10/13] fix empty screen Signed-off-by: alperozturk --- .../nextcloud/ui/ClientIntegrationScreen.kt | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt b/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt index 58febe6a34a7..02ccb7566f4a 100644 --- a/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt +++ b/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt @@ -9,13 +9,17 @@ package com.nextcloud.ui import android.app.Activity import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items -import androidx.compose.material3.Button +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -36,13 +40,16 @@ import com.owncloud.android.utils.DisplayUtils fun ClientIntegrationScreen(clientIntegrationUI: ClientIntegrationUI, baseUrl: String) { val activity = LocalContext.current.getActivity() - Column { + Scaffold(topBar = { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { - TextButton(onClick = { activity?.finish() }) { - Text("X") + IconButton(onClick = { activity?.finish() }) { + Icon( + imageVector = Icons.Filled.Close, + contentDescription = "Close" + ) } } - + }, modifier = Modifier.fillMaxSize()) { when (clientIntegrationUI.root.orientation) { Orientation.VERTICAL -> { LazyColumn { @@ -73,15 +80,15 @@ fun ClientIntegrationScreen(clientIntegrationUI: ClientIntegrationUI, baseUrl: S @Composable private fun DisplayElement(element: Element, baseUrl: String, activity: Activity?) { when (element) { - is Button -> Button(onClick = { }) { - Text(element.label) + is Button -> androidx.compose.material3.Button(onClick = { }) { + androidx.compose.material3.Text(element.label) } is URL -> TextButton({ openLink(activity, baseUrl, element.url) - }) { Text(element.text) } + }) { androidx.compose.material3.Text(element.text) } - is Text -> Text(element.text) + is Text -> androidx.compose.material3.Text(element.text) } } From 7ed947850151655eea2cd8c225739ddf19c04b07 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 19 Dec 2025 14:44:18 +0100 Subject: [PATCH 11/13] handle lib Signed-off-by: alperozturk --- .../nextcloud/ui/ClientIntegrationScreen.kt | 56 ++++++++++--------- .../ui/fileactions/ClientIntegration.kt | 15 +++-- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 16 ++++++ 4 files changed, 56 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt b/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt index 02ccb7566f4a..42f00c5dd0ae 100644 --- a/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt +++ b/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items @@ -20,17 +21,19 @@ import androidx.compose.material.icons.filled.Close import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview -import com.nextcloud.android.lib.resources.clientintegration.Button import com.nextcloud.android.lib.resources.clientintegration.ClientIntegrationUI import com.nextcloud.android.lib.resources.clientintegration.Element import com.nextcloud.android.lib.resources.clientintegration.Layout -import com.nextcloud.android.lib.resources.clientintegration.Orientation -import com.nextcloud.android.lib.resources.clientintegration.Text +import com.nextcloud.android.lib.resources.clientintegration.LayoutButton +import com.nextcloud.android.lib.resources.clientintegration.LayoutOrientation +import com.nextcloud.android.lib.resources.clientintegration.LayoutRow +import com.nextcloud.android.lib.resources.clientintegration.LayoutText import com.nextcloud.android.lib.resources.clientintegration.URL import com.nextcloud.utils.extensions.getActivity import com.owncloud.android.lib.resources.status.OCCapability @@ -39,6 +42,7 @@ import com.owncloud.android.utils.DisplayUtils @Composable fun ClientIntegrationScreen(clientIntegrationUI: ClientIntegrationUI, baseUrl: String) { val activity = LocalContext.current.getActivity() + val layoutRows = clientIntegrationUI.root?.layoutRows ?: listOf() Scaffold(topBar = { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { @@ -50,10 +54,10 @@ fun ClientIntegrationScreen(clientIntegrationUI: ClientIntegrationUI, baseUrl: S } } }, modifier = Modifier.fillMaxSize()) { - when (clientIntegrationUI.root.orientation) { - Orientation.VERTICAL -> { - LazyColumn { - items(clientIntegrationUI.root.rows) { row -> + when (clientIntegrationUI.root?.orientation) { + LayoutOrientation.VERTICAL -> { + LazyColumn(modifier = Modifier.padding(it)) { + items(layoutRows) { row -> LazyRow { items(row.children) { element -> DisplayElement(element, baseUrl, activity) @@ -63,8 +67,8 @@ fun ClientIntegrationScreen(clientIntegrationUI: ClientIntegrationUI, baseUrl: S } } else -> { - LazyRow { - items(clientIntegrationUI.root.rows) { row -> + LazyRow(modifier = Modifier.padding(it)) { + items(layoutRows) { row -> LazyColumn { items(row.children) { element -> DisplayElement(element, baseUrl, activity) @@ -80,15 +84,15 @@ fun ClientIntegrationScreen(clientIntegrationUI: ClientIntegrationUI, baseUrl: S @Composable private fun DisplayElement(element: Element, baseUrl: String, activity: Activity?) { when (element) { - is Button -> androidx.compose.material3.Button(onClick = { }) { - androidx.compose.material3.Text(element.label) + is LayoutButton -> androidx.compose.material3.Button(onClick = { }) { + Text(element.label) } is URL -> TextButton({ openLink(activity, baseUrl, element.url) - }) { androidx.compose.material3.Text(element.text) } + }) { Text(element.text) } - is Text -> androidx.compose.material3.Text(element.text) + is LayoutText -> Text(element.text) } } @@ -104,15 +108,15 @@ private fun ClientIntegrationScreenPreviewVertical() { val clientIntegrationUI = ClientIntegrationUI( OCCapability.CLIENT_INTEGRATION_VERSION, Layout( - Orientation.VERTICAL, + LayoutOrientation.VERTICAL, mutableListOf( - com.nextcloud.android.lib.resources.clientintegration.Row( - listOf(Button("Click", "Primary"), Text("123")) + LayoutRow( + listOf(LayoutButton("Click", "Primary"), LayoutText("123")) ), - com.nextcloud.android.lib.resources.clientintegration.Row( - listOf(Button("Click2", "Primary")) + LayoutRow( + listOf(LayoutButton("Click2", "Primary")) ), - com.nextcloud.android.lib.resources.clientintegration.Row( + LayoutRow( listOf(URL("Analytics report created", "https://nextcloud.com")) ) ) @@ -131,15 +135,15 @@ private fun ClientIntegrationScreenPreviewHorizontal() { val clientIntegrationUI = ClientIntegrationUI( OCCapability.CLIENT_INTEGRATION_VERSION, Layout( - Orientation.HORIZONTAL, + LayoutOrientation.HORIZONTAL, mutableListOf( - com.nextcloud.android.lib.resources.clientintegration.Row( - listOf(Button("Click", "Primary"), Text("123")) + LayoutRow( + listOf(LayoutButton("Click", "Primary"), LayoutText("123")) ), - com.nextcloud.android.lib.resources.clientintegration.Row( - listOf(Button("Click2", "Primary")) + LayoutRow( + listOf(LayoutButton("Click2", "Primary")) ), - com.nextcloud.android.lib.resources.clientintegration.Row( + LayoutRow( listOf(URL("Analytics report created", "https://nextcloud.com")) ) ) @@ -155,7 +159,7 @@ private fun ClientIntegrationScreenPreviewEmpty() { val clientIntegrationUI = ClientIntegrationUI( OCCapability.CLIENT_INTEGRATION_VERSION, Layout( - Orientation.HORIZONTAL, + LayoutOrientation.HORIZONTAL, emptyList() ) ) diff --git a/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt b/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt index a03d4208a639..87dd4b7d1ef7 100644 --- a/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt +++ b/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt @@ -37,7 +37,6 @@ import com.nextcloud.operations.PostMethod import com.nextcloud.ui.composeActivity.ComposeActivity import com.nextcloud.ui.composeActivity.ComposeDestination import com.nextcloud.utils.GlideHelper -import com.nextcloud.utils.extensions.showToast import com.owncloud.android.R import com.owncloud.android.databinding.FileActionsBottomSheetBinding import com.owncloud.android.databinding.FileActionsBottomSheetItemBinding @@ -165,30 +164,34 @@ class ClientIntegration( val result = try { client.execute(method) } catch (_: IOException) { - context.showToast(context.resources.getString(R.string.failed_to_start_action)) + showMessage(context.resources.getString(R.string.failed_to_start_action)) } val response = method.getResponseBodyAsString() var output: ClientIntegrationUI? try { output = parseClientIntegrationResult(response) - if (output.root != null) { + if (output.root != null && output.root?.layoutRows != null) { startClientIntegration(endpoint, output) } else { val tooltipResponse = parseTooltipResult(response) - context.showToast(tooltipResponse.tooltip) + showMessage(tooltipResponse.tooltip) } } catch (_: JsonSyntaxException) { if (result == HttpStatus.SC_OK) { - context.showToast(context.resources.getString(R.string.action_triggered)) + showMessage(context.resources.getString(R.string.action_triggered)) } else { - context.showToast(context.resources.getString(R.string.failed_to_start_action)) + showMessage(context.resources.getString(R.string.failed_to_start_action)) } } sheet.dismiss() } } + private suspend fun showMessage(message: String) = withContext(Dispatchers.Main) { + DisplayUtils.showSnackMessage(sheet.view, message) + } + private fun parseTooltipResult(response: String?): TooltipResponse { val element: JsonElement = JsonParser.parseString(response) return Gson() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d30a0f325ac9..b67b60cd36f5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ androidCommonLibraryVersion = "0.30.0" androidGifDrawableVersion = "1.2.29" androidImageCropperVersion = "4.7.0" -androidLibraryVersion = "264b979b78f04ed2885c0c06f589fbf4080be932" +androidLibraryVersion = "f51ba3bf5f" androidPluginVersion = '8.13.2' androidsvgVersion = "1.4" androidxMediaVersion = "1.5.1" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index e3d997d7dcaa..cb60cff44dc4 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -18995,6 +18995,14 @@ + + + + + + + + @@ -19784,6 +19792,14 @@ + + + + + + + + From 0a07d6c9500b27172f6aeba166e347a9be43c108 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 19 Dec 2025 15:12:24 +0100 Subject: [PATCH 12/13] handle lib Signed-off-by: alperozturk --- .../com/nextcloud/ui/ClientIntegrationScreen.kt | 13 +++++++------ .../nextcloud/ui/fileactions/ClientIntegration.kt | 2 +- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 8 ++++++++ 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt b/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt index 42f00c5dd0ae..76bd4a9f2307 100644 --- a/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt +++ b/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold @@ -34,7 +35,7 @@ import com.nextcloud.android.lib.resources.clientintegration.LayoutButton import com.nextcloud.android.lib.resources.clientintegration.LayoutOrientation import com.nextcloud.android.lib.resources.clientintegration.LayoutRow import com.nextcloud.android.lib.resources.clientintegration.LayoutText -import com.nextcloud.android.lib.resources.clientintegration.URL +import com.nextcloud.android.lib.resources.clientintegration.LayoutURL import com.nextcloud.utils.extensions.getActivity import com.owncloud.android.lib.resources.status.OCCapability import com.owncloud.android.utils.DisplayUtils @@ -42,7 +43,7 @@ import com.owncloud.android.utils.DisplayUtils @Composable fun ClientIntegrationScreen(clientIntegrationUI: ClientIntegrationUI, baseUrl: String) { val activity = LocalContext.current.getActivity() - val layoutRows = clientIntegrationUI.root?.layoutRows ?: listOf() + val layoutRows = clientIntegrationUI.root?.rows ?: listOf() Scaffold(topBar = { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { @@ -84,11 +85,11 @@ fun ClientIntegrationScreen(clientIntegrationUI: ClientIntegrationUI, baseUrl: S @Composable private fun DisplayElement(element: Element, baseUrl: String, activity: Activity?) { when (element) { - is LayoutButton -> androidx.compose.material3.Button(onClick = { }) { + is LayoutButton -> Button(onClick = { }) { Text(element.label) } - is URL -> TextButton({ + is LayoutURL -> TextButton({ openLink(activity, baseUrl, element.url) }) { Text(element.text) } @@ -117,7 +118,7 @@ private fun ClientIntegrationScreenPreviewVertical() { listOf(LayoutButton("Click2", "Primary")) ), LayoutRow( - listOf(URL("Analytics report created", "https://nextcloud.com")) + listOf(LayoutURL("Analytics report created", "https://nextcloud.com")) ) ) ) @@ -144,7 +145,7 @@ private fun ClientIntegrationScreenPreviewHorizontal() { listOf(LayoutButton("Click2", "Primary")) ), LayoutRow( - listOf(URL("Analytics report created", "https://nextcloud.com")) + listOf(LayoutURL("Analytics report created", "https://nextcloud.com")) ) ) ) diff --git a/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt b/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt index 87dd4b7d1ef7..95ce2955b435 100644 --- a/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt +++ b/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt @@ -171,7 +171,7 @@ class ClientIntegration( var output: ClientIntegrationUI? try { output = parseClientIntegrationResult(response) - if (output.root != null && output.root?.layoutRows != null) { + if (output.root != null && output.root?.rows != null) { startClientIntegration(endpoint, output) } else { val tooltipResponse = parseTooltipResult(response) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b67b60cd36f5..4e7d3a791625 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ androidCommonLibraryVersion = "0.30.0" androidGifDrawableVersion = "1.2.29" androidImageCropperVersion = "4.7.0" -androidLibraryVersion = "f51ba3bf5f" +androidLibraryVersion = "16fc939b86" androidPluginVersion = '8.13.2' androidsvgVersion = "1.4" androidxMediaVersion = "1.5.1" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index cb60cff44dc4..4230381247dd 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -18955,6 +18955,14 @@ + + + + + + + + From 999c84820bd47cfc61b7e186d58a770d0383c220 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 19 Dec 2025 15:18:40 +0100 Subject: [PATCH 13/13] handle lib Signed-off-by: alperozturk --- .../main/java/com/nextcloud/ui/ClientIntegrationScreen.kt | 1 + .../java/com/nextcloud/ui/fileactions/ClientIntegration.kt | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt b/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt index 76bd4a9f2307..f7cc6b585097 100644 --- a/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt +++ b/app/src/main/java/com/nextcloud/ui/ClientIntegrationScreen.kt @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2025 Alper Ozturk * SPDX-FileCopyrightText: 2025 Tobias Kaminsky * SPDX-License-Identifier: AGPL-3.0-or-later */ diff --git a/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt b/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt index 95ce2955b435..0561a66b5178 100644 --- a/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt +++ b/app/src/main/java/com/nextcloud/ui/fileactions/ClientIntegration.kt @@ -1,7 +1,7 @@ /* * Nextcloud - Android Client * - * SPDX-FileCopyrightText: 2025 Your Name + * SPDX-FileCopyrightText: 2025 Alper Ozturk * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -168,9 +168,8 @@ class ClientIntegration( } val response = method.getResponseBodyAsString() - var output: ClientIntegrationUI? try { - output = parseClientIntegrationResult(response) + val output = parseClientIntegrationResult(response) if (output.root != null && output.root?.rows != null) { startClientIntegration(endpoint, output) } else { @@ -189,7 +188,7 @@ class ClientIntegration( } private suspend fun showMessage(message: String) = withContext(Dispatchers.Main) { - DisplayUtils.showSnackMessage(sheet.view, message) + DisplayUtils.showSnackMessage(sheet.requireActivity(), message) } private fun parseTooltipResult(response: String?): TooltipResponse {