diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RemoteDataSourceModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RemoteDataSourceModule.kt
index 14efaaac634..483b90e6e11 100644
--- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RemoteDataSourceModule.kt
+++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RemoteDataSourceModule.kt
@@ -33,6 +33,8 @@ import com.owncloud.android.data.capabilities.datasources.implementation.OCRemot
import com.owncloud.android.data.capabilities.datasources.mapper.RemoteCapabilityMapper
import com.owncloud.android.data.files.datasources.RemoteFileDataSource
import com.owncloud.android.data.files.datasources.implementation.OCRemoteFileDataSource
+import com.owncloud.android.data.links.datasources.RemoteLinksDataSource
+import com.owncloud.android.data.links.datasources.implementation.OCRemoteLinksDataSource
import com.owncloud.android.data.members.datasources.RemoteMembersDataSource
import com.owncloud.android.data.members.datasources.implementation.OCRemoteMembersDataSource
import com.owncloud.android.data.oauth.datasources.RemoteOAuthDataSource
@@ -78,6 +80,7 @@ val remoteDataSourceModule = module {
singleOf(::OCRemoteAuthenticationDataSource) bind RemoteAuthenticationDataSource::class
singleOf(::OCRemoteCapabilitiesDataSource) bind RemoteCapabilitiesDataSource::class
singleOf(::OCRemoteFileDataSource) bind RemoteFileDataSource::class
+ singleOf(::OCRemoteLinksDataSource) bind RemoteLinksDataSource::class
singleOf(::OCRemoteMembersDataSource) bind RemoteMembersDataSource::class
singleOf(::OCRemoteOAuthDataSource) bind RemoteOAuthDataSource::class
singleOf(::OCRemoteRolesDataSource) bind RemoteRolesDataSource::class
diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt
index 44e1e762781..3135cde74e6 100644
--- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt
+++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt
@@ -28,6 +28,7 @@ import com.owncloud.android.data.authentication.repository.OCAuthenticationRepos
import com.owncloud.android.data.capabilities.repository.OCCapabilityRepository
import com.owncloud.android.data.files.repository.OCFileRepository
import com.owncloud.android.data.folderbackup.repository.OCFolderBackupRepository
+import com.owncloud.android.data.links.repository.OCLinksRepository
import com.owncloud.android.data.members.repository.OCMembersRepository
import com.owncloud.android.data.oauth.repository.OCOAuthRepository
import com.owncloud.android.data.roles.repository.OCRolesRepository
@@ -44,6 +45,7 @@ import com.owncloud.android.domain.authentication.oauth.OAuthRepository
import com.owncloud.android.domain.automaticuploads.FolderBackupRepository
import com.owncloud.android.domain.capabilities.CapabilityRepository
import com.owncloud.android.domain.files.FileRepository
+import com.owncloud.android.domain.links.LinksRepository
import com.owncloud.android.domain.members.MembersRepository
import com.owncloud.android.domain.roles.RolesRepository
import com.owncloud.android.domain.server.ServerInfoRepository
@@ -63,6 +65,7 @@ val repositoryModule = module {
factoryOf(::OCCapabilityRepository) bind CapabilityRepository::class
factoryOf(::OCFileRepository) bind FileRepository::class
factoryOf(::OCFolderBackupRepository) bind FolderBackupRepository::class
+ factoryOf(::OCLinksRepository) bind LinksRepository::class
factoryOf(::OCMembersRepository) bind MembersRepository::class
factoryOf(::OCOAuthRepository) bind OAuthRepository::class
factoryOf(::OCRolesRepository) bind RolesRepository::class
diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt
index b53e5bd983a..a771da5d52c 100644
--- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt
+++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt
@@ -79,6 +79,7 @@ import com.owncloud.android.domain.files.usecases.SetLastUsageFileUseCase
import com.owncloud.android.domain.files.usecases.SortFilesUseCase
import com.owncloud.android.domain.files.usecases.SortFilesWithSyncInfoUseCase
import com.owncloud.android.domain.files.usecases.UpdateAlreadyDownloadedFilesPathUseCase
+import com.owncloud.android.domain.links.usecases.AddLinkUseCase
import com.owncloud.android.domain.members.usecases.AddMemberUseCase
import com.owncloud.android.domain.members.usecases.EditMemberUseCase
import com.owncloud.android.domain.members.usecases.RemoveMemberUseCase
@@ -316,4 +317,7 @@ val useCaseModule = module {
factoryOf(::EditMemberUseCase)
factoryOf(::RemoveMemberUseCase)
factoryOf(::SearchMembersUseCase)
+
+ // Links
+ factoryOf(::AddLinkUseCase)
}
diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt
index 201c13e9b02..c9dee872bcb 100644
--- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt
+++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt
@@ -7,7 +7,7 @@
* @author David Crespo Ríos
* @author Jorge Aguado Recio
*
- * Copyright (C) 2025 ownCloud GmbH.
+ * Copyright (C) 2026 ownCloud GmbH.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
@@ -55,6 +55,7 @@ import com.owncloud.android.presentation.settings.more.SettingsMoreViewModel
import com.owncloud.android.presentation.settings.security.SettingsSecurityViewModel
import com.owncloud.android.presentation.sharing.ShareViewModel
import com.owncloud.android.presentation.spaces.SpacesListViewModel
+import com.owncloud.android.presentation.spaces.links.SpaceLinksViewModel
import com.owncloud.android.presentation.spaces.members.SpaceMembersViewModel
import com.owncloud.android.presentation.transfers.TransfersViewModel
import com.owncloud.android.ui.ReceiveExternalFilesViewModel
@@ -85,6 +86,7 @@ val viewModelModule = module {
viewModelOf(::SettingsSecurityViewModel)
viewModelOf(::SettingsVideoUploadsViewModel)
viewModelOf(::SettingsViewModel)
+ viewModelOf(::SpaceLinksViewModel)
viewModelOf(::SpaceMembersViewModel)
viewModelOf(::FileOperationsViewModel)
diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/AddPublicLinkFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/AddPublicLinkFragment.kt
new file mode 100644
index 00000000000..0c7b2a1f5c2
--- /dev/null
+++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/AddPublicLinkFragment.kt
@@ -0,0 +1,298 @@
+/**
+ * ownCloud Android client application
+ *
+ * @author Jorge Aguado Recio
+ *
+ * Copyright (C) 2026 ownCloud GmbH.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.owncloud.android.presentation.spaces.links
+
+import android.app.DatePickerDialog
+import android.icu.util.Calendar
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.RadioButton
+import androidx.core.view.isVisible
+import androidx.fragment.app.Fragment
+import com.owncloud.android.R
+import com.owncloud.android.databinding.AddPublicLinkFragmentBinding
+import com.owncloud.android.domain.capabilities.model.CapabilityBooleanType
+import com.owncloud.android.domain.capabilities.model.OCCapability
+import com.owncloud.android.domain.links.model.OCLinkType
+import com.owncloud.android.domain.spaces.model.OCSpace
+import com.owncloud.android.extensions.collectLatestLifecycleFlow
+import com.owncloud.android.extensions.showErrorInSnackbar
+import com.owncloud.android.presentation.capabilities.CapabilityViewModel
+import com.owncloud.android.presentation.common.UIResult
+import com.owncloud.android.utils.DisplayUtils
+import org.koin.androidx.viewmodel.ext.android.activityViewModel
+import org.koin.androidx.viewmodel.ext.android.viewModel
+import org.koin.core.parameter.parametersOf
+import timber.log.Timber
+import java.text.SimpleDateFormat
+import java.util.Locale
+import java.util.TimeZone
+
+class AddPublicLinkFragment: Fragment(), SetPasswordDialogFragment.SetPasswordListener {
+ private var _binding: AddPublicLinkFragmentBinding? = null
+ private val binding get() = _binding!!
+
+ private val spaceLinksViewModel: SpaceLinksViewModel by activityViewModel {
+ parametersOf(
+ requireArguments().getString(ARG_ACCOUNT_NAME),
+ requireArguments().getParcelable(ARG_CURRENT_SPACE)
+ )
+ }
+ private val capabilityViewModel: CapabilityViewModel by viewModel {
+ parametersOf(
+ requireArguments().getString(ARG_ACCOUNT_NAME)
+ )
+ }
+
+ private var capabilities: OCCapability? = null
+ private var isPasswordEnforced = true
+ private var hasPassword = false
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ _binding = AddPublicLinkFragmentBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ requireActivity().setTitle(R.string.public_link_create_title)
+
+ binding.publicLinkPermissions.apply {
+ canViewPublicLinkRadioButton.tag = OCLinkType.CAN_VIEW
+ canEditPublicLinkRadioButton.tag = OCLinkType.CAN_EDIT
+ secretFileDropPublicLinkRadioButton.tag = OCLinkType.CREATE_ONLY
+ }
+
+ collectLatestLifecycleFlow(spaceLinksViewModel.addPublicLinkUIState) { uiState ->
+ uiState?.let {
+ it.selectedExpirationDate?.let { expirationDate ->
+ binding.expirationDateLayout.expirationDateValue.apply {
+ visibility = View.VISIBLE
+ text = DisplayUtils.displayDateToHumanReadable(expirationDate)
+ }
+ }
+
+ hasPassword = it.selectedPassword != null
+ it.selectedPermission?.let { selectedPermission ->
+ binding.optionsLayout.isVisible = true
+ binding.passwordLayout.apply {
+ passwordValue.isVisible = hasPassword
+ setPasswordButton.isVisible = !hasPassword && isPasswordEnforced
+ removePasswordButton.isVisible = hasPassword && isPasswordEnforced
+ setPasswordSwitch.isVisible = !isPasswordEnforced
+ setPasswordSwitch.isChecked = hasPassword
+ }
+ binding.createPublicLinkButton.isEnabled = isPasswordEnforced && hasPassword || !isPasswordEnforced
+
+ binding.createPublicLinkButton.setOnClickListener {
+ spaceLinksViewModel.createPublicLink(
+ binding.publicLinkNameEditText.text.toString().ifEmpty { getString(R.string.public_link_default_display_name) },
+ selectedPermission,
+ uiState.selectedExpirationDate,
+ uiState.selectedPassword,
+ )
+ }
+ }
+
+ bindDatePickerDialog(uiState.selectedExpirationDate)
+
+ binding.expirationDateLayout.apply {
+ expirationDateLayout.setOnClickListener {
+ if (uiState.selectedExpirationDate != null) {
+ openDatePickerDialog(uiState.selectedExpirationDate)
+ } else {
+ expirationDateSwitch.isChecked = true
+ }
+ }
+ }
+
+ binding.passwordLayout.apply {
+ passwordLayout.setOnClickListener {
+ if (!isPasswordEnforced){
+ setPasswordSwitch.isChecked = true
+ }
+ showPasswordDialog(uiState.selectedPassword)
+ }
+ }
+ }
+ }
+
+ capabilityViewModel.capabilities.observe(viewLifecycleOwner) { event->
+ when (val uiResult = event.peekContent()) {
+ is UIResult.Success -> {
+ capabilities = uiResult.data
+ }
+ is UIResult.Loading -> { }
+ is UIResult.Error -> {
+ Timber.e(uiResult.error, "Failed to retrieve server capabilities")
+ }
+ }
+ }
+
+ collectLatestLifecycleFlow(spaceLinksViewModel.addLinkResultFlow) { event ->
+ event?.peekContent()?.let { uiResult ->
+ when (uiResult) {
+ is UIResult.Loading -> { }
+ is UIResult.Success -> parentFragmentManager.popBackStack()
+ is UIResult.Error -> showErrorInSnackbar(R.string.public_link_add_failed, uiResult.error)
+ }
+ }
+ }
+
+ binding.publicLinkPermissions.apply {
+ canViewPublicLinkRadioButton.setOnClickListener { selectRadioButton(canViewPublicLinkRadioButton) }
+ canViewPublicLinkLayout.setOnClickListener { selectRadioButton(canViewPublicLinkRadioButton) }
+ canEditPublicLinkRadioButton.setOnClickListener { selectRadioButton(canEditPublicLinkRadioButton) }
+ canEditPublicLinkLayout.setOnClickListener { selectRadioButton(canEditPublicLinkRadioButton) }
+ secretFileDropPublicLinkRadioButton.setOnClickListener { selectRadioButton(secretFileDropPublicLinkRadioButton) }
+ secretFileDropPublicLinkLayout.setOnClickListener { selectRadioButton(secretFileDropPublicLinkRadioButton) }
+ }
+
+ binding.passwordLayout.apply {
+ setPasswordButton.setOnClickListener {
+ showPasswordDialog()
+ }
+ removePasswordButton.setOnClickListener {
+ removePassword()
+ }
+ setPasswordSwitch.setOnClickListener {
+ if (setPasswordSwitch.isChecked) showPasswordDialog() else removePassword()
+ }
+ }
+ }
+
+ override fun onCancelPassword() {
+ if (!isPasswordEnforced && !hasPassword) {
+ binding.passwordLayout.setPasswordSwitch.isChecked = false
+ }
+ }
+
+ override fun onSetPassword(password: String) {
+ spaceLinksViewModel.onPasswordSelected(password)
+ }
+
+ private fun selectRadioButton(selectedRadioButton: RadioButton) {
+ binding.publicLinkPermissions.apply {
+ canViewPublicLinkRadioButton.isChecked = false
+ canEditPublicLinkRadioButton.isChecked = false
+ secretFileDropPublicLinkRadioButton.isChecked = false
+ selectedRadioButton.isChecked = true
+ }
+ val selectedPermission = selectedRadioButton.tag as OCLinkType
+ checkPasswordEnforced(selectedPermission)
+ spaceLinksViewModel.onPermissionSelected(selectedPermission)
+ }
+
+ private fun checkPasswordEnforced(selectedPermission: OCLinkType) {
+ isPasswordEnforced = when (selectedPermission) {
+ OCLinkType.CAN_VIEW -> {
+ capabilities?.filesSharingPublicPasswordEnforcedReadOnly == CapabilityBooleanType.TRUE
+ }
+ OCLinkType.CAN_EDIT -> {
+ capabilities?.filesSharingPublicPasswordEnforcedReadWrite == CapabilityBooleanType.TRUE
+ }
+ OCLinkType.CREATE_ONLY -> {
+ capabilities?.filesSharingPublicPasswordEnforcedUploadOnly == CapabilityBooleanType.TRUE
+ }
+ else -> {
+ true
+ }
+ }
+ }
+
+ private fun bindDatePickerDialog(expirationDate: String?) {
+ binding.expirationDateLayout.expirationDateSwitch.setOnCheckedChangeListener { _, isChecked ->
+ if (isChecked) {
+ openDatePickerDialog(expirationDate)
+ } else {
+ binding.expirationDateLayout.expirationDateValue.visibility = View.GONE
+ spaceLinksViewModel.onExpirationDateSelected(null)
+ }
+ }
+ }
+
+ private fun openDatePickerDialog(expirationDate: String?) {
+ val calendar = Calendar.getInstance()
+ val formatter = SimpleDateFormat(DisplayUtils.DATE_FORMAT_ISO, Locale.ROOT).apply {
+ timeZone = TimeZone.getTimeZone("UTC")
+ }
+
+ expirationDate?.let {
+ calendar.time = formatter.parse(it)
+ }
+
+ DatePickerDialog(
+ requireContext(),
+ { _, selectedYear, selectedMonth, selectedDay ->
+ calendar.set(selectedYear, selectedMonth, selectedDay, 23, 59, 59)
+ calendar.set(Calendar.MILLISECOND, 999)
+ val isoExpirationDate = formatter.format(calendar.time)
+ spaceLinksViewModel.onExpirationDateSelected(isoExpirationDate)
+ binding.expirationDateLayout.expirationDateValue.apply {
+ visibility = View.VISIBLE
+ text = DisplayUtils.displayDateToHumanReadable(isoExpirationDate)
+ }
+ },
+ calendar.get(Calendar.YEAR),
+ calendar.get(Calendar.MONTH),
+ calendar.get(Calendar.DAY_OF_MONTH)
+ ).apply {
+ datePicker.minDate = Calendar.getInstance().timeInMillis
+ show()
+ setOnCancelListener {
+ if (expirationDate == null) {
+ binding.expirationDateLayout.expirationDateSwitch.isChecked = false
+ }
+ }
+ }
+ }
+
+ private fun showPasswordDialog(password: String? = null) {
+ val accountName = requireArguments().getString(ARG_ACCOUNT_NAME) ?: return
+ val dialog = SetPasswordDialogFragment.newInstance(accountName, password, this)
+ dialog.show(parentFragmentManager, DIALOG_SET_PASSWORD)
+ }
+
+ private fun removePassword() {
+ spaceLinksViewModel.onPasswordSelected(null)
+ }
+
+ companion object {
+ private const val DIALOG_SET_PASSWORD = "DIALOG_SET_PASSWORD"
+ private const val ARG_ACCOUNT_NAME = "ARG_ACCOUNT_NAME"
+ private const val ARG_CURRENT_SPACE = "ARG_CURRENT_SPACE"
+
+ fun newInstance(
+ accountName: String,
+ currentSpace: OCSpace
+ ): AddPublicLinkFragment {
+ val args = Bundle().apply {
+ putString(ARG_ACCOUNT_NAME, accountName)
+ putParcelable(ARG_CURRENT_SPACE, currentSpace)
+ }
+ return AddPublicLinkFragment().apply {
+ arguments = args
+ }
+ }
+ }
+}
diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/SetPasswordDialogFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/SetPasswordDialogFragment.kt
new file mode 100644
index 00000000000..5a26c972d1a
--- /dev/null
+++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/SetPasswordDialogFragment.kt
@@ -0,0 +1,267 @@
+/**
+ * ownCloud Android client application
+ *
+ * @author Jorge Aguado Recio
+ *
+ * Copyright (C) 2026 ownCloud GmbH.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.owncloud.android.presentation.spaces.links
+
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Context
+import android.content.DialogInterface
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.appcompat.widget.AppCompatButton
+import androidx.core.content.ContextCompat
+import androidx.core.view.isVisible
+import androidx.core.widget.doOnTextChanged
+import androidx.fragment.app.DialogFragment
+import com.owncloud.android.R
+import com.owncloud.android.databinding.SetPasswordDialogBinding
+import com.owncloud.android.domain.capabilities.model.OCCapability
+import com.owncloud.android.presentation.capabilities.CapabilityViewModel
+import com.owncloud.android.presentation.common.UIResult
+import com.owncloud.android.presentation.sharing.generatePassword
+import org.koin.androidx.viewmodel.ext.android.viewModel
+import org.koin.core.parameter.parametersOf
+import timber.log.Timber
+
+class SetPasswordDialogFragment: DialogFragment() {
+ private var _binding: SetPasswordDialogBinding? = null
+ private val binding get() = _binding!!
+
+ private val capabilityViewModel: CapabilityViewModel by viewModel {
+ parametersOf(
+ requireArguments().getString(ARG_ACCOUNT_NAME)
+ )
+ }
+
+ private lateinit var setPasswordListener: SetPasswordListener
+
+ private var passwordPolicy: OCCapability.PasswordPolicy? = null
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ _binding = SetPasswordDialogBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ requireArguments().getString(ARG_PASSWORD)?.let {
+ binding.passwordValue.setText(it)
+ }
+
+ capabilityViewModel.capabilities.observe(viewLifecycleOwner) { event->
+ when (val uiResult = event.peekContent()) {
+ is UIResult.Success -> {
+ passwordPolicy = uiResult.data?.passwordPolicy
+ passwordPolicy?.let {
+ updatePasswordPolicyRequirements(binding.passwordValue.text.toString(), it)
+ }
+ }
+ is UIResult.Loading -> { }
+ is UIResult.Error -> {
+ Timber.e(uiResult.error, "Failed to retrieve server capabilities")
+ }
+ }
+ }
+
+ binding.passwordValue.doOnTextChanged { text, _, _, _ ->
+ passwordPolicy?.let {
+ updatePasswordPolicyRequirements(text.toString(), it)
+ }
+ }
+
+ binding.generatePasswordButton.setOnClickListener {
+ passwordPolicy?.let { passwordPolicy ->
+ binding.passwordValue.setText(
+ generatePassword(
+ minChars = passwordPolicy.minCharacters,
+ maxChars = passwordPolicy.maxCharacters,
+ minDigitsChars = passwordPolicy.minDigits,
+ minLowercaseChars = passwordPolicy.minLowercaseCharacters,
+ minUppercaseChars = passwordPolicy.minUppercaseCharacters,
+ minSpecialChars = passwordPolicy.minSpecialCharacters,
+ )
+ )
+ }
+ }
+
+ binding.copyPasswordButton.setOnClickListener {
+ val clipboard = requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
+ val clip = ClipData.newPlainText("Public link", binding.passwordValue.text.toString())
+ clipboard.setPrimaryClip(clip)
+ }
+
+ binding.cancelPasswordButton.setOnClickListener {
+ setPasswordListener.onCancelPassword()
+ dismiss()
+ }
+
+ binding.setPasswordButton.setOnClickListener {
+ setPasswordListener.onSetPassword(binding.passwordValue.text.toString())
+ dismiss()
+ }
+ }
+
+ override fun onCancel(dialog: DialogInterface) {
+ super.onCancel(dialog)
+ setPasswordListener.onCancelPassword()
+ }
+
+ private fun updatePasswordPolicyRequirements(password: String, passwordPolicy: OCCapability.PasswordPolicy) {
+ var hasMinCharacters = true
+ var hasMaxCharacters = true
+ var hasUpperCase = true
+ var hasLowerCase = true
+ var hasSpecialCharacter = true
+ var hasDigit = true
+
+ passwordPolicy.minCharacters?.let { minCharacters ->
+ if (minCharacters > 0) {
+ hasMinCharacters = password.length >= minCharacters
+ updateRequirement(
+ hasRequirement = hasMinCharacters,
+ layout = binding.passwordPolicyMinCharacters,
+ textView = binding.passwordPolicyMinCharactersText,
+ textViewIcon = binding.passwordPolicyMinCharactersIcon,
+ text = getString(R.string.password_policy_min_characters, passwordPolicy.minCharacters)
+ )
+ }
+ }
+
+ passwordPolicy.maxCharacters?.let { maxCharacters ->
+ if (maxCharacters > 0) {
+ hasMaxCharacters = password.length <= maxCharacters
+ updateRequirement(
+ hasRequirement = hasMaxCharacters,
+ layout = binding.passwordPolicyMaxCharacters,
+ textView = binding.passwordPolicyMaxCharactersText,
+ textViewIcon = binding.passwordPolicyMaxCharactersIcon,
+ text = getString(R.string.password_policy_max_characters, passwordPolicy.maxCharacters)
+ )
+ }
+ }
+
+ passwordPolicy.minUppercaseCharacters?.let { minUppercaseCharacters ->
+ if (minUppercaseCharacters > 0) {
+ hasUpperCase = password.count { it.isUpperCase() } >= minUppercaseCharacters
+ updateRequirement(
+ hasRequirement = hasUpperCase,
+ layout = binding.passwordPolicyUpperCharacters,
+ textView = binding.passwordPolicyUpperCharactersText,
+ textViewIcon = binding.passwordPolicyUpperCharactersIcon,
+ text = getString(R.string.password_policy_uppercase_characters, passwordPolicy.minUppercaseCharacters)
+ )
+ }
+ }
+
+ passwordPolicy.minLowercaseCharacters?.let { minLowercaseCharacters ->
+ if (minLowercaseCharacters > 0) {
+ hasLowerCase = password.count { it.isLowerCase() } >= minLowercaseCharacters
+ updateRequirement(
+ hasRequirement = hasLowerCase,
+ layout = binding.passwordPolicyLowerCaseCharacters,
+ textView = binding.passwordPolicyLowerCaseCharactersText,
+ textViewIcon = binding.passwordPolicyLowerCaseCharactersIcon,
+ text = getString(R.string.password_policy_lowercase_characters, passwordPolicy.minLowercaseCharacters)
+ )
+ }
+ }
+
+ passwordPolicy.minSpecialCharacters?.let { minSpecialCharacters ->
+ if (minSpecialCharacters > 0) {
+ hasSpecialCharacter = password.count { SPECIALS_CHARACTERS.contains(it) } >= minSpecialCharacters
+ updateRequirement(
+ hasRequirement = hasSpecialCharacter,
+ layout = binding.passwordPolicyMinSpecialCharacters,
+ textView = binding.passwordPolicyMinSpecialCharactersText,
+ textViewIcon = binding.passwordPolicyMinSpecialCharactersIcon,
+ text = getString(R.string.password_policy_min_special_character, passwordPolicy.minSpecialCharacters, SPECIALS_CHARACTERS)
+ )
+ }
+ }
+
+ passwordPolicy.minDigits?.let { minDigits ->
+ if (minDigits > 0) {
+ hasDigit = password.count { it.isDigit() } >= minDigits
+ updateRequirement(
+ hasRequirement = hasDigit,
+ layout = binding.passwordPolicyMinDigits,
+ textView = binding.passwordPolicyMinDigitsText,
+ textViewIcon = binding.passwordPolicyMinDigitsIcon,
+ text = getString(R.string.password_policy_min_digits, passwordPolicy.minDigits)
+ )
+ }
+ }
+
+ val allConditionsCheck = hasMinCharacters && hasUpperCase && hasLowerCase && hasDigit && hasSpecialCharacter && hasMaxCharacters
+ enableButton(binding.setPasswordButton, allConditionsCheck)
+ enableButton(binding.copyPasswordButton, allConditionsCheck)
+ }
+
+ private fun updateRequirement(hasRequirement: Boolean, layout: View, textView: TextView, textViewIcon: TextView, text: String) {
+ val textColor = if (hasRequirement) R.color.success else R.color.warning
+ val drawable = if (hasRequirement) R.drawable.ic_check_password_policy else R.drawable.ic_cross_warning_password_policy
+
+ layout.isVisible = true
+ textView.apply {
+ setText(text)
+ setTextColor(ContextCompat.getColor(context, textColor))
+ }
+ textViewIcon.setCompoundDrawablesRelativeWithIntrinsicBounds(drawable, 0, 0, 0)
+ }
+
+ private fun enableButton(button: AppCompatButton, enable: Boolean) {
+ val textColor = if (enable) R.color.primary_button_background_color else R.color.grey
+ button.apply {
+ isEnabled = enable
+ setTextColor(ContextCompat.getColor(context, textColor))
+ }
+ }
+
+ interface SetPasswordListener {
+ fun onCancelPassword()
+ fun onSetPassword(password: String)
+ }
+
+ companion object {
+ private const val ARG_ACCOUNT_NAME = "ARG_ACCOUNT_NAME"
+ private const val ARG_PASSWORD = "ARG_PASSWORD"
+ private const val SPECIALS_CHARACTERS = "!#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
+
+ fun newInstance(
+ accountName: String,
+ password: String?,
+ listener: SetPasswordListener
+ ): SetPasswordDialogFragment {
+ val args = Bundle().apply {
+ putString(ARG_ACCOUNT_NAME, accountName)
+ putString(ARG_PASSWORD, password)
+ }
+ return SetPasswordDialogFragment().apply {
+ setPasswordListener = listener
+ arguments = args
+ }
+ }
+ }
+}
diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/SpaceLinksViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/SpaceLinksViewModel.kt
new file mode 100644
index 00000000000..96216255feb
--- /dev/null
+++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/SpaceLinksViewModel.kt
@@ -0,0 +1,85 @@
+/**
+ * ownCloud Android client application
+ *
+ * @author Jorge Aguado Recio
+ *
+ * Copyright (C) 2026 ownCloud GmbH.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.owncloud.android.presentation.spaces.links
+
+import androidx.lifecycle.ViewModel
+import com.owncloud.android.domain.links.model.OCLinkType
+import com.owncloud.android.domain.links.usecases.AddLinkUseCase
+import com.owncloud.android.domain.spaces.model.OCSpace
+import com.owncloud.android.domain.utils.Event
+import com.owncloud.android.extensions.ViewModelExt.runUseCaseWithResult
+import com.owncloud.android.presentation.common.UIResult
+import com.owncloud.android.providers.CoroutinesDispatcherProvider
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.update
+
+class SpaceLinksViewModel(
+ private val addLinkUseCase: AddLinkUseCase,
+ private val accountName: String,
+ private val space: OCSpace,
+ private val coroutineDispatcherProvider: CoroutinesDispatcherProvider
+): ViewModel() {
+
+ private val _addPublicLinkUIState = MutableStateFlow(null)
+ val addPublicLinkUIState: StateFlow = _addPublicLinkUIState
+
+ private val _addLinkResultFlow = MutableStateFlow>?>(null)
+ val addLinkResultFlow: StateFlow>?> = _addLinkResultFlow
+
+ init {
+ _addPublicLinkUIState.value = AddPublicLinkUIState()
+ }
+
+ fun onPermissionSelected(permission: OCLinkType) {
+ _addPublicLinkUIState.update { it?.copy(selectedPermission = permission) }
+ }
+
+ fun onExpirationDateSelected(expirationDate: String?) {
+ _addPublicLinkUIState.update { it?.copy(selectedExpirationDate = expirationDate) }
+ }
+
+ fun onPasswordSelected(password: String?) {
+ _addPublicLinkUIState.update { it?.copy(selectedPassword = password) }
+ }
+
+ fun createPublicLink(displayName: String, permission: OCLinkType, expirationDate: String?, password: String?) {
+ runUseCaseWithResult(
+ coroutineDispatcher = coroutineDispatcherProvider.io,
+ flow = _addLinkResultFlow,
+ useCase = addLinkUseCase,
+ useCaseParams = AddLinkUseCase.Params(
+ accountName = accountName,
+ spaceId = space.id,
+ displayName = displayName,
+ type = OCLinkType.toString(permission),
+ expirationDate = expirationDate,
+ password = password
+ )
+ )
+ }
+
+ data class AddPublicLinkUIState(
+ val selectedPermission: OCLinkType? = null,
+ val selectedExpirationDate: String? = null,
+ val selectedPassword: String? = null
+ )
+}
diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersActivity.kt
index 5ef7e74d270..fb2fe90acbc 100644
--- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersActivity.kt
+++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersActivity.kt
@@ -32,6 +32,7 @@ import com.owncloud.android.domain.roles.model.OCRole
import com.owncloud.android.domain.spaces.model.OCSpace
import com.owncloud.android.domain.spaces.model.SpaceMember
import com.owncloud.android.presentation.common.ShareSheetHelper
+import com.owncloud.android.presentation.spaces.links.AddPublicLinkFragment
import com.owncloud.android.ui.activity.FileActivity
import com.owncloud.android.utils.DisplayUtils
@@ -100,6 +101,16 @@ class SpaceMembersActivity: FileActivity(), SpaceMembersFragment.SpaceMemberFrag
}
}
+ override fun addPublicLink(space: OCSpace) {
+ val addPublicLinkFragment = AddPublicLinkFragment.newInstance(account.name, space)
+ val transaction = supportFragmentManager.beginTransaction()
+ transaction.apply {
+ replace(R.id.members_fragment_container, addPublicLinkFragment, TAG_ADD_PUBLIC_LINK_FRAGMENT)
+ addToBackStack(null)
+ commit()
+ }
+ }
+
override fun copyOrSendPublicLink(publicLinkUrl: String, spaceName: String) {
copyOrSendLink(publicLinkUrl, spaceName)
}
@@ -127,6 +138,7 @@ class SpaceMembersActivity: FileActivity(), SpaceMembersFragment.SpaceMemberFrag
companion object {
private const val TAG_SPACE_MEMBERS_FRAGMENT = "SPACE_MEMBERS_FRAGMENT"
private const val TAG_ADD_MEMBER_FRAGMENT ="ADD_MEMBER_FRAGMENT"
+ private const val TAG_ADD_PUBLIC_LINK_FRAGMENT = "ADD_PUBLIC_LINK_FRAGMENT"
private const val TYPE_PLAIN = "text/plain"
private const val KEY_DISPLAY_NAME = "oc_display_name"
private const val KEY_UUID = "oc_uuid"
diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersFragment.kt
index 4ea5d250ba7..d907dd17110 100644
--- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersFragment.kt
+++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersFragment.kt
@@ -42,6 +42,7 @@ import com.owncloud.android.extensions.showErrorInSnackbar
import com.owncloud.android.extensions.showMessageInSnackbar
import com.owncloud.android.presentation.common.UIResult
import com.owncloud.android.presentation.spaces.links.SpaceLinksAdapter
+import com.owncloud.android.presentation.spaces.links.SpaceLinksViewModel
import com.owncloud.android.utils.DisplayUtils
import org.koin.androidx.viewmodel.ext.android.activityViewModel
import org.koin.core.parameter.parametersOf
@@ -61,6 +62,12 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter
requireArguments().getParcelable(ARG_CURRENT_SPACE)
)
}
+ private val spaceLinksViewModel: SpaceLinksViewModel by activityViewModel {
+ parametersOf(
+ requireArguments().getString(ARG_ACCOUNT_NAME),
+ requireArguments().getParcelable(ARG_CURRENT_SPACE)
+ )
+ }
private lateinit var spaceMembersAdapter: SpaceMembersAdapter
private lateinit var spaceLinksAdapter: SpaceLinksAdapter
@@ -112,6 +119,10 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter
selectedMember = null
)
}
+
+ binding.addPublicLinkButton.setOnClickListener {
+ listener?.addPublicLink(currentSpace)
+ }
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
@@ -166,6 +177,16 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter
}
private fun subscribeToViewModels() {
+ observeRoles()
+ observeSpaceMembers()
+ observeSpacePermissions()
+ observeAddMemberResult()
+ observeRemoveMemberResult()
+ observeEditMemberResult()
+ observeAddLinkResult()
+ }
+
+ private fun observeRoles() {
collectLatestLifecycleFlow(spaceMembersViewModel.roles) { event ->
event?.let {
when (val uiResult = event.peekContent()) {
@@ -182,7 +203,9 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter
}
}
}
+ }
+ private fun observeSpaceMembers() {
collectLatestLifecycleFlow(spaceMembersViewModel.spaceMembers) { event ->
event?.let {
when (val uiResult = event.peekContent()) {
@@ -209,15 +232,15 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter
}
}
}
+ }
+ private fun observeSpacePermissions() {
collectLatestLifecycleFlow(spaceMembersViewModel.spacePermissions) { event ->
event?.let {
when (val uiResult = event.peekContent()) {
is UIResult.Success -> {
uiResult.data?.let { spacePermissions ->
- binding.addMemberButton.isVisible = DRIVES_CREATE_PERMISSION in spacePermissions
- canRemoveMembers = DRIVES_DELETE_PERMISSION in spacePermissions
- canEditMembers = DRIVES_UPDATE_PERMISSION in spacePermissions
+ checkPermissions(spacePermissions)
spaceMembersAdapter.setSpaceMembers(spaceMembers, roles, canRemoveMembers, canEditMembers, numberOfManagers)
}
}
@@ -228,7 +251,9 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter
}
}
}
+ }
+ private fun observeAddMemberResult() {
collectLatestLifecycleFlow(spaceMembersViewModel.addMemberResultFlow) { event ->
event?.peekContent()?.let { uiResult ->
when (uiResult) {
@@ -241,7 +266,9 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter
}
}
}
+ }
+ private fun observeRemoveMemberResult() {
collectLatestLifecycleFlow(spaceMembersViewModel.removeMemberResultFlow) { uiResult ->
when (uiResult) {
is UIResult.Loading -> { }
@@ -255,7 +282,9 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter
}
}
}
+ }
+ private fun observeEditMemberResult() {
collectLatestLifecycleFlow(spaceMembersViewModel.editMemberResultFlow) { event ->
event?.peekContent()?.let { uiResult ->
when (uiResult) {
@@ -270,6 +299,28 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter
}
}
+ private fun observeAddLinkResult() {
+ collectLatestLifecycleFlow(spaceLinksViewModel.addLinkResultFlow) { event ->
+ event?.peekContent()?.let { uiResult ->
+ when (uiResult) {
+ is UIResult.Loading -> { }
+ is UIResult.Success -> showMessageInSnackbar(getString(R.string.public_link_add_correctly))
+ is UIResult.Error -> { }
+ }
+ }
+ }
+ }
+
+ private fun checkPermissions(spacePermissions: List) {
+ binding.apply {
+ val hasCreatePermission = DRIVES_CREATE_PERMISSION in spacePermissions
+ addMemberButton.isVisible = hasCreatePermission
+ addPublicLinkButton.isVisible = hasCreatePermission
+ }
+ canRemoveMembers = DRIVES_DELETE_PERMISSION in spacePermissions
+ canEditMembers = DRIVES_UPDATE_PERMISSION in spacePermissions
+ }
+
private fun showOrHideEmptyView(hasLinks: Boolean) {
binding.apply {
publicLinksRecyclerView.isVisible = hasLinks
@@ -292,6 +343,7 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter
interface SpaceMemberFragmentListener {
fun addMember(space: OCSpace, spaceMembers: List, roles: List, editMode: Boolean, selectedMember: SpaceMember?)
+ fun addPublicLink(space: OCSpace)
fun copyOrSendPublicLink(publicLinkUrl: String, spaceName: String)
}
diff --git a/owncloudApp/src/main/res/drawable/ic_secret_file_drop.xml b/owncloudApp/src/main/res/drawable/ic_secret_file_drop.xml
new file mode 100644
index 00000000000..f5a69e75311
--- /dev/null
+++ b/owncloudApp/src/main/res/drawable/ic_secret_file_drop.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/owncloudApp/src/main/res/layout/add_member_fragment.xml b/owncloudApp/src/main/res/layout/add_member_fragment.xml
index 60f662286f5..b4326bcfca8 100644
--- a/owncloudApp/src/main/res/layout/add_member_fragment.xml
+++ b/owncloudApp/src/main/res/layout/add_member_fragment.xml
@@ -164,7 +164,7 @@
+ layout="@layout/expiration_date_layout" />
diff --git a/owncloudApp/src/main/res/layout/add_public_link_fragment.xml b/owncloudApp/src/main/res/layout/add_public_link_fragment.xml
new file mode 100644
index 00000000000..eed8912a50f
--- /dev/null
+++ b/owncloudApp/src/main/res/layout/add_public_link_fragment.xml
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/owncloudApp/src/main/res/layout/expiration_date_item.xml b/owncloudApp/src/main/res/layout/expiration_date_layout.xml
similarity index 98%
rename from owncloudApp/src/main/res/layout/expiration_date_item.xml
rename to owncloudApp/src/main/res/layout/expiration_date_layout.xml
index a6cbd2058ee..c17c486945d 100644
--- a/owncloudApp/src/main/res/layout/expiration_date_item.xml
+++ b/owncloudApp/src/main/res/layout/expiration_date_layout.xml
@@ -78,7 +78,7 @@
android:id="@+id/expiration_date_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/standard_quarter_margin"
+ android:layout_marginEnd="@dimen/standard_margin"
android:contentDescription="@string/content_description_expiration_date_switch"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/owncloudApp/src/main/res/layout/members_fragment.xml b/owncloudApp/src/main/res/layout/members_fragment.xml
index 9331537adf5..f56c56796d8 100644
--- a/owncloudApp/src/main/res/layout/members_fragment.xml
+++ b/owncloudApp/src/main/res/layout/members_fragment.xml
@@ -103,6 +103,21 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/owncloudApp/src/main/res/layout/public_link_permissions.xml b/owncloudApp/src/main/res/layout/public_link_permissions.xml
new file mode 100644
index 00000000000..7374c84daad
--- /dev/null
+++ b/owncloudApp/src/main/res/layout/public_link_permissions.xml
@@ -0,0 +1,211 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/owncloudApp/src/main/res/layout/set_password_dialog.xml b/owncloudApp/src/main/res/layout/set_password_dialog.xml
new file mode 100644
index 00000000000..807865c2fa9
--- /dev/null
+++ b/owncloudApp/src/main/res/layout/set_password_dialog.xml
@@ -0,0 +1,267 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/owncloudApp/src/main/res/values-de-rDE/strings.xml b/owncloudApp/src/main/res/values-de-rDE/strings.xml
index 2119cd9e966..8f1c6d1f67b 100644
--- a/owncloudApp/src/main/res/values-de-rDE/strings.xml
+++ b/owncloudApp/src/main/res/values-de-rDE/strings.xml
@@ -824,8 +824,6 @@
%1$s Space Name
%1$s Space Menü
%1$s Space Untertitel
- Erstellen-Schaltfläche
- Abbruch-Schaltfläche
Space Kontingent
Benutzer %1$s
Gruppe %1$s
diff --git a/owncloudApp/src/main/res/values-de/strings.xml b/owncloudApp/src/main/res/values-de/strings.xml
index 66363f5ec8f..f28122f4103 100644
--- a/owncloudApp/src/main/res/values-de/strings.xml
+++ b/owncloudApp/src/main/res/values-de/strings.xml
@@ -824,8 +824,6 @@
%1$s Space Name
%1$s Space Menü
%1$s Space Untertitel
- Erstellen-Schaltfläche
- Abbruch-Schaltfläche
Space Kontingent
Benutzer %1$s
Gruppe %1$s
diff --git a/owncloudApp/src/main/res/values-en-rGB/strings.xml b/owncloudApp/src/main/res/values-en-rGB/strings.xml
index c603b2d70b6..94877c9c575 100644
--- a/owncloudApp/src/main/res/values-en-rGB/strings.xml
+++ b/owncloudApp/src/main/res/values-en-rGB/strings.xml
@@ -807,8 +807,6 @@
%1$s space name
%1$s space menu
%1$s space subtitle
- Create button
- Cancel button
Space quota
User %1$s
Group %1$s
diff --git a/owncloudApp/src/main/res/values-en-rUS/strings.xml b/owncloudApp/src/main/res/values-en-rUS/strings.xml
index 60ef10ce182..92a1db28f3c 100644
--- a/owncloudApp/src/main/res/values-en-rUS/strings.xml
+++ b/owncloudApp/src/main/res/values-en-rUS/strings.xml
@@ -807,8 +807,6 @@
%1$s space name
%1$s space menu
%1$s space subtitle
- Create button
- Cancel button
Space quota
User %1$s
Group %1$s
diff --git a/owncloudApp/src/main/res/values-es/strings.xml b/owncloudApp/src/main/res/values-es/strings.xml
index 2fbcabbf742..e3cc74e6fcc 100644
--- a/owncloudApp/src/main/res/values-es/strings.xml
+++ b/owncloudApp/src/main/res/values-es/strings.xml
@@ -825,8 +825,6 @@
%1$s nombre del space
%1$s menu del space
%1$s subtitulo del space
- Botón Crear
- Botón Cancelar
Cuota del space
Usuario %1$s
Grupo %1$s
diff --git a/owncloudApp/src/main/res/values-et-rEE/strings.xml b/owncloudApp/src/main/res/values-et-rEE/strings.xml
index 196ccd1be5d..a7912856594 100644
--- a/owncloudApp/src/main/res/values-et-rEE/strings.xml
+++ b/owncloudApp/src/main/res/values-et-rEE/strings.xml
@@ -805,8 +805,6 @@
%1$s ruumi nimi
%1$s ruumi menüü
%1$s ruumi alapealkiri
- Loomise nupp
- Tühistamise nupp
Ruumi mahupiirang
Loo otsetee
URL
diff --git a/owncloudApp/src/main/res/values-ja-rJP/strings.xml b/owncloudApp/src/main/res/values-ja-rJP/strings.xml
index 02008b9d24d..527813edf65 100644
--- a/owncloudApp/src/main/res/values-ja-rJP/strings.xml
+++ b/owncloudApp/src/main/res/values-ja-rJP/strings.xml
@@ -806,8 +806,6 @@
%1$sスペース名
%1$sスペースメニュー
%1$sスペースサブタイトル
- 作成ボタン
- キャンセルボタン
スペース割り当て
ユーザー %1$s
グループ %1$s
diff --git a/owncloudApp/src/main/res/values-lo/strings.xml b/owncloudApp/src/main/res/values-lo/strings.xml
index 4d88150ab5a..501a177ba48 100644
--- a/owncloudApp/src/main/res/values-lo/strings.xml
+++ b/owncloudApp/src/main/res/values-lo/strings.xml
@@ -801,8 +801,6 @@
ຊື່ພື້ນທີ່
ການດຳເນີນການກັບພື້ນທີ່
ລາຍລະອຽດພື້ນທີ່
- ສ້າງ
- ຍົກເລີກ
ຕັ້ງຄ່າໂຄຕ້າ
ສ້າງທາງລັດ
URL
diff --git a/owncloudApp/src/main/res/values-pt-rBR/strings.xml b/owncloudApp/src/main/res/values-pt-rBR/strings.xml
index 6752e657001..8c0bb7c8fd8 100644
--- a/owncloudApp/src/main/res/values-pt-rBR/strings.xml
+++ b/owncloudApp/src/main/res/values-pt-rBR/strings.xml
@@ -806,8 +806,6 @@
%1$s nome do espaço
%1$s menu espacial
%1$s legenda espacial
- Botão Criar
- Botão Cancelar
Cota de espaço
Crie um atalho
URL
diff --git a/owncloudApp/src/main/res/values-pt-rPT/strings.xml b/owncloudApp/src/main/res/values-pt-rPT/strings.xml
index b9d2811e6c5..c3dadbd4148 100644
--- a/owncloudApp/src/main/res/values-pt-rPT/strings.xml
+++ b/owncloudApp/src/main/res/values-pt-rPT/strings.xml
@@ -806,8 +806,6 @@
%1$s nome do espaço
%1$s menu espacial
%1$s legenda espacial
- Botão Criar
- Botão Cancelar
Cota de espaço
Crie um atalho
URL
diff --git a/owncloudApp/src/main/res/values-sq/strings.xml b/owncloudApp/src/main/res/values-sq/strings.xml
index 78efc58bdc3..bcc6462dd79 100644
--- a/owncloudApp/src/main/res/values-sq/strings.xml
+++ b/owncloudApp/src/main/res/values-sq/strings.xml
@@ -806,8 +806,6 @@
Emër hapësire %1$s
Menu hapësire %1$s
Nëntitull hapësire %1$s
- Buton krijimi
- Buton anulimi
Kuota hapësire
Përdorues %1$s
Grup %1$s
diff --git a/owncloudApp/src/main/res/values/strings.xml b/owncloudApp/src/main/res/values/strings.xml
index 6ffef0c72ce..d95fa82bc53 100644
--- a/owncloudApp/src/main/res/values/strings.xml
+++ b/owncloudApp/src/main/res/values/strings.xml
@@ -218,6 +218,7 @@
unknown error
Pending
Important
+ Set
Change password
Remove account
Create account
@@ -827,8 +828,9 @@
%1$s space name
%1$s space menu
%1$s space subtitle
- Create button
- Cancel button
+ Create
+ Cancel
+ Set
Space quota
User %1$s
Group %1$s
@@ -839,6 +841,11 @@
Remove member %1$s
Edit member %1$s
Get permanent link
+ Create public link
+ Set password
+ Remove password
+ Generate password
+ Copy password
Create a shortcut
URL
@@ -905,11 +912,20 @@
User
Group
(me)
+
+ Create public link
+ Set password
Can view
+ View, download
Can edit
+ View, upload, edit, download, delete
Can upload
Secret file drop
+ Upload only, existing content is not revealed
Invited people
+ Unnamed Link
+ Public link created correctly
+ Public link could not be created
forum or contribute in our GitHub repo]]>
diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/AddRemoteLinkOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/AddRemoteLinkOperation.kt
new file mode 100644
index 00000000000..4357466c526
--- /dev/null
+++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/AddRemoteLinkOperation.kt
@@ -0,0 +1,88 @@
+/**
+ * ownCloud Android client application
+ *
+ * @author Jorge Aguado Recio
+ *
+ * Copyright (C) 2026 ownCloud GmbH.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.owncloud.android.lib.resources.links
+
+import com.owncloud.android.lib.common.OwnCloudClient
+import com.owncloud.android.lib.common.http.HttpConstants
+import com.owncloud.android.lib.common.http.HttpConstants.CONTENT_TYPE_JSON
+import com.owncloud.android.lib.common.http.methods.nonwebdav.PostMethod
+import com.owncloud.android.lib.common.operations.RemoteOperation
+import com.owncloud.android.lib.common.operations.RemoteOperationResult
+import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.RequestBody.Companion.toRequestBody
+import org.json.JSONObject
+import timber.log.Timber
+import java.net.URL
+
+class AddRemoteLinkOperation(
+ private val spaceId: String,
+ private val displayName: String,
+ private val type: String,
+ private val expirationDate: String?,
+ private val password: String?
+): RemoteOperation() {
+ override fun run(client: OwnCloudClient): RemoteOperationResult {
+ var result: RemoteOperationResult
+ try {
+ val uriBuilder = client.baseUri.buildUpon().apply {
+ appendEncodedPath(GRAPH_API_DRIVES_PATH)
+ appendEncodedPath(spaceId)
+ appendEncodedPath(GRAPH_API_CREATE_LINK_PATH)
+ }
+
+ val requestBody = JSONObject().apply {
+ put(DISPLAY_NAME_BODY_PARAM, displayName)
+ expirationDate?.let { put(EXPIRATION_DATE_BODY_PARAM, it) }
+ password?.let { put(PASSWORD_BODY_PARAM, it) }
+ put(TYPE_BODY_PARAM, type)
+ }.toString().toRequestBody(CONTENT_TYPE_JSON.toMediaType())
+
+ val postMethod = PostMethod(URL(uriBuilder.build().toString()), requestBody)
+
+ val status = client.executeHttpMethod(postMethod)
+
+ val response = postMethod.getResponseBodyAsString()
+
+ if (status == HttpConstants.HTTP_OK) {
+ Timber.d("Successful response: $response")
+ result = RemoteOperationResult(ResultCode.OK)
+ Timber.d("Add a public link operation completed and parsed to ${result.data}")
+ } else {
+ result = RemoteOperationResult(postMethod)
+ Timber.e("Failed response while adding a public link; status code: $status, response: $response")
+ }
+ } catch (e: Exception) {
+ result = RemoteOperationResult(e)
+ Timber.e(e, "Exception while adding a public link")
+ }
+ return result
+ }
+
+ companion object {
+ private const val GRAPH_API_DRIVES_PATH = "graph/v1beta1/drives/"
+ private const val GRAPH_API_CREATE_LINK_PATH = "root/createLink"
+ private const val DISPLAY_NAME_BODY_PARAM = "displayName"
+ private const val EXPIRATION_DATE_BODY_PARAM = "expirationDateTime"
+ private const val PASSWORD_BODY_PARAM = "password"
+ private const val TYPE_BODY_PARAM = "type"
+ }
+}
diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/services/LinksService.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/services/LinksService.kt
new file mode 100644
index 00000000000..f88face3482
--- /dev/null
+++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/services/LinksService.kt
@@ -0,0 +1,28 @@
+/**
+ * ownCloud Android client application
+ *
+ * @author Jorge Aguado Recio
+ *
+ * Copyright (C) 2026 ownCloud GmbH.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.owncloud.android.lib.resources.links.services
+
+import com.owncloud.android.lib.common.operations.RemoteOperationResult
+import com.owncloud.android.lib.resources.Service
+
+interface LinksService: Service {
+ fun addLink(spaceId: String, displayName: String, type: String, expirationDate: String?, password: String?): RemoteOperationResult
+}
diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/services/OCLinksService.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/services/OCLinksService.kt
new file mode 100644
index 00000000000..15fdf5207a7
--- /dev/null
+++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/services/OCLinksService.kt
@@ -0,0 +1,43 @@
+/**
+ * ownCloud Android client application
+ *
+ * @author Jorge Aguado Recio
+ *
+ * Copyright (C) 2026 ownCloud GmbH.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.owncloud.android.lib.resources.links.services
+
+import com.owncloud.android.lib.common.OwnCloudClient
+import com.owncloud.android.lib.common.operations.RemoteOperationResult
+import com.owncloud.android.lib.resources.links.AddRemoteLinkOperation
+
+class OCLinksService(override val client: OwnCloudClient) : LinksService {
+
+ override fun addLink(
+ spaceId: String,
+ displayName: String,
+ type: String,
+ expirationDate: String?,
+ password: String?
+ ): RemoteOperationResult =
+ AddRemoteLinkOperation(
+ spaceId = spaceId,
+ displayName = displayName,
+ type = type,
+ expirationDate = expirationDate,
+ password = password
+ ).execute(client)
+}
diff --git a/owncloudData/src/main/java/com/owncloud/android/data/ClientManager.kt b/owncloudData/src/main/java/com/owncloud/android/data/ClientManager.kt
index b4681c050c8..e0fa7997e96 100644
--- a/owncloudData/src/main/java/com/owncloud/android/data/ClientManager.kt
+++ b/owncloudData/src/main/java/com/owncloud/android/data/ClientManager.kt
@@ -36,6 +36,8 @@ import com.owncloud.android.lib.resources.appregistry.services.AppRegistryServic
import com.owncloud.android.lib.resources.appregistry.services.OCAppRegistryService
import com.owncloud.android.lib.resources.files.services.FileService
import com.owncloud.android.lib.resources.files.services.implementation.OCFileService
+import com.owncloud.android.lib.resources.links.services.LinksService
+import com.owncloud.android.lib.resources.links.services.OCLinksService
import com.owncloud.android.lib.resources.members.services.MembersService
import com.owncloud.android.lib.resources.members.services.OCMembersService
import com.owncloud.android.lib.resources.roles.services.OCRolesService
@@ -176,4 +178,9 @@ class ClientManager(
val ownCloudClient = getClientForAccount(accountName)
return OCMembersService(client = ownCloudClient)
}
+
+ fun getLinksService(accountName: String): LinksService {
+ val ownCloudClient = getClientForAccount(accountName)
+ return OCLinksService(client = ownCloudClient)
+ }
}
diff --git a/owncloudData/src/main/java/com/owncloud/android/data/links/datasources/RemoteLinksDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/links/datasources/RemoteLinksDataSource.kt
new file mode 100644
index 00000000000..d3e8ef88577
--- /dev/null
+++ b/owncloudData/src/main/java/com/owncloud/android/data/links/datasources/RemoteLinksDataSource.kt
@@ -0,0 +1,25 @@
+/**
+ * ownCloud Android client application
+ *
+ * @author Jorge Aguado Recio
+ *
+ * Copyright (C) 2026 ownCloud GmbH.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.owncloud.android.data.links.datasources
+
+interface RemoteLinksDataSource {
+ fun addLink(accountName: String, spaceId: String, displayName: String, type: String, expirationDate: String?, password: String?)
+}
diff --git a/owncloudData/src/main/java/com/owncloud/android/data/links/datasources/implementation/OCRemoteLinksDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/links/datasources/implementation/OCRemoteLinksDataSource.kt
new file mode 100644
index 00000000000..f038a93e883
--- /dev/null
+++ b/owncloudData/src/main/java/com/owncloud/android/data/links/datasources/implementation/OCRemoteLinksDataSource.kt
@@ -0,0 +1,34 @@
+/**
+ * ownCloud Android client application
+ *
+ * @author Jorge Aguado Recio
+ *
+ * Copyright (C) 2026 ownCloud GmbH.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.owncloud.android.data.links.datasources.implementation
+
+import com.owncloud.android.data.ClientManager
+import com.owncloud.android.data.executeRemoteOperation
+import com.owncloud.android.data.links.datasources.RemoteLinksDataSource
+
+class OCRemoteLinksDataSource(
+ private val clientManager: ClientManager
+): RemoteLinksDataSource {
+
+ override fun addLink(accountName: String, spaceId: String, displayName: String, type: String, expirationDate: String?, password: String?) {
+ executeRemoteOperation { clientManager.getLinksService(accountName).addLink(spaceId, displayName, type, expirationDate, password) }
+ }
+}
diff --git a/owncloudData/src/main/java/com/owncloud/android/data/links/repository/OCLinksRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/links/repository/OCLinksRepository.kt
new file mode 100644
index 00000000000..0cea80e5dae
--- /dev/null
+++ b/owncloudData/src/main/java/com/owncloud/android/data/links/repository/OCLinksRepository.kt
@@ -0,0 +1,33 @@
+/**
+ * ownCloud Android client application
+ *
+ * @author Jorge Aguado Recio
+ *
+ * Copyright (C) 2026 ownCloud GmbH.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.owncloud.android.data.links.repository
+
+import com.owncloud.android.data.links.datasources.RemoteLinksDataSource
+import com.owncloud.android.domain.links.LinksRepository
+
+class OCLinksRepository(
+ private val remoteLinksDataSource: RemoteLinksDataSource
+): LinksRepository {
+
+ override fun addLink(accountName: String, spaceId: String, displayName: String, type: String, expirationDate: String?, password: String?) {
+ remoteLinksDataSource.addLink(accountName, spaceId, displayName, type, expirationDate, password)
+ }
+}
diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/links/LinksRepository.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/links/LinksRepository.kt
new file mode 100644
index 00000000000..8ff4a8cca9f
--- /dev/null
+++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/links/LinksRepository.kt
@@ -0,0 +1,25 @@
+/**
+ * ownCloud Android client application
+ *
+ * @author Jorge Aguado Recio
+ *
+ * Copyright (C) 2026 ownCloud GmbH.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.owncloud.android.domain.links
+
+interface LinksRepository {
+ fun addLink(accountName: String, spaceId: String, displayName: String, type: String, expirationDate: String?, password: String?)
+}
diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/links/model/OCLink.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/links/model/OCLink.kt
index a44e1061565..7cfa5d7b881 100644
--- a/owncloudDomain/src/main/java/com/owncloud/android/domain/links/model/OCLink.kt
+++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/links/model/OCLink.kt
@@ -38,13 +38,28 @@ enum class OCLinkType {
CAN_VIEW, CAN_EDIT, CAN_UPLOAD, CREATE_ONLY, INTERNAL;
companion object {
+ private const val CAN_VIEW_TYPE_STRING = "view"
+ private const val CAN_EDIT_TYPE_STRING = "edit"
+ private const val CAN_UPLOAD_TYPE_STRING = "upload"
+ private const val CREATE_ONLY_TYPE_STRING = "createOnly"
+ private const val INTERNAL_TYPE_STRING = "internal"
+
fun parseFromString(type: String): OCLinkType =
when (type) {
- "view" -> CAN_VIEW
- "edit" -> CAN_EDIT
- "upload" -> CAN_UPLOAD
- "createOnly" -> CREATE_ONLY
+ CAN_VIEW_TYPE_STRING -> CAN_VIEW
+ CAN_EDIT_TYPE_STRING -> CAN_EDIT
+ CAN_UPLOAD_TYPE_STRING -> CAN_UPLOAD
+ CREATE_ONLY_TYPE_STRING -> CREATE_ONLY
else -> INTERNAL
}
+
+ fun toString(type: OCLinkType): String =
+ when (type) {
+ CAN_VIEW -> CAN_VIEW_TYPE_STRING
+ CAN_EDIT -> CAN_EDIT_TYPE_STRING
+ CAN_UPLOAD -> CAN_UPLOAD_TYPE_STRING
+ CREATE_ONLY -> CREATE_ONLY_TYPE_STRING
+ INTERNAL -> INTERNAL_TYPE_STRING
+ }
}
}
diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/links/usecases/AddLinkUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/links/usecases/AddLinkUseCase.kt
new file mode 100644
index 00000000000..08a552c407d
--- /dev/null
+++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/links/usecases/AddLinkUseCase.kt
@@ -0,0 +1,42 @@
+/**
+ * ownCloud Android client application
+ *
+ * @author Jorge Aguado Recio
+ *
+ * Copyright (C) 2026 ownCloud GmbH.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.owncloud.android.domain.links.usecases
+
+import com.owncloud.android.domain.BaseUseCaseWithResult
+import com.owncloud.android.domain.links.LinksRepository
+
+class AddLinkUseCase(
+ private val linksRepository: LinksRepository
+): BaseUseCaseWithResult() {
+
+ override fun run(params: Params) {
+ linksRepository.addLink(params.accountName, params.spaceId, params.displayName, params.type, params.expirationDate, params.password)
+ }
+
+ data class Params(
+ val accountName: String,
+ val spaceId: String,
+ val displayName: String,
+ val type: String,
+ val expirationDate: String?,
+ val password: String?
+ )
+}