Skip to content

Commit

Permalink
Merge pull request #4138 from owncloud/feature/apply_to_all_when_many…
Browse files Browse the repository at this point in the history
…_name_conflicts_arise

[FEATURE REQUEST] "Apply to all" when many name conflicts arise
  • Loading branch information
Aitorbp authored Sep 13, 2023
2 parents df2d0e6 + 706e619 commit bf9e477
Show file tree
Hide file tree
Showing 7 changed files with 347 additions and 37 deletions.
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
Changelog for ownCloud Android Client [unreleased] (UNRELEASED)
=======================================
The following sections list the changes in ownCloud Android Client unreleased relevant to
ownCloud admins and users.

[unreleased]: https://github.com/owncloud/android/compare/v4.1.0...master

Summary
-------

* Enhancement - "Apply to all" when many name conflicts arise: [#4078](https://github.com/owncloud/android/issues/4078)

Details
-------

* Enhancement - "Apply to all" when many name conflicts arise: [#4078](https://github.com/owncloud/android/issues/4078)

A new dialog has been created where a checkbox has been added to be able to select all the folders
or files that have conflicts.

https://github.com/owncloud/android/issues/4078
https://github.com/owncloud/android/pull/4138

Changelog for ownCloud Android Client [4.1.0] (2023-08-23)
=======================================
The following sections list the changes in ownCloud Android Client 4.1.0 relevant to
Expand Down
6 changes: 6 additions & 0 deletions changelog/unreleased/4138
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Enhancement: "Apply to all" when many name conflicts arise

A new dialog has been created where a checkbox has been added to be able to select all the folders or files that have conflicts.

https://github.com/owncloud/android/issues/4078
https://github.com/owncloud/android/pull/4138
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import com.owncloud.android.extensions.ViewModelExt.runUseCaseWithResult
import com.owncloud.android.presentation.common.UIResult
import com.owncloud.android.providers.ContextProvider
import com.owncloud.android.providers.CoroutinesDispatcherProvider
import com.owncloud.android.ui.dialog.FileAlreadyExistsDialog
import com.owncloud.android.usecases.synchronization.SynchronizeFileUseCase
import com.owncloud.android.usecases.synchronization.SynchronizeFolderUseCase
import kotlinx.coroutines.flow.MutableStateFlow
Expand Down Expand Up @@ -92,6 +93,9 @@ class FileOperationsViewModel(
private val _createFileWithAppProviderFlow = MutableStateFlow<Event<UIResult<String>>?>(null)
val createFileWithAppProviderFlow: StateFlow<Event<UIResult<String>>?> = _createFileWithAppProviderFlow

val openDialogs = mutableListOf<FileAlreadyExistsDialog>()


// Used to save the last operation folder
private var lastTargetFolder: OCFile? = null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ package com.owncloud.android.ui.activity

import android.Manifest.permission.POST_NOTIFICATIONS
import android.accounts.Account
import android.app.AlertDialog
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
Expand Down Expand Up @@ -97,6 +96,7 @@ import com.owncloud.android.presentation.spaces.SpacesListFragment.Companion.REQ
import com.owncloud.android.presentation.transfers.TransfersViewModel
import com.owncloud.android.providers.WorkManagerProvider
import com.owncloud.android.syncadapter.FileSyncAdapter
import com.owncloud.android.ui.dialog.FileAlreadyExistsDialog
import com.owncloud.android.ui.fragment.FileFragment
import com.owncloud.android.ui.fragment.TaskRetainerFragment
import com.owncloud.android.ui.helpers.FilesUploadHelper
Expand Down Expand Up @@ -742,6 +742,7 @@ class FileDisplayActivity : FileActivity(),
// responsibility of restore is preferred in onCreate() before than in
// onRestoreInstanceState when there are Fragments involved
Timber.v("onSaveInstanceState() start")

super.onSaveInstanceState(outState)
outState.putParcelable(KEY_WAITING_TO_PREVIEW, fileWaitingToPreview)
outState.putBoolean(KEY_SYNC_IN_PROGRESS, syncInProgress)
Expand Down Expand Up @@ -777,6 +778,7 @@ class FileDisplayActivity : FileActivity(),
syncBroadcastReceiver = SyncBroadcastReceiver()
localBroadcastManager!!.registerReceiver(syncBroadcastReceiver!!, syncIntentFilter)

showDialogs()
Timber.v("onResume() end")
}

Expand All @@ -788,6 +790,7 @@ class FileDisplayActivity : FileActivity(),
}

super.onPause()
dismissDialogs()
Timber.v("onPause() end")
}

Expand Down Expand Up @@ -1056,7 +1059,7 @@ class FileDisplayActivity : FileActivity(),
genericErrorMessage = getString(R.string.copy_file_error),
resources = resources,
showJustReason = true,
)
)
)
}
}
Expand All @@ -1069,53 +1072,137 @@ class FileDisplayActivity : FileActivity(),
replace: MutableList<Boolean?>,
launchAction: (List<OCFile>, List<Boolean?>) -> Unit,
) {
val context = this@FileDisplayActivity
if (!uiResult.data.isNullOrEmpty()) {
var pos = 0
data.asReversed().forEach { file ->
AlertDialog.Builder(context)
.setTitle(
val posArray = intArrayOf(0)
var posDialog = intArrayOf(data.lastIndex)
data.asReversed().forEachIndexed { index, file ->
val countDownValue = index + 1

val customDialog = FileAlreadyExistsDialog.newInstance(
titleText = this.getString(
if (file.isFolder) {
R.string.folder_already_exists
} else {
R.string.file_already_exists
}
)
.setMessage(
String.format(
context.getString(
if (file.isFolder) {
R.string.folder_already_exists_description
} else {
R.string.file_already_exists_description
}
), file.fileName
),
descriptionText = this.getString(
if (file.isFolder) {
R.string.folder_already_exists_description
} else {
R.string.file_already_exists_description
}, file.fileName
),
checkboxText = this.getString(R.string.apply_to_all_conflicts, countDownValue.toString()),
checkboxVisible = countDownValue > 1
)
customDialog.isCancelable = false
customDialog.show(this.supportFragmentManager, CUSTOM_DIALOG_TAG)

fileOperationsViewModel.openDialogs.add(customDialog)


customDialog.setDialogButtonClickListener(object : FileAlreadyExistsDialog.DialogButtonClickListener {

override fun onKeepBothButtonClick() {
applyAction(
posDialog = posDialog,
data = data,
replace = replace,
pos = posArray,
launchAction = launchAction,
uiResult = uiResult,
action = false
)
)
.setNeutralButton(R.string.welcome_feature_skip_button) { _, _ ->
replace.add(null)
pos++
if (pos == data.size) {
launchAction(uiResult.data, replace)
}
}
.setNegativeButton(R.string.conflict_replace) { _, _ ->
replace.add(true)
pos++
if (pos == data.size) {
launchAction(uiResult.data, replace)
}

override fun onSkipButtonClick() {
applyAction(
posDialog = posDialog,
data = data,
replace = replace,
pos = posArray,
launchAction = launchAction,
uiResult = uiResult,
action = null
)
}
.setPositiveButton(R.string.conflict_keep_both) { _, _ ->
replace.add(false)
pos++
if (pos == data.size) {
launchAction(uiResult.data, replace)
}

override fun onReplaceButtonClick() {
applyAction(
posDialog = posDialog,
data = data,
replace = replace,
pos = posArray,
launchAction = launchAction,
uiResult = uiResult,
action = true
)
}
.show()
}
)
}
}
}

private fun applyAction(
posDialog: IntArray,
data: List<OCFile>,
replace: MutableList<Boolean?>,
pos: IntArray,
launchAction: (List<OCFile>, List<Boolean?>) -> Unit,
uiResult: UIResult.Success<List<OCFile>>,
action: Boolean?
) {
var posDialog1 = posDialog
var pos1 = pos
if (fileOperationsViewModel.openDialogs[posDialog1[0]].isCheckBoxChecked) {
repeat(data.asReversed().size) {
replace.add(action)
pos1[0]++
if (pos1[0] == data.size) {
launchAction(
uiResult.data!!,
replace,
)
}
}
dismissAllOpenDialogs()
} else {
replace.add(action)
pos1[0]++
if (pos1[0] == data.size) {
launchAction(
uiResult.data!!,
replace,
)
}
fileOperationsViewModel.openDialogs[posDialog1[0]].dismiss()
fileOperationsViewModel.openDialogs.removeAt(posDialog1[0])
if (posDialog1[0] == 0) {
fileOperationsViewModel.openDialogs.clear()
} else {
posDialog1[0]--
}
}
}

private fun dismissAllOpenDialogs() {
fileOperationsViewModel.openDialogs.forEach { dialog ->
dialog.dismiss()
}
fileOperationsViewModel.openDialogs.clear()
}

private fun showDialogs() {
fileOperationsViewModel.openDialogs.forEach { dialog ->
dialog.show(this.supportFragmentManager, CUSTOM_DIALOG_TAG)
}
}

private fun dismissDialogs() {
fileOperationsViewModel.openDialogs.forEach { dialog ->
dialog.dismiss()
}
}

Expand Down Expand Up @@ -1706,6 +1793,8 @@ class FileDisplayActivity : FileActivity(),
private const val KEY_UPLOAD_HELPER = "FILE_UPLOAD_HELPER"
private const val KEY_FILE_LIST_OPTION = "FILE_LIST_OPTION"

private const val CUSTOM_DIALOG_TAG = "CUSTOM_DIALOG"

private const val PREFERENCE_NOTIFICATION_PERMISSION_REQUESTED = "PREFERENCE_NOTIFICATION_PERMISSION_REQUESTED"
const val ALL_FILES_SAF_REGEX = "*/*"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.owncloud.android.ui.dialog

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import com.owncloud.android.databinding.DialogFileAlreadyExistsBinding

class FileAlreadyExistsDialog : DialogFragment() {

private lateinit var binding: DialogFileAlreadyExistsBinding
internal var isCheckBoxChecked: Boolean = false

interface DialogButtonClickListener {
fun onKeepBothButtonClick()
fun onSkipButtonClick()
fun onReplaceButtonClick()
}

companion object {
var mListener: DialogButtonClickListener? = null

const val TITLE_TEXT = "titleText"
const val DESCRIPTION_TEXT = "descriptionText"
const val CHECKBOX_TEXT = "checkboxText"
private const val CHECKBOX_VISIBLE = "checkboxVisible"

fun newInstance(
titleText: String?,
descriptionText: String?,
checkboxText: String?,
checkboxVisible: Boolean,
dialogClickListener: DialogButtonClickListener? = null,
): FileAlreadyExistsDialog {
val fragment = FileAlreadyExistsDialog()
val args = Bundle()
args.putString(TITLE_TEXT, titleText)
args.putString(DESCRIPTION_TEXT, descriptionText)
args.putString(CHECKBOX_TEXT, checkboxText)
args.putBoolean(CHECKBOX_VISIBLE, checkboxVisible)

mListener = dialogClickListener
fragment.arguments = args
return fragment
}
}

fun setDialogButtonClickListener(listener: DialogButtonClickListener) = apply { mListener = listener }

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
binding = DialogFileAlreadyExistsBinding.inflate(inflater, container, false)

return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val titleText = arguments?.getString(TITLE_TEXT)
val descriptionText = arguments?.getString(DESCRIPTION_TEXT)
val checkboxText = arguments?.getString(CHECKBOX_TEXT)
val checkboxVisible = arguments?.getBoolean(CHECKBOX_VISIBLE)

binding.dialogFileAlreadyExistsTitle.text = titleText
binding.dialogFileAlreadyExistsInformation.text = descriptionText
binding.dialogFileAlreadyExistsCheckbox.text = checkboxText

binding.dialogFileAlreadyExistsKeepBoth.setOnClickListener { mListener?.onKeepBothButtonClick() }
binding.dialogFileAlreadyExistsCheckbox.setOnCheckedChangeListener { _, isChecked ->
isCheckBoxChecked = isChecked
}
binding.dialogFileAlreadyExistsReplace.setOnClickListener { mListener?.onReplaceButtonClick() }
binding.dialogFileAlreadyExistsSkip.setOnClickListener { mListener?.onSkipButtonClick() }

binding.dialogFileAlreadyExistsCheckbox.visibility = if (checkboxVisible == true) { View.VISIBLE } else { View.GONE }
}

}
Loading

0 comments on commit bf9e477

Please sign in to comment.