/*
* ownCloud Android client application
*
* @author Bartek Przybylski
* Copyright (C) 2012 Bartek Przybylski
* Copyright (C) 2015 ownCloud Inc.
*
* 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.ui.dialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.nextcloud.client.account.User;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
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.ui.adapter.OCFileListAdapter;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.ThemeUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatCheckBox;
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 {
private OCFile existingFile;
private File newFile;
public OnConflictDecisionMadeListener listener;
private User user;
private List asyncTasks = new ArrayList<>();
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);
args.putSerializable(KEY_NEW_FILE, new File(newFile.getStoragePath()));
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;
}
int color = ThemeUtils.primaryAccentColor(getContext());
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(color);
alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(color);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
existingFile = savedInstanceState.getParcelable(KEY_EXISTING_FILE);
newFile = (File) savedInstanceState.getSerializable(KEY_NEW_FILE);
user = savedInstanceState.getParcelable(KEY_USER);
} else if (getArguments() != null) {
existingFile = getArguments().getParcelable(KEY_EXISTING_FILE);
newFile = (File) getArguments().getSerializable(KEY_NEW_FILE);
user = getArguments().getParcelable(KEY_USER);
} else {
Toast.makeText(getContext(), "Failed to create conflict dialog", Toast.LENGTH_LONG).show();
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
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
LayoutInflater inflater = getActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.conflict_resolve_dialog, null);
int accentColor = ThemeUtils.primaryAccentColor(getContext());
AppCompatCheckBox newFileCheckbox = view.findViewById(R.id.new_checkbox);
AppCompatCheckBox existingFileCheckbox = view.findViewById(R.id.existing_checkbox);
ThemeUtils.tintCheckbox(newFileCheckbox, ThemeUtils.primaryColor(getContext()));
ThemeUtils.tintCheckbox(existingFileCheckbox, ThemeUtils.primaryColor(getContext()));
// Build the dialog
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(view)
.setPositiveButton(R.string.common_ok, (dialog, which) -> {
if (listener != null) {
if (newFileCheckbox.isChecked() && existingFileCheckbox.isChecked()) {
listener.conflictDecisionMade(Decision.KEEP_BOTH);
} else if (newFileCheckbox.isChecked()) {
listener.conflictDecisionMade(Decision.KEEP_LOCAL);
} else {
listener.conflictDecisionMade(Decision.KEEP_SERVER);
}
}
})
.setNegativeButton(R.string.common_cancel, (dialog, which) -> {
if (listener != null) {
listener.conflictDecisionMade(Decision.CANCEL);
}
})
.setTitle(ThemeUtils.getColoredTitle(getResources().getString(R.string.conflict_message_headline),
accentColor));
// set info for new file
TextView newSize = view.findViewById(R.id.new_size);
newSize.setText(DisplayUtils.bytesToHumanReadable(newFile.length()));
TextView newTimestamp = view.findViewById(R.id.new_timestamp);
newTimestamp.setText(DisplayUtils.getRelativeTimestamp(getContext(), newFile.lastModified()));
ImageView newThumbnail = view.findViewById(R.id.new_thumbnail);
newThumbnail.setTag(newFile.hashCode());
LocalFileListAdapter.setThumbnail(newFile, newThumbnail, getContext());
// set info for existing file
TextView existingSize = view.findViewById(R.id.existing_size);
existingSize.setText(DisplayUtils.bytesToHumanReadable(existingFile.getFileLength()));
TextView existingTimestamp = view.findViewById(R.id.existing_timestamp);
existingTimestamp.setText(DisplayUtils.getRelativeTimestamp(getContext(),
existingFile.getModificationTimestamp()));
ImageView existingThumbnail = view.findViewById(R.id.existing_thumbnail);
existingThumbnail.setTag(existingFile.getFileId());
OCFileListAdapter.setThumbnail(existingFile,
view.findViewById(R.id.existing_thumbnail),
user,
new FileDataStorageManager(user.toPlatformAccount(),
requireContext().getContentResolver()),
asyncTasks,
false,
getContext());
view.findViewById(R.id.newFileContainer)
.setOnClickListener(v -> newFileCheckbox.setChecked(!newFileCheckbox.isChecked()));
view.findViewById(R.id.existingFileContainer)
.setOnClickListener(v -> existingFileCheckbox.setChecked(!existingFileCheckbox.isChecked()));
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();
}
}