Merge pull request #13226 from nextcloud/convert-ConflictsResolveDialog-to-kt

Convert Conflicts Resolve Dialog to Kotlin
This commit is contained in:
Alper Öztürk 2024-07-04 21:47:43 +02:00 committed by GitHub
commit 060c78a6f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 273 additions and 279 deletions

View file

@ -150,7 +150,7 @@ class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener
outState.putInt(EXTRA_LOCAL_BEHAVIOUR, localBehaviour)
}
override fun conflictDecisionMade(decision: Decision) {
override fun conflictDecisionMade(decision: Decision?) {
listener?.conflictDecisionMade(decision)
}
@ -205,10 +205,10 @@ class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener
if (prev != null) {
fragmentTransaction.remove(prev)
}
if (existingFile != null && storageManager.fileExists(remotePath)) {
if (existingFile != null && storageManager.fileExists(remotePath) && newFile != null) {
val dialog = ConflictsResolveDialog.newInstance(
existingFile,
newFile,
newFile!!,
userOptional.get()
)
dialog.show(fragmentTransaction, "conflictDialog")

View file

@ -1,276 +0,0 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-FileCopyrightText: 2020-2022 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2015 ownCloud Inc.
* SPDX-FileCopyrightText: 2012 Bartosz Przybylski <bart.p.pl@gmail.com>
* SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
*/
package com.owncloud.android.ui.dialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.nextcloud.client.account.User;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.utils.extensions.BundleExtensionsKt;
import com.nextcloud.utils.extensions.FileExtensionsKt;
import com.owncloud.android.R;
import com.owncloud.android.databinding.ConflictResolveDialogBinding;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.SyncedFolderProvider;
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.ui.adapter.LocalFileListAdapter;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.theme.ViewThemeUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
/**
* Dialog which will be displayed to user upon keep-in-sync file conflict.
*/
public class ConflictsResolveDialog extends DialogFragment implements Injectable {
private ConflictResolveDialogBinding binding;
private OCFile existingFile;
private File newFile;
public OnConflictDecisionMadeListener listener;
private User user;
private final List<ThumbnailsCacheManager.ThumbnailGenerationTask> asyncTasks = new ArrayList<>();
private MaterialButton positiveButton;
@Inject ViewThemeUtils viewThemeUtils;
@Inject SyncedFolderProvider syncedFolderProvider;
private static final String TAG = "ConflictsResolveDialog";
private static final String KEY_NEW_FILE = "file";
private static final String KEY_EXISTING_FILE = "ocfile";
private static final String KEY_USER = "user";
public enum Decision {
CANCEL,
KEEP_BOTH,
KEEP_LOCAL,
KEEP_SERVER,
}
public static ConflictsResolveDialog newInstance(OCFile existingFile, OCFile newFile, User user) {
ConflictsResolveDialog dialog = new ConflictsResolveDialog();
Bundle args = new Bundle();
args.putParcelable(KEY_EXISTING_FILE, existingFile);
File file = new File(newFile.getStoragePath());
FileExtensionsKt.logFileSize(file, TAG);
args.putSerializable(KEY_NEW_FILE, file);
args.putParcelable(KEY_USER, user);
dialog.setArguments(args);
return dialog;
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
try {
listener = (OnConflictDecisionMadeListener) context;
} catch (ClassCastException e) {
throw new ClassCastException("Activity of this dialog must implement OnConflictDecisionMadeListener");
}
}
@Override
public void onStart() {
super.onStart();
AlertDialog alertDialog = (AlertDialog) getDialog();
if (alertDialog == null) {
Toast.makeText(getContext(), "Failed to create conflict dialog", Toast.LENGTH_LONG).show();
return;
}
positiveButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
MaterialButton negativeButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton);
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton);
positiveButton.setEnabled(false);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
existingFile = BundleExtensionsKt.getParcelableArgument(savedInstanceState, KEY_EXISTING_FILE, OCFile.class);
newFile = BundleExtensionsKt.getSerializableArgument(savedInstanceState, KEY_NEW_FILE, File.class);
user = BundleExtensionsKt.getParcelableArgument(savedInstanceState, KEY_USER, User.class);
} else if (getArguments() != null) {
existingFile = BundleExtensionsKt.getParcelableArgument(getArguments(), KEY_EXISTING_FILE, OCFile.class);
newFile = BundleExtensionsKt.getSerializableArgument(getArguments(), KEY_NEW_FILE, File.class);
user = BundleExtensionsKt.getParcelableArgument(getArguments(), KEY_USER, User.class);
} else {
Toast.makeText(getContext(), "Failed to create conflict dialog", Toast.LENGTH_LONG).show();
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
FileExtensionsKt.logFileSize(existingFile, TAG);
FileExtensionsKt.logFileSize(newFile, TAG);
outState.putParcelable(KEY_EXISTING_FILE, existingFile);
outState.putSerializable(KEY_NEW_FILE, newFile);
outState.putParcelable(KEY_USER, user);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Inflate the layout for the dialog
binding = ConflictResolveDialogBinding.inflate(requireActivity().getLayoutInflater());
viewThemeUtils.platform.themeCheckbox(binding.newCheckbox);
viewThemeUtils.platform.themeCheckbox(binding.existingCheckbox);
// Build the dialog
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
builder.setView(binding.getRoot())
.setPositiveButton(R.string.common_ok, (dialog, which) -> {
if (listener != null) {
if (binding.newCheckbox.isChecked() && binding.existingCheckbox.isChecked()) {
listener.conflictDecisionMade(Decision.KEEP_BOTH);
} else if (binding.newCheckbox.isChecked()) {
listener.conflictDecisionMade(Decision.KEEP_LOCAL);
} else if (binding.existingCheckbox.isChecked()) {
listener.conflictDecisionMade(Decision.KEEP_SERVER);
} // else do nothing
}
})
.setNegativeButton(R.string.common_cancel, (dialog, which) -> {
if (listener != null) {
listener.conflictDecisionMade(Decision.CANCEL);
}
})
.setTitle(String.format(getString(R.string.conflict_file_headline), existingFile.getFileName()));
File parentFile = new File(existingFile.getRemotePath()).getParentFile();
if (parentFile != null) {
binding.in.setText(String.format(getString(R.string.in_folder), parentFile.getAbsolutePath()));
} else {
binding.in.setVisibility(View.GONE);
}
// set info for new file
binding.newSize.setText(DisplayUtils.bytesToHumanReadable(newFile.length()));
binding.newTimestamp.setText(DisplayUtils.getRelativeTimestamp(getContext(), newFile.lastModified()));
binding.newThumbnail.setTag(newFile.hashCode());
LocalFileListAdapter.setThumbnail(newFile,
binding.newThumbnail,
getContext(),
viewThemeUtils);
// set info for existing file
binding.existingSize.setText(DisplayUtils.bytesToHumanReadable(existingFile.getFileLength()));
binding.existingTimestamp.setText(DisplayUtils.getRelativeTimestamp(getContext(),
existingFile.getModificationTimestamp()));
binding.existingThumbnail.setTag(existingFile.getFileId());
DisplayUtils.setThumbnail(existingFile,
binding.existingThumbnail,
user,
new FileDataStorageManager(user,
requireContext().getContentResolver()),
asyncTasks,
false,
getContext(),
null,
syncedFolderProvider.getPreferences(),
viewThemeUtils,
syncedFolderProvider);
View.OnClickListener checkBoxClickListener = v ->
positiveButton.setEnabled(binding.newCheckbox.isChecked() || binding.existingCheckbox.isChecked());
binding.newCheckbox.setOnClickListener(checkBoxClickListener);
binding.existingCheckbox.setOnClickListener(checkBoxClickListener);
binding.newFileContainer.setOnClickListener(v -> {
binding.newCheckbox.setChecked(!binding.newCheckbox.isChecked());
positiveButton.setEnabled(binding.newCheckbox.isChecked() || binding.existingCheckbox.isChecked());
});
binding.existingFileContainer.setOnClickListener(v -> {
binding.existingCheckbox.setChecked(!binding.existingCheckbox.isChecked());
positiveButton.setEnabled(binding.newCheckbox.isChecked() || binding.existingCheckbox.isChecked());
});
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(binding.existingFileContainer.getContext(), builder);
return builder.create();
}
public void showDialog(AppCompatActivity activity) {
Fragment prev = activity.getSupportFragmentManager().findFragmentByTag("dialog");
FragmentTransaction ft = activity.getSupportFragmentManager().beginTransaction();
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
this.show(ft, "dialog");
}
@Override
public void onCancel(@NonNull DialogInterface dialog) {
if (listener != null) {
listener.conflictDecisionMade(Decision.CANCEL);
}
}
public interface OnConflictDecisionMadeListener {
void conflictDecisionMade(Decision decision);
}
@Override
public void onStop() {
super.onStop();
for (ThumbnailsCacheManager.ThumbnailGenerationTask task : asyncTasks) {
if (task != null) {
task.cancel(true);
if (task.getGetMethod() != null) {
Log_OC.d(this, "cancel: abort get method directly");
task.getGetMethod().abort();
}
}
}
asyncTasks.clear();
}
}

View file

@ -0,0 +1,270 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-FileCopyrightText: 2020-2022 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2015 ownCloud Inc.
* SPDX-FileCopyrightText: 2012 Bartosz Przybylski <bart.p.pl@gmail.com>
* SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
*/
package com.owncloud.android.ui.dialog
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment
import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.client.account.User
import com.nextcloud.client.di.Injectable
import com.nextcloud.utils.extensions.getParcelableArgument
import com.nextcloud.utils.extensions.getSerializableArgument
import com.nextcloud.utils.extensions.logFileSize
import com.owncloud.android.R
import com.owncloud.android.databinding.ConflictResolveDialogBinding
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.datamodel.SyncedFolderProvider
import com.owncloud.android.datamodel.ThumbnailsCacheManager.ThumbnailGenerationTask
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.ui.adapter.LocalFileListAdapter
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.theme.ViewThemeUtils
import java.io.File
import javax.inject.Inject
/**
* Dialog which will be displayed to user upon keep-in-sync file conflict.
*/
class ConflictsResolveDialog : DialogFragment(), Injectable {
private lateinit var binding: ConflictResolveDialogBinding
private var existingFile: OCFile? = null
private var newFile: File? = null
var listener: OnConflictDecisionMadeListener? = null
private var user: User? = null
private val asyncTasks: MutableList<ThumbnailGenerationTask> = ArrayList()
private var positiveButton: MaterialButton? = null
@Inject
lateinit var viewThemeUtils: ViewThemeUtils
@Inject
lateinit var syncedFolderProvider: SyncedFolderProvider
enum class Decision {
CANCEL,
KEEP_BOTH,
KEEP_LOCAL,
KEEP_SERVER
}
override fun onAttach(context: Context) {
super.onAttach(context)
try {
listener = context as OnConflictDecisionMadeListener
} catch (e: ClassCastException) {
throw ClassCastException("Activity of this dialog must implement OnConflictDecisionMadeListener")
}
}
override fun onStart() {
super.onStart()
val alertDialog = dialog as AlertDialog?
if (alertDialog == null) {
Toast.makeText(context, "Failed to create conflict dialog", Toast.LENGTH_LONG).show()
return
}
positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) as MaterialButton
val negativeButton = alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE) as MaterialButton
positiveButton?.let {
viewThemeUtils.material.colorMaterialButtonPrimaryTonal(it)
}
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton)
positiveButton?.isEnabled = false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
existingFile = savedInstanceState.getParcelableArgument(KEY_EXISTING_FILE, OCFile::class.java)
newFile = savedInstanceState.getSerializableArgument(KEY_NEW_FILE, File::class.java)
user = savedInstanceState.getParcelableArgument(KEY_USER, User::class.java)
} else if (arguments != null) {
existingFile = arguments.getParcelableArgument(KEY_EXISTING_FILE, OCFile::class.java)
newFile = arguments.getSerializableArgument(KEY_NEW_FILE, File::class.java)
user = arguments.getParcelableArgument(KEY_USER, User::class.java)
} else {
Toast.makeText(context, "Failed to create conflict dialog", Toast.LENGTH_LONG).show()
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
existingFile.logFileSize(TAG)
newFile.logFileSize(TAG)
outState.putParcelable(KEY_EXISTING_FILE, existingFile)
outState.putSerializable(KEY_NEW_FILE, newFile)
outState.putParcelable(KEY_USER, user)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
binding = ConflictResolveDialogBinding.inflate(requireActivity().layoutInflater)
viewThemeUtils.platform.themeCheckbox(binding.newCheckbox)
viewThemeUtils.platform.themeCheckbox(binding.existingCheckbox)
val builder = MaterialAlertDialogBuilder(requireActivity())
builder.setView(binding.root)
.setPositiveButton(R.string.common_ok) { _: DialogInterface?, _: Int ->
if (binding.newCheckbox.isChecked && binding.existingCheckbox.isChecked) {
listener?.conflictDecisionMade(Decision.KEEP_BOTH)
} else if (binding.newCheckbox.isChecked) {
listener?.conflictDecisionMade(Decision.KEEP_LOCAL)
} else if (binding.existingCheckbox.isChecked) {
listener?.conflictDecisionMade(Decision.KEEP_SERVER)
}
}
.setNegativeButton(R.string.common_cancel) { _: DialogInterface?, _: Int ->
listener?.conflictDecisionMade(Decision.CANCEL)
}
.setTitle(String.format(getString(R.string.conflict_file_headline), existingFile?.fileName))
setupUI()
setOnClickListeners()
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(binding.existingFileContainer.context, builder)
return builder.create()
}
private fun setupUI() {
val parentFile = existingFile?.remotePath?.let { File(it).parentFile }
if (parentFile != null) {
binding.`in`.text = String.format(getString(R.string.in_folder), parentFile.absolutePath)
} else {
binding.`in`.visibility = View.GONE
}
// set info for new file
binding.newSize.text = newFile?.length()?.let { DisplayUtils.bytesToHumanReadable(it) }
binding.newTimestamp.text = newFile?.lastModified()?.let { DisplayUtils.getRelativeTimestamp(context, it) }
binding.newThumbnail.tag = newFile.hashCode()
LocalFileListAdapter.setThumbnail(
newFile,
binding.newThumbnail,
context,
viewThemeUtils
)
// set info for existing file
binding.existingSize.text = existingFile?.fileLength?.let { DisplayUtils.bytesToHumanReadable(it) }
binding.existingTimestamp.text = existingFile?.modificationTimestamp?.let {
DisplayUtils.getRelativeTimestamp(
context,
it
)
}
binding.existingThumbnail.tag = existingFile?.fileId
DisplayUtils.setThumbnail(
existingFile,
binding.existingThumbnail,
user,
FileDataStorageManager(
user,
requireContext().contentResolver
),
asyncTasks,
false,
context,
null,
syncedFolderProvider.preferences,
viewThemeUtils,
syncedFolderProvider
)
}
private fun setOnClickListeners() {
val checkBoxClickListener = View.OnClickListener {
positiveButton?.isEnabled = binding.newCheckbox.isChecked || binding.existingCheckbox.isChecked
}
binding.newCheckbox.setOnClickListener(checkBoxClickListener)
binding.existingCheckbox.setOnClickListener(checkBoxClickListener)
binding.newFileContainer.setOnClickListener {
binding.newCheckbox.isChecked = !binding.newCheckbox.isChecked
positiveButton?.isEnabled = binding.newCheckbox.isChecked || binding.existingCheckbox.isChecked
}
binding.existingFileContainer.setOnClickListener {
binding.existingCheckbox.isChecked = !binding.existingCheckbox.isChecked
positiveButton?.isEnabled = binding.newCheckbox.isChecked || binding.existingCheckbox.isChecked
}
}
fun showDialog(activity: AppCompatActivity) {
val prev = activity.supportFragmentManager.findFragmentByTag("dialog")
val ft = activity.supportFragmentManager.beginTransaction()
if (prev != null) {
ft.remove(prev)
}
ft.addToBackStack(null)
show(ft, "dialog")
}
override fun onCancel(dialog: DialogInterface) {
listener?.conflictDecisionMade(Decision.CANCEL)
}
fun interface OnConflictDecisionMadeListener {
fun conflictDecisionMade(decision: Decision?)
}
override fun onStop() {
super.onStop()
for (task in asyncTasks) {
task.cancel(true)
Log_OC.d(this, "cancel: abort get method directly")
task.getMethod?.abort()
}
asyncTasks.clear()
}
companion object {
private const val TAG = "ConflictsResolveDialog"
private const val KEY_NEW_FILE = "file"
private const val KEY_EXISTING_FILE = "ocfile"
private const val KEY_USER = "user"
@JvmStatic
fun newInstance(existingFile: OCFile?, newFile: OCFile, user: User?): ConflictsResolveDialog {
val file = File(newFile.storagePath)
file.logFileSize(TAG)
val bundle = Bundle().apply {
putParcelable(KEY_EXISTING_FILE, existingFile)
putSerializable(KEY_NEW_FILE, file)
putParcelable(KEY_USER, user)
}
return ConflictsResolveDialog().apply {
arguments = bundle
}
}
}
}