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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.files

import com.owncloud.android.datamodel.Template
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.ui.dialog.ChooseRichDocumentsTemplateDialogFragment
import org.apache.commons.httpclient.HttpStatus
import org.apache.commons.httpclient.methods.GetMethod
import org.json.JSONObject

class FetchTemplateOperation(private val type: ChooseRichDocumentsTemplateDialogFragment.Type) :
RemoteOperation<Any>() {

@Suppress("TooGenericExceptionCaught")
override fun run(client: OwnCloudClient): RemoteOperationResult<Any> {
var getMethod: GetMethod? = null

return try {
getMethod = GetMethod(templateUrl(client.baseUri.toString())).apply {
addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE)
}

val status = client.executeMethod(getMethod, SYNC_READ_TIMEOUT, SYNC_CONNECTION_TIMEOUT)
if (status != HttpStatus.SC_OK) {
client.exhaustResponse(getMethod.responseBodyAsStream)
return RemoteOperationResult(false, getMethod)
}

val templates = parseTemplates(getMethod.responseBodyAsString)
RemoteOperationResult<Any>(true, getMethod).apply { setData(ArrayList<Any>(templates)) }
} catch (e: Exception) {
RemoteOperationResult<Any>(e).also {
Log_OC.e(TAG, "Get templates for type $type failed: ${it.logMessage}", it.exception)
}
} finally {
getMethod?.releaseConnection()
}
}

private fun templateUrl(baseUri: String): String = baseUri + TEMPLATE_URL + type.name.lowercase() + JSON_FORMAT

private fun parseTemplates(response: String): List<Template> {
val data = JSONObject(response).getJSONObject(NODE_OCS).getJSONArray(NODE_DATA)

return (0 until data.length()).map { index ->
data.getJSONObject(index).toTemplate()
}
}

private fun JSONObject.toTemplate(): Template = Template(
getLong(NODE_ID),
getString(NODE_NAME),
optString(NODE_PREVIEW),
Template.Type.parse(getString(NODE_TYPE)),
getString(NODE_EXTENSION)
)

companion object {
private val TAG = FetchTemplateOperation::class.java.simpleName
private const val SYNC_READ_TIMEOUT = 40000
private const val SYNC_CONNECTION_TIMEOUT = 5000
private const val TEMPLATE_URL = "/ocs/v2.php/apps/richdocuments/api/v1/templates/"
private const val JSON_FORMAT = "?format=json"

private const val NODE_OCS = "ocs"
private const val NODE_DATA = "data"
private const val NODE_ID = "id"
private const val NODE_NAME = "name"
private const val NODE_PREVIEW = "preview"
private const val NODE_TYPE = "type"
private const val NODE_EXTENSION = "extension"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class ChooseTemplateDialogFragment :
Injectable {

private lateinit var fileNames: MutableSet<String>
private var hasUserInteracted = false

@Inject
lateinit var clientFactory: ClientFactory
Expand Down Expand Up @@ -142,6 +143,7 @@ class ChooseTemplateDialogFragment :
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) = Unit
override fun afterTextChanged(s: Editable) {
hasUserInteracted = true
checkFileNameAfterEachType()
}
})
Expand Down Expand Up @@ -224,71 +226,59 @@ class ChooseTemplateDialogFragment :
private fun getOCCapability(): OCCapability = fileDataStorageManager.getCapability(currentAccount.user.accountName)

override fun onClick(v: View) {
val selectedTemplate = adapter?.selectedTemplate
?: return DisplayUtils.showSnackMessage(binding.list, R.string.select_one_template)

val state = resolveFilenameState()
if (state !is TemplateFilenameState.Valid) {
state.errorMessage?.let { DisplayUtils.showSnackMessage(requireActivity(), it.toString()) }
return
}

val name = binding.filename.text.toString()
val path = parentFolder?.remotePath + name
val selectedTemplate = adapter?.selectedTemplate
val fullPath = if (name.endsWith(selectedTemplate.extension)) {
path
} else {
path + DOT + selectedTemplate.extension
}

val errorMessage = FileNameValidator.checkFileName(name, getOCCapability(), requireContext())
createFromTemplate(selectedTemplate, fullPath)
}

when {
selectedTemplate == null -> {
DisplayUtils.showSnackMessage(binding.list, R.string.select_one_template)
}
private fun resolveFilenameState(): TemplateFilenameState {
val selectedTemplate = adapter?.selectedTemplate ?: return TemplateFilenameState.NoTemplateSelected
val name = binding.filename.text.toString().trim()
val validationError = FileNameValidator.checkFileName(name, getOCCapability(), requireContext(), fileNames)

errorMessage != null -> {
DisplayUtils.showSnackMessage(requireActivity(), errorMessage)
}
return when {
name.equals(DOT + selectedTemplate.extension, ignoreCase = true) ->
TemplateFilenameState.JustExtension(getString(R.string.enter_filename))

name.equals(DOT + selectedTemplate.extension, ignoreCase = true) -> {
DisplayUtils.showSnackMessage(binding.list, R.string.enter_filename)
}
validationError != null -> TemplateFilenameState.Invalid(validationError)

else -> {
val fullPath = if (!name.endsWith(selectedTemplate.extension)) {
path + DOT + selectedTemplate.extension
} else {
path
}
createFromTemplate(selectedTemplate, fullPath)
}
FileNameValidator.isFileHidden(name) ->
TemplateFilenameState.HiddenName(getText(R.string.hidden_file_name_warning))

name.substringAfterLast(DOT) != selectedTemplate.extension ->
TemplateFilenameState.ChangedExtension(getString(R.string.extension_cannot_be_changed))

else -> TemplateFilenameState.Valid
}
}

private fun checkFileNameAfterEachType() {
if (positiveButton == null) return
val positiveButton = positiveButton ?: return
val state = resolveFilenameState()

val selectedTemplate = adapter?.selectedTemplate
val name = binding.filename.text.toString().trim()
val isNameJustExtension = selectedTemplate != null &&
name.equals(
DOT + selectedTemplate.extension,
ignoreCase = true
)
val fileNameValidatorResult =
FileNameValidator.checkFileName(name, getOCCapability(), requireContext(), fileNames)

val errorMessage = when {
isNameJustExtension -> null
fileNameValidatorResult != null -> fileNameValidatorResult
else -> null
}
val isValid = state is TemplateFilenameState.Valid
positiveButton.isEnabled = isValid
positiveButton.isClickable = isValid

val isNameValid = (errorMessage == null) && !name.equals(DOT + selectedTemplate?.extension, ignoreCase = true)
val isHiddenFileName = FileNameValidator.isFileHidden(name)
val isChangedExtension = name.substringAfterLast(DOT) != selectedTemplate?.extension
if (!hasUserInteracted) return

binding.filenameContainer.isErrorEnabled = !isNameValid || isHiddenFileName || isChangedExtension
binding.filenameContainer.error = when {
!isNameValid -> errorMessage ?: getString(R.string.enter_filename)
isHiddenFileName -> getText(R.string.hidden_file_name_warning)
isChangedExtension -> getString(R.string.extension_cannot_be_changed)
else -> null
}

positiveButton?.apply {
isEnabled = isNameValid && !isHiddenFileName && !isChangedExtension
isClickable = isEnabled
}
binding.filenameContainer.isErrorEnabled = state.errorMessage != null
binding.filenameContainer.error = state.errorMessage
}

@Suppress("LongParameterList", "DEPRECATION")
Expand Down Expand Up @@ -406,7 +396,8 @@ class ChooseTemplateDialogFragment :
}

if (templateList.templates.isEmpty()) {
DisplayUtils.showSnackMessage(fragment.binding.list, R.string.error_retrieving_templates)
fragment.dismiss()
DisplayUtils.showSnackMessage(fragment.requireActivity(), R.string.error_retrieving_templates)
return
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.dialog

sealed class TemplateFilenameState(val errorMessage: CharSequence?) {
data object Valid : TemplateFilenameState(null)
data object NoTemplateSelected : TemplateFilenameState(null)
class JustExtension(message: CharSequence) : TemplateFilenameState(message)
class HiddenName(message: CharSequence) : TemplateFilenameState(message)
class ChangedExtension(message: CharSequence) : TemplateFilenameState(message)
class Invalid(message: CharSequence) : TemplateFilenameState(message)
}
Loading