@@ -30,19 +30,30 @@ import androidx.fragment.app.FragmentManager
3030import androidx.fragment.app.setFragmentResult
3131import androidx.lifecycle.LifecycleOwner
3232import androidx.lifecycle.ViewModelProvider
33+ import androidx.lifecycle.coroutineScope
3334import com.google.android.material.bottomsheet.BottomSheetBehavior
3435import com.google.android.material.bottomsheet.BottomSheetDialog
3536import com.google.android.material.bottomsheet.BottomSheetDialogFragment
37+ import com.google.gson.Gson
38+ import com.google.gson.GsonBuilder
3639import com.nextcloud.android.common.ui.theme.utils.ColorRole
40+ import com.nextcloud.android.lib.resources.clientintegration.ClientIntegrationUI
41+ import com.nextcloud.android.lib.resources.clientintegration.Element
42+ import com.nextcloud.android.lib.resources.clientintegration.ElementTypeAdapter
3743import com.nextcloud.android.lib.resources.clientintegration.Endpoint
44+ import com.nextcloud.android.lib.resources.clientintegration.TooltipResponse
3845import com.nextcloud.client.account.CurrentAccountProvider
3946import com.nextcloud.client.di.Injectable
4047import com.nextcloud.client.di.ViewModelFactory
48+ import com.nextcloud.common.JSONRequestBody
49+ import com.nextcloud.operations.GetMethod
50+ import com.nextcloud.operations.PostMethod
4151import com.nextcloud.ui.composeActivity.ComposeActivity
4252import com.nextcloud.ui.composeActivity.ComposeDestination
4353import com.nextcloud.ui.fileactions.FileActionsViewModel.Companion.ARG_ENDPOINTS
4454import com.nextcloud.utils.GlideHelper
4555import com.nextcloud.utils.extensions.setVisibleIf
56+ import com.nextcloud.utils.extensions.showToast
4657import com.owncloud.android.R
4758import com.owncloud.android.databinding.FileActionsBottomSheetBinding
4859import com.owncloud.android.databinding.FileActionsBottomSheetItemBinding
@@ -52,6 +63,7 @@ import com.owncloud.android.datamodel.SyncedFolderProvider
5263import com.owncloud.android.datamodel.ThumbnailsCacheManager
5364import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
5465import com.owncloud.android.lib.resources.files.model.FileLockType
66+ import com.owncloud.android.lib.resources.status.Method
5567import com.owncloud.android.ui.activity.ComponentsGetter
5668import com.owncloud.android.utils.DisplayUtils
5769import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener
@@ -61,6 +73,7 @@ import kotlinx.coroutines.CoroutineScope
6173import kotlinx.coroutines.Dispatchers
6274import kotlinx.coroutines.launch
6375import kotlinx.coroutines.withContext
76+ import okhttp3.RequestBody
6477import javax.inject.Inject
6578
6679class FileActionsBottomSheet :
@@ -93,7 +106,7 @@ class FileActionsBottomSheet :
93106 private val thumbnailAsyncTasks = mutableListOf<ThumbnailsCacheManager .ThumbnailGenerationTask >()
94107
95108 private var endpoints: List <Endpoint >? = mutableListOf ()
96-
109+
97110 fun interface ResultListener {
98111 fun onResult (@IdRes actionId : Int )
99112 }
@@ -220,7 +233,7 @@ class FileActionsBottomSheet :
220233 }
221234 // add client integration
222235 if (endpoints != null ) {
223- for (val e in endpoints) {
236+ for (val e in endpoints) {
224237 val ui = inflateClientIntegrationActionView(e)
225238 binding.fileActionsList.addView(ui)
226239 }
@@ -332,23 +345,16 @@ class FileActionsBottomSheet :
332345 val itemBinding = FileActionsBottomSheetItemBinding .inflate(layoutInflater, binding.fileActionsList, false )
333346 .apply {
334347 root.setOnClickListener {
335- val composeActivity = Intent (context, ComposeActivity ::class .java)
336- composeActivity.putExtra(ComposeActivity .DESTINATION , ComposeDestination .ClientIntegrationScreen )
337- composeActivity.putExtra(ComposeActivity .ARGS_ENDPOINT , endpoint)
338348 if (viewModel.uiState.value is FileActionsViewModel .UiState .LoadedForSingleFile ) {
339- composeActivity.putExtra(
340- ComposeActivity .ARGS_FILE_ID ,
341- (viewModel.uiState.value as FileActionsViewModel .UiState .LoadedForSingleFile ).titleFile?.localId
342- )
349+ val singleFile = (viewModel.uiState.value as FileActionsViewModel .UiState .LoadedForSingleFile )
343350
344- composeActivity.putExtra(
345- ComposeActivity .ARGS_FILE_PATH ,
346- (viewModel.uiState.value as FileActionsViewModel .UiState .LoadedForSingleFile ).titleFile?.remotePath
347- )
351+ val fileId = singleFile.titleFile?.localId.toString()
352+ val filePath = singleFile.titleFile?.remotePath.toString()
353+
354+ requestClientIntegration(endpoint, fileId, filePath)
355+ } else {
356+ requestClientIntegration(endpoint, " " , " " )
348357 }
349- composeActivity.putExtra(ComposeActivity .TITLE , endpoint.name)
350- startActivity(composeActivity)
351- dismiss()
352358 }
353359 text.text = endpoint.name
354360
@@ -384,6 +390,95 @@ class FileActionsBottomSheet :
384390 return itemBinding.root
385391 }
386392
393+ private fun requestClientIntegration (endpoint : Endpoint , fileId : String , filePath : String ) {
394+ lifecycle.coroutineScope.launch(Dispatchers .IO ) {
395+ val client = OwnCloudClientManagerFactory .getDefaultSingleton()
396+ .getNextcloudClientFor(currentUserProvider.user.toOwnCloudAccount(), context)
397+
398+ // construct url
399+ var url = client.baseUri.toString() + endpoint.url
400+
401+ val method = when (endpoint.method) {
402+ Method .GET -> {
403+ endpoint.params!! .forEach {
404+ when (it.value) {
405+ " {fileId}" -> url = url.replace(it.key, fileId, false )
406+ " {filePath}" -> url = url.replace(it.key, filePath, false )
407+ }
408+ }
409+ GetMethod (url, true )
410+ }
411+
412+ Method .POST -> {
413+ val requestBody = if (endpoint.params?.isNotEmpty() == true ) {
414+ val jsonRequestBody = JSONRequestBody ()
415+ endpoint.params!! .forEach {
416+ when (it.value) {
417+ " {filePath}" -> jsonRequestBody.put(it.key, filePath)
418+ " {fileId}" -> jsonRequestBody.put(it.key, fileId)
419+ }
420+ }
421+
422+ jsonRequestBody.get()
423+ } else {
424+ RequestBody .EMPTY
425+ }
426+
427+ PostMethod (url, true , requestBody)
428+ }
429+
430+ else -> GetMethod (url, true )
431+ }
432+
433+ val result = try {
434+ client.execute(method)
435+ } catch (exception: Exception ) {
436+ val e = exception
437+ TODO (" Add error handling here" )
438+ }
439+ val response = method.getResponseBodyAsString()
440+
441+ var output: ClientIntegrationUI ? = null
442+ try {
443+ output = parseClientIntegrationResult(response)
444+ startClientIntegration(endpoint, output)
445+ } catch (e: Exception ) {
446+ try {
447+ val tooltipResponse = parseTooltipResult(response)
448+
449+ activity?.showToast(tooltipResponse.tooltip)
450+ } catch (e: Exception ) {
451+ activity?.showToast(" Failed to start action!" )
452+ }
453+ dismiss()
454+ }
455+ }
456+ }
457+
458+ private fun startClientIntegration (endpoint : Endpoint , clientIntegrationUI : ClientIntegrationUI ) {
459+ CoroutineScope (Dispatchers .IO ).launch {
460+ val composeActivity = Intent (context, ComposeActivity ::class .java)
461+ composeActivity.putExtra(ComposeActivity .DESTINATION , ComposeDestination .ClientIntegrationScreen )
462+ composeActivity.putExtra(ComposeActivity .ARGS_CLIENT_INTEGRATION_UI , clientIntegrationUI)
463+
464+ composeActivity.putExtra(ComposeActivity .TITLE , endpoint.name)
465+ startActivity(composeActivity)
466+ dismiss()
467+ }
468+ }
469+
470+ private fun parseClientIntegrationResult (response : String? ): ClientIntegrationUI {
471+ val gson =
472+ GsonBuilder ()
473+ .registerTypeHierarchyAdapter(Element ::class .java, ElementTypeAdapter ())
474+ .create()
475+
476+ return gson.fromJson(response, ClientIntegrationUI ::class .java)
477+ }
478+
479+ private fun parseTooltipResult (response : String? ): TooltipResponse =
480+ Gson ().fromJson(response, TooltipResponse ::class .java)
481+
387482 private fun dispatchActionClick (id : Int? ) {
388483 if (id != null ) {
389484 setFragmentResult(REQUEST_KEY , bundleOf(RESULT_KEY_ACTION_ID to id))
0 commit comments