mirror of
https://github.com/nextcloud/android.git
synced 2024-11-27 09:39:25 +03:00
Merge pull request #10307 from nextcloud/fix/choosetemplate-button-nullpointer
Fix NPE in ChooseTemplateDialogFragment
This commit is contained in:
commit
3fd8928327
3 changed files with 412 additions and 452 deletions
|
@ -1,451 +0,0 @@
|
||||||
/*
|
|
||||||
* Nextcloud Android client application
|
|
||||||
*
|
|
||||||
* @author Tobias Kaminsky
|
|
||||||
* @author Chris Narkiewicz
|
|
||||||
*
|
|
||||||
* Copyright (C) 2018 Tobias Kaminsky
|
|
||||||
* Copyright (C) 2018 Nextcloud GmbH.
|
|
||||||
* Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.owncloud.android.ui.dialog;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.Window;
|
|
||||||
import android.view.WindowManager.LayoutParams;
|
|
||||||
import android.widget.Button;
|
|
||||||
|
|
||||||
import com.nextcloud.android.lib.resources.directediting.DirectEditingCreateFileRemoteOperation;
|
|
||||||
import com.nextcloud.android.lib.resources.directediting.DirectEditingObtainListOfTemplatesRemoteOperation;
|
|
||||||
import com.nextcloud.client.account.CurrentAccountProvider;
|
|
||||||
import com.nextcloud.client.account.User;
|
|
||||||
import com.nextcloud.client.di.Injectable;
|
|
||||||
import com.nextcloud.client.network.ClientFactory;
|
|
||||||
import com.owncloud.android.MainApp;
|
|
||||||
import com.owncloud.android.R;
|
|
||||||
import com.owncloud.android.databinding.ChooseTemplateBinding;
|
|
||||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
|
||||||
import com.owncloud.android.lib.common.Creator;
|
|
||||||
import com.owncloud.android.lib.common.OwnCloudClient;
|
|
||||||
import com.owncloud.android.lib.common.Template;
|
|
||||||
import com.owncloud.android.lib.common.TemplateList;
|
|
||||||
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
|
||||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
|
||||||
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation;
|
|
||||||
import com.owncloud.android.lib.resources.files.model.RemoteFile;
|
|
||||||
import com.owncloud.android.ui.activity.ExternalSiteWebView;
|
|
||||||
import com.owncloud.android.ui.activity.TextEditorWebView;
|
|
||||||
import com.owncloud.android.ui.adapter.TemplateAdapter;
|
|
||||||
import com.owncloud.android.utils.DisplayUtils;
|
|
||||||
import com.owncloud.android.utils.FileStorageUtils;
|
|
||||||
import com.owncloud.android.utils.theme.ThemeButtonUtils;
|
|
||||||
import com.owncloud.android.utils.theme.ThemeColorUtils;
|
|
||||||
import com.owncloud.android.utils.theme.ThemeDrawableUtils;
|
|
||||||
import com.owncloud.android.utils.theme.ThemeTextInputUtils;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.fragment.app.DialogFragment;
|
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dialog to show templates for new documents/spreadsheets/presentations.
|
|
||||||
*/
|
|
||||||
public class ChooseTemplateDialogFragment extends DialogFragment implements View.OnClickListener,
|
|
||||||
TemplateAdapter.ClickListener, Injectable {
|
|
||||||
|
|
||||||
private static final String ARG_PARENT_FOLDER = "PARENT_FOLDER";
|
|
||||||
private static final String ARG_CREATOR = "CREATOR";
|
|
||||||
private static final String ARG_HEADLINE = "HEADLINE";
|
|
||||||
private static final String TAG = ChooseTemplateDialogFragment.class.getSimpleName();
|
|
||||||
private static final String DOT = ".";
|
|
||||||
public static final int SINGLE_TEMPLATE = 1;
|
|
||||||
|
|
||||||
@Inject ClientFactory clientFactory;
|
|
||||||
@Inject CurrentAccountProvider currentAccount;
|
|
||||||
@Inject ThemeColorUtils themeColorUtils;
|
|
||||||
@Inject ThemeDrawableUtils themeDrawableUtils;
|
|
||||||
@Inject ThemeButtonUtils themeButtonUtils;
|
|
||||||
@Inject ThemeTextInputUtils themeTextInputUtils;
|
|
||||||
private TemplateAdapter adapter;
|
|
||||||
private OCFile parentFolder;
|
|
||||||
private String title;
|
|
||||||
private Button positiveButton;
|
|
||||||
private Creator creator;
|
|
||||||
|
|
||||||
public enum Type {
|
|
||||||
DOCUMENT,
|
|
||||||
SPREADSHEET,
|
|
||||||
PRESENTATION
|
|
||||||
}
|
|
||||||
|
|
||||||
ChooseTemplateBinding binding;
|
|
||||||
|
|
||||||
public static ChooseTemplateDialogFragment newInstance(OCFile parentFolder, Creator creator, String headline) {
|
|
||||||
ChooseTemplateDialogFragment frag = new ChooseTemplateDialogFragment();
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
args.putParcelable(ARG_PARENT_FOLDER, parentFolder);
|
|
||||||
args.putParcelable(ARG_CREATOR, creator);
|
|
||||||
args.putString(ARG_HEADLINE, headline);
|
|
||||||
frag.setArguments(args);
|
|
||||||
return frag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
|
|
||||||
AlertDialog alertDialog = (AlertDialog) getDialog();
|
|
||||||
|
|
||||||
positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
|
||||||
themeButtonUtils.themeBorderlessButton(themeColorUtils,
|
|
||||||
positiveButton,
|
|
||||||
alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL));
|
|
||||||
positiveButton.setOnClickListener(this);
|
|
||||||
positiveButton.setEnabled(false);
|
|
||||||
|
|
||||||
checkEnablingCreateButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
||||||
Bundle arguments = getArguments();
|
|
||||||
if (arguments == null) {
|
|
||||||
throw new IllegalArgumentException("Arguments may not be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
Activity activity = getActivity();
|
|
||||||
if (activity == null) {
|
|
||||||
throw new IllegalArgumentException("Activity may not be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
parentFolder = arguments.getParcelable(ARG_PARENT_FOLDER);
|
|
||||||
creator = arguments.getParcelable(ARG_CREATOR);
|
|
||||||
title = arguments.getString(ARG_HEADLINE, getString(R.string.select_template));
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
title = arguments.getString(ARG_HEADLINE);
|
|
||||||
} else {
|
|
||||||
title = savedInstanceState.getString(ARG_HEADLINE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inflate the layout for the dialog
|
|
||||||
LayoutInflater inflater = requireActivity().getLayoutInflater();
|
|
||||||
binding = ChooseTemplateBinding.inflate(inflater, null, false);
|
|
||||||
View view = binding.getRoot();
|
|
||||||
|
|
||||||
binding.filename.requestFocus();
|
|
||||||
themeTextInputUtils.colorTextInput(binding.filenameContainer,
|
|
||||||
binding.filename,
|
|
||||||
themeColorUtils.primaryColor(getContext()));
|
|
||||||
|
|
||||||
binding.filename.setOnKeyListener((v, keyCode, event) -> {
|
|
||||||
checkEnablingCreateButton();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
binding.filename.addTextChangedListener(new TextWatcher() {
|
|
||||||
@Override
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
||||||
// generated method stub
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
// generated method stub
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s) {
|
|
||||||
checkEnablingCreateButton();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
User user = currentAccount.getUser();
|
|
||||||
new FetchTemplateTask(this, clientFactory, user, creator).execute();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log_OC.e(TAG, "Loading stream url not possible: " + e);
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.list.setHasFixedSize(true);
|
|
||||||
binding.list.setLayoutManager(new GridLayoutManager(activity, 2));
|
|
||||||
adapter = new TemplateAdapter(creator.getMimetype(),
|
|
||||||
this,
|
|
||||||
getContext(),
|
|
||||||
currentAccount,
|
|
||||||
clientFactory,
|
|
||||||
themeColorUtils,
|
|
||||||
themeDrawableUtils);
|
|
||||||
binding.list.setAdapter(adapter);
|
|
||||||
|
|
||||||
// Build the dialog
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
|
||||||
builder.setView(view)
|
|
||||||
.setPositiveButton(R.string.create, null)
|
|
||||||
.setNeutralButton(R.string.common_cancel, null)
|
|
||||||
.setTitle(title);
|
|
||||||
Dialog dialog = builder.create();
|
|
||||||
|
|
||||||
Window window = dialog.getWindow();
|
|
||||||
|
|
||||||
if (window != null) {
|
|
||||||
window.setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
super.onDestroyView();
|
|
||||||
binding = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
|
|
||||||
super.onSaveInstanceState(savedInstanceState);
|
|
||||||
savedInstanceState.putString(ARG_HEADLINE, title);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createFromTemplate(Template template, String path) {
|
|
||||||
new CreateFileFromTemplateTask(this, clientFactory, currentAccount.getUser(), template, path, creator).execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTemplateList(TemplateList templateList) {
|
|
||||||
adapter.setTemplateList(templateList);
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(Template template) {
|
|
||||||
onTemplateChosen(template);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onTemplateChosen(Template template) {
|
|
||||||
adapter.setTemplateAsActive(template);
|
|
||||||
prefillFilenameIfEmpty(template);
|
|
||||||
checkEnablingCreateButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void prefillFilenameIfEmpty(Template template) {
|
|
||||||
String name = binding.filename.getText().toString();
|
|
||||||
if (name.isEmpty() || name.equalsIgnoreCase(DOT + template.getExtension())) {
|
|
||||||
binding.filename.setText(String.format("%s.%s", template.getTitle(), template.getExtension()));
|
|
||||||
name = binding.filename.getText().toString();
|
|
||||||
int dotPos = name.lastIndexOf('.');
|
|
||||||
binding.filename.setSelection((dotPos != -1) ? dotPos : name.length());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
String name = binding.filename.getText().toString();
|
|
||||||
String path = parentFolder.getRemotePath() + name;
|
|
||||||
|
|
||||||
Template selectedTemplate = adapter.getSelectedTemplate();
|
|
||||||
|
|
||||||
if (selectedTemplate == null) {
|
|
||||||
DisplayUtils.showSnackMessage(binding.list, R.string.select_one_template);
|
|
||||||
} else if (name.isEmpty() || name.equalsIgnoreCase(DOT + selectedTemplate.getExtension())) {
|
|
||||||
DisplayUtils.showSnackMessage(binding.list, R.string.enter_filename);
|
|
||||||
} else if (!name.endsWith(selectedTemplate.getExtension())) {
|
|
||||||
createFromTemplate(selectedTemplate, path + DOT + selectedTemplate.getExtension());
|
|
||||||
} else {
|
|
||||||
createFromTemplate(selectedTemplate, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkEnablingCreateButton() {
|
|
||||||
Template selectedTemplate = adapter.getSelectedTemplate();
|
|
||||||
String name = binding.filename.getText().toString();
|
|
||||||
|
|
||||||
positiveButton.setEnabled(selectedTemplate != null && !name.isEmpty() &&
|
|
||||||
!name.equalsIgnoreCase(DOT + selectedTemplate.getExtension()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class CreateFileFromTemplateTask extends AsyncTask<Void, Void, String> {
|
|
||||||
private ClientFactory clientFactory;
|
|
||||||
private WeakReference<ChooseTemplateDialogFragment> chooseTemplateDialogFragmentWeakReference;
|
|
||||||
private Template template;
|
|
||||||
private String path;
|
|
||||||
private Creator creator;
|
|
||||||
private User user;
|
|
||||||
private OCFile file;
|
|
||||||
|
|
||||||
CreateFileFromTemplateTask(ChooseTemplateDialogFragment chooseTemplateDialogFragment,
|
|
||||||
ClientFactory clientFactory,
|
|
||||||
User user,
|
|
||||||
Template template,
|
|
||||||
String path,
|
|
||||||
Creator creator
|
|
||||||
) {
|
|
||||||
this.clientFactory = clientFactory;
|
|
||||||
this.chooseTemplateDialogFragmentWeakReference = new WeakReference<>(chooseTemplateDialogFragment);
|
|
||||||
this.template = template;
|
|
||||||
this.path = path;
|
|
||||||
this.creator = creator;
|
|
||||||
this.user = user;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String doInBackground(Void... voids) {
|
|
||||||
try {
|
|
||||||
OwnCloudClient client = clientFactory.create(user);
|
|
||||||
|
|
||||||
RemoteOperationResult<String> result =
|
|
||||||
new DirectEditingCreateFileRemoteOperation(path,
|
|
||||||
creator.getEditor(),
|
|
||||||
creator.getId(),
|
|
||||||
template.getTitle()).execute(client);
|
|
||||||
if (!result.isSuccess()) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoteOperationResult newFileResult = new ReadFileRemoteOperation(path).execute(client);
|
|
||||||
if (!newFileResult.isSuccess()) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
final ChooseTemplateDialogFragment fragment = chooseTemplateDialogFragmentWeakReference.get();
|
|
||||||
if (fragment == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
final Context context = fragment.getContext();
|
|
||||||
if (context == null) {
|
|
||||||
// fragment has been detached
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
FileDataStorageManager storageManager = new FileDataStorageManager(user,
|
|
||||||
context.getContentResolver());
|
|
||||||
|
|
||||||
OCFile temp = FileStorageUtils.fillOCFile((RemoteFile) newFileResult.getData().get(0));
|
|
||||||
storageManager.saveFile(temp);
|
|
||||||
file = storageManager.getFileByPath(path);
|
|
||||||
|
|
||||||
return result.getResultData();
|
|
||||||
|
|
||||||
} catch (ClientFactory.CreationException e) {
|
|
||||||
Log_OC.e(TAG, "Error creating file from template!", e);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(String url) {
|
|
||||||
final ChooseTemplateDialogFragment fragment = chooseTemplateDialogFragmentWeakReference.get();
|
|
||||||
|
|
||||||
if (fragment != null && fragment.isAdded()) {
|
|
||||||
if (url.isEmpty()) {
|
|
||||||
DisplayUtils.showSnackMessage(fragment.binding.list, "Error creating file from template");
|
|
||||||
} else {
|
|
||||||
Intent editorWebView = new Intent(MainApp.getAppContext(), TextEditorWebView.class);
|
|
||||||
editorWebView.putExtra(ExternalSiteWebView.EXTRA_TITLE, "Text");
|
|
||||||
editorWebView.putExtra(ExternalSiteWebView.EXTRA_URL, url);
|
|
||||||
editorWebView.putExtra(ExternalSiteWebView.EXTRA_FILE, file);
|
|
||||||
editorWebView.putExtra(ExternalSiteWebView.EXTRA_SHOW_SIDEBAR, false);
|
|
||||||
fragment.startActivity(editorWebView);
|
|
||||||
|
|
||||||
fragment.dismiss();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log_OC.e(TAG, "Error creating file from template!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class FetchTemplateTask extends AsyncTask<Void, Void, TemplateList> {
|
|
||||||
|
|
||||||
private User user;
|
|
||||||
private ClientFactory clientFactory;
|
|
||||||
private WeakReference<ChooseTemplateDialogFragment> chooseTemplateDialogFragmentWeakReference;
|
|
||||||
private Creator creator;
|
|
||||||
|
|
||||||
FetchTemplateTask(ChooseTemplateDialogFragment chooseTemplateDialogFragment,
|
|
||||||
ClientFactory clientFactory,
|
|
||||||
User user,
|
|
||||||
Creator creator) {
|
|
||||||
this.user = user;
|
|
||||||
this.clientFactory = clientFactory;
|
|
||||||
this.chooseTemplateDialogFragmentWeakReference = new WeakReference<>(chooseTemplateDialogFragment);
|
|
||||||
this.creator = creator;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected TemplateList doInBackground(Void... voids) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
OwnCloudClient client = clientFactory.create(user);
|
|
||||||
RemoteOperationResult<TemplateList> result =
|
|
||||||
new DirectEditingObtainListOfTemplatesRemoteOperation(creator.getEditor(),
|
|
||||||
creator.getId())
|
|
||||||
.execute(client);
|
|
||||||
|
|
||||||
if (!result.isSuccess()) {
|
|
||||||
return new TemplateList();
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.getResultData();
|
|
||||||
} catch (ClientFactory.CreationException e) {
|
|
||||||
Log_OC.e(TAG, "Could not fetch template", e);
|
|
||||||
|
|
||||||
return new TemplateList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(TemplateList templateList) {
|
|
||||||
ChooseTemplateDialogFragment fragment = chooseTemplateDialogFragmentWeakReference.get();
|
|
||||||
|
|
||||||
if (fragment != null && fragment.isAdded()) {
|
|
||||||
if (templateList.getTemplates().isEmpty()) {
|
|
||||||
DisplayUtils.showSnackMessage(fragment.binding.list, R.string.error_retrieving_templates);
|
|
||||||
} else {
|
|
||||||
if (templateList.getTemplates().size() == SINGLE_TEMPLATE) {
|
|
||||||
fragment.onTemplateChosen(templateList.getTemplates().values().iterator().next());
|
|
||||||
fragment.binding.list.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
String name = DOT + templateList.getTemplates().values().iterator().next().getExtension();
|
|
||||||
fragment.binding.filename.setText(name);
|
|
||||||
fragment.binding.helperText.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment.setTemplateList(templateList);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log_OC.e(TAG, "Error streaming file: no previewMediaFragment!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,411 @@
|
||||||
|
/*
|
||||||
|
* Nextcloud Android client application
|
||||||
|
*
|
||||||
|
* @author Tobias Kaminsky
|
||||||
|
* @author Chris Narkiewicz
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Tobias Kaminsky
|
||||||
|
* Copyright (C) 2018 Nextcloud GmbH.
|
||||||
|
* Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.owncloud.android.ui.dialog
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.AsyncTask
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.View
|
||||||
|
import android.view.WindowManager
|
||||||
|
import android.widget.Button
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import com.nextcloud.android.lib.resources.directediting.DirectEditingCreateFileRemoteOperation
|
||||||
|
import com.nextcloud.android.lib.resources.directediting.DirectEditingObtainListOfTemplatesRemoteOperation
|
||||||
|
import com.nextcloud.client.account.CurrentAccountProvider
|
||||||
|
import com.nextcloud.client.account.User
|
||||||
|
import com.nextcloud.client.di.Injectable
|
||||||
|
import com.nextcloud.client.network.ClientFactory
|
||||||
|
import com.nextcloud.client.network.ClientFactory.CreationException
|
||||||
|
import com.owncloud.android.MainApp
|
||||||
|
import com.owncloud.android.R
|
||||||
|
import com.owncloud.android.databinding.ChooseTemplateBinding
|
||||||
|
import com.owncloud.android.datamodel.FileDataStorageManager
|
||||||
|
import com.owncloud.android.datamodel.OCFile
|
||||||
|
import com.owncloud.android.lib.common.Creator
|
||||||
|
import com.owncloud.android.lib.common.Template
|
||||||
|
import com.owncloud.android.lib.common.TemplateList
|
||||||
|
import com.owncloud.android.lib.common.utils.Log_OC
|
||||||
|
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation
|
||||||
|
import com.owncloud.android.lib.resources.files.model.RemoteFile
|
||||||
|
import com.owncloud.android.ui.activity.ExternalSiteWebView
|
||||||
|
import com.owncloud.android.ui.activity.TextEditorWebView
|
||||||
|
import com.owncloud.android.ui.adapter.TemplateAdapter
|
||||||
|
import com.owncloud.android.utils.DisplayUtils
|
||||||
|
import com.owncloud.android.utils.FileStorageUtils
|
||||||
|
import com.owncloud.android.utils.theme.ThemeButtonUtils
|
||||||
|
import com.owncloud.android.utils.theme.ThemeColorUtils
|
||||||
|
import com.owncloud.android.utils.theme.ThemeDrawableUtils
|
||||||
|
import com.owncloud.android.utils.theme.ThemeTextInputUtils
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialog to show templates for new documents/spreadsheets/presentations.
|
||||||
|
*/
|
||||||
|
class ChooseTemplateDialogFragment : DialogFragment(), View.OnClickListener, TemplateAdapter.ClickListener, Injectable {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var clientFactory: ClientFactory
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var currentAccount: CurrentAccountProvider
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var themeColorUtils: ThemeColorUtils
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var themeDrawableUtils: ThemeDrawableUtils
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var themeButtonUtils: ThemeButtonUtils
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var themeTextInputUtils: ThemeTextInputUtils
|
||||||
|
|
||||||
|
private var adapter: TemplateAdapter? = null
|
||||||
|
private var parentFolder: OCFile? = null
|
||||||
|
private var title: String? = null
|
||||||
|
private var positiveButton: Button? = null
|
||||||
|
private var creator: Creator? = null
|
||||||
|
|
||||||
|
enum class Type {
|
||||||
|
DOCUMENT, SPREADSHEET, PRESENTATION
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _binding: ChooseTemplateBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
val alertDialog = dialog as AlertDialog
|
||||||
|
val button = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||||
|
|
||||||
|
themeButtonUtils.themeBorderlessButton(
|
||||||
|
themeColorUtils,
|
||||||
|
button,
|
||||||
|
alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL)
|
||||||
|
)
|
||||||
|
button.setOnClickListener(this)
|
||||||
|
button.isEnabled = false
|
||||||
|
|
||||||
|
positiveButton = button
|
||||||
|
checkEnablingCreateButton()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val arguments = arguments ?: throw IllegalArgumentException("Arguments may not be null")
|
||||||
|
val activity = activity ?: throw IllegalArgumentException("Activity may not be null")
|
||||||
|
|
||||||
|
parentFolder = arguments.getParcelable(ARG_PARENT_FOLDER)
|
||||||
|
creator = arguments.getParcelable(ARG_CREATOR)
|
||||||
|
title = arguments.getString(ARG_HEADLINE, getString(R.string.select_template))
|
||||||
|
title = when (savedInstanceState) {
|
||||||
|
null -> arguments.getString(ARG_HEADLINE)
|
||||||
|
else -> savedInstanceState.getString(ARG_HEADLINE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inflate the layout for the dialog
|
||||||
|
val inflater = requireActivity().layoutInflater
|
||||||
|
_binding = ChooseTemplateBinding.inflate(inflater, null, false)
|
||||||
|
val view: View = binding.root
|
||||||
|
|
||||||
|
binding.filename.requestFocus()
|
||||||
|
themeTextInputUtils.colorTextInput(
|
||||||
|
binding.filenameContainer,
|
||||||
|
binding.filename,
|
||||||
|
themeColorUtils.primaryColor(context)
|
||||||
|
)
|
||||||
|
binding.filename.setOnKeyListener { _, _, _ ->
|
||||||
|
checkEnablingCreateButton()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
binding.filename.addTextChangedListener(object : TextWatcher {
|
||||||
|
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
|
||||||
|
|
||||||
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) = Unit
|
||||||
|
|
||||||
|
override fun afterTextChanged(s: Editable) {
|
||||||
|
checkEnablingCreateButton()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
fetchTemplate()
|
||||||
|
|
||||||
|
binding.list.setHasFixedSize(true)
|
||||||
|
binding.list.layoutManager = GridLayoutManager(activity, 2)
|
||||||
|
adapter = TemplateAdapter(
|
||||||
|
creator!!.mimetype,
|
||||||
|
this,
|
||||||
|
context,
|
||||||
|
currentAccount,
|
||||||
|
clientFactory,
|
||||||
|
themeColorUtils,
|
||||||
|
themeDrawableUtils
|
||||||
|
)
|
||||||
|
binding.list.adapter = adapter
|
||||||
|
|
||||||
|
// Build the dialog
|
||||||
|
val builder = AlertDialog.Builder(activity)
|
||||||
|
builder.setView(view)
|
||||||
|
.setPositiveButton(R.string.create, null)
|
||||||
|
.setNeutralButton(R.string.common_cancel, null)
|
||||||
|
.setTitle(title)
|
||||||
|
val dialog: Dialog = builder.create()
|
||||||
|
val window = dialog.window
|
||||||
|
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("TooGenericExceptionCaught") // legacy code
|
||||||
|
private fun fetchTemplate() {
|
||||||
|
try {
|
||||||
|
val user = currentAccount.user
|
||||||
|
FetchTemplateTask(this, clientFactory, user, creator).execute()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log_OC.e(TAG, "Loading stream url not possible: $e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(savedInstanceState: Bundle) {
|
||||||
|
super.onSaveInstanceState(savedInstanceState)
|
||||||
|
savedInstanceState.putString(ARG_HEADLINE, title)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createFromTemplate(template: Template, path: String) {
|
||||||
|
CreateFileFromTemplateTask(this, clientFactory, currentAccount.user, template, path, creator).execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setTemplateList(templateList: TemplateList?) {
|
||||||
|
adapter!!.setTemplateList(templateList)
|
||||||
|
adapter!!.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(template: Template) {
|
||||||
|
onTemplateChosen(template)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onTemplateChosen(template: Template) {
|
||||||
|
adapter!!.setTemplateAsActive(template)
|
||||||
|
prefillFilenameIfEmpty(template)
|
||||||
|
checkEnablingCreateButton()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prefillFilenameIfEmpty(template: Template) {
|
||||||
|
var name = binding.filename.text.toString()
|
||||||
|
if (name.isEmpty() || name.equals(DOT + template.extension, ignoreCase = true)) {
|
||||||
|
binding.filename.setText(String.format("%s.%s", template.title, template.extension))
|
||||||
|
name = binding.filename.text.toString()
|
||||||
|
val dotPos = name.lastIndexOf('.')
|
||||||
|
binding.filename.setSelection(if (dotPos != -1) dotPos else name.length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(v: View) {
|
||||||
|
val name = binding.filename.text.toString()
|
||||||
|
val path = parentFolder!!.remotePath + name
|
||||||
|
|
||||||
|
val selectedTemplate = adapter!!.selectedTemplate
|
||||||
|
|
||||||
|
if (selectedTemplate == null) {
|
||||||
|
DisplayUtils.showSnackMessage(binding.list, R.string.select_one_template)
|
||||||
|
} else if (name.isEmpty() || name.equals(DOT + selectedTemplate.extension, ignoreCase = true)) {
|
||||||
|
DisplayUtils.showSnackMessage(binding.list, R.string.enter_filename)
|
||||||
|
} else if (!name.endsWith(selectedTemplate.extension)) {
|
||||||
|
createFromTemplate(selectedTemplate, path + DOT + selectedTemplate.extension)
|
||||||
|
} else {
|
||||||
|
createFromTemplate(selectedTemplate, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkEnablingCreateButton() {
|
||||||
|
if (positiveButton != null) {
|
||||||
|
val selectedTemplate = adapter!!.selectedTemplate
|
||||||
|
val name = binding.filename.text.toString()
|
||||||
|
positiveButton!!.isEnabled = selectedTemplate != null && name.isNotEmpty() &&
|
||||||
|
!name.equals(DOT + selectedTemplate.extension, ignoreCase = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("LongParameterList") // legacy code
|
||||||
|
private class CreateFileFromTemplateTask(
|
||||||
|
chooseTemplateDialogFragment: ChooseTemplateDialogFragment,
|
||||||
|
private val clientFactory: ClientFactory?,
|
||||||
|
user: User,
|
||||||
|
template: Template,
|
||||||
|
path: String,
|
||||||
|
creator: Creator?
|
||||||
|
) : AsyncTask<Void, Void, String>() {
|
||||||
|
private val chooseTemplateDialogFragmentWeakReference: WeakReference<ChooseTemplateDialogFragment>
|
||||||
|
private val template: Template
|
||||||
|
private val path: String
|
||||||
|
private val creator: Creator?
|
||||||
|
private val user: User
|
||||||
|
private var file: OCFile? = null
|
||||||
|
|
||||||
|
@Suppress("ReturnCount") // legacy code
|
||||||
|
override fun doInBackground(vararg params: Void): String {
|
||||||
|
return try {
|
||||||
|
val client = clientFactory!!.create(user)
|
||||||
|
val result = DirectEditingCreateFileRemoteOperation(
|
||||||
|
path,
|
||||||
|
creator!!.editor,
|
||||||
|
creator.id,
|
||||||
|
template.title
|
||||||
|
).execute(client)
|
||||||
|
if (!result.isSuccess) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
val newFileResult = ReadFileRemoteOperation(path).execute(client)
|
||||||
|
if (!newFileResult.isSuccess) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
val fragment = chooseTemplateDialogFragmentWeakReference.get() ?: return ""
|
||||||
|
val context = fragment.context
|
||||||
|
?: // fragment has been detached
|
||||||
|
return ""
|
||||||
|
val storageManager = FileDataStorageManager(
|
||||||
|
user,
|
||||||
|
context.contentResolver
|
||||||
|
)
|
||||||
|
val temp = FileStorageUtils.fillOCFile(newFileResult.data[0] as RemoteFile)
|
||||||
|
storageManager.saveFile(temp)
|
||||||
|
file = storageManager.getFileByPath(path)
|
||||||
|
result.resultData
|
||||||
|
} catch (e: CreationException) {
|
||||||
|
Log_OC.e(TAG, "Error creating file from template!", e)
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPostExecute(url: String) {
|
||||||
|
val fragment = chooseTemplateDialogFragmentWeakReference.get()
|
||||||
|
if (fragment != null && fragment.isAdded) {
|
||||||
|
if (url.isEmpty()) {
|
||||||
|
DisplayUtils.showSnackMessage(fragment.binding.list, "Error creating file from template")
|
||||||
|
} else {
|
||||||
|
val editorWebView = Intent(MainApp.getAppContext(), TextEditorWebView::class.java)
|
||||||
|
editorWebView.putExtra(ExternalSiteWebView.EXTRA_TITLE, "Text")
|
||||||
|
editorWebView.putExtra(ExternalSiteWebView.EXTRA_URL, url)
|
||||||
|
editorWebView.putExtra(ExternalSiteWebView.EXTRA_FILE, file)
|
||||||
|
editorWebView.putExtra(ExternalSiteWebView.EXTRA_SHOW_SIDEBAR, false)
|
||||||
|
fragment.startActivity(editorWebView)
|
||||||
|
fragment.dismiss()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log_OC.e(TAG, "Error creating file from template!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
chooseTemplateDialogFragmentWeakReference = WeakReference(chooseTemplateDialogFragment)
|
||||||
|
this.template = template
|
||||||
|
this.path = path
|
||||||
|
this.creator = creator
|
||||||
|
this.user = user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FetchTemplateTask(
|
||||||
|
chooseTemplateDialogFragment: ChooseTemplateDialogFragment,
|
||||||
|
private val clientFactory: ClientFactory?,
|
||||||
|
private val user: User,
|
||||||
|
creator: Creator?
|
||||||
|
) : AsyncTask<Void, Void, TemplateList>() {
|
||||||
|
private val chooseTemplateDialogFragmentWeakReference: WeakReference<ChooseTemplateDialogFragment>
|
||||||
|
private val creator: Creator?
|
||||||
|
|
||||||
|
override fun doInBackground(vararg voids: Void): TemplateList {
|
||||||
|
return try {
|
||||||
|
val client = clientFactory!!.create(user)
|
||||||
|
val result = DirectEditingObtainListOfTemplatesRemoteOperation(
|
||||||
|
creator!!.editor,
|
||||||
|
creator.id
|
||||||
|
)
|
||||||
|
.execute(client)
|
||||||
|
if (!result.isSuccess) {
|
||||||
|
TemplateList()
|
||||||
|
} else result.resultData
|
||||||
|
} catch (e: CreationException) {
|
||||||
|
Log_OC.e(TAG, "Could not fetch template", e)
|
||||||
|
TemplateList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPostExecute(templateList: TemplateList) {
|
||||||
|
val fragment = chooseTemplateDialogFragmentWeakReference.get()
|
||||||
|
if (fragment != null && fragment.isAdded) {
|
||||||
|
if (templateList.templates.isEmpty()) {
|
||||||
|
DisplayUtils.showSnackMessage(fragment.binding.list, R.string.error_retrieving_templates)
|
||||||
|
} else {
|
||||||
|
if (templateList.templates.size == SINGLE_TEMPLATE) {
|
||||||
|
fragment.onTemplateChosen(templateList.templates.values.iterator().next())
|
||||||
|
fragment.binding.list.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
val name = DOT + templateList.templates.values.iterator().next().extension
|
||||||
|
fragment.binding.filename.setText(name)
|
||||||
|
fragment.binding.helperText.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
fragment.setTemplateList(templateList)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log_OC.e(TAG, "Error streaming file: no previewMediaFragment!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
chooseTemplateDialogFragmentWeakReference = WeakReference(chooseTemplateDialogFragment)
|
||||||
|
this.creator = creator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ARG_PARENT_FOLDER = "PARENT_FOLDER"
|
||||||
|
private const val ARG_CREATOR = "CREATOR"
|
||||||
|
private const val ARG_HEADLINE = "HEADLINE"
|
||||||
|
private val TAG = ChooseTemplateDialogFragment::class.java.simpleName
|
||||||
|
private const val DOT = "."
|
||||||
|
const val SINGLE_TEMPLATE = 1
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun newInstance(parentFolder: OCFile?, creator: Creator?, headline: String?): ChooseTemplateDialogFragment {
|
||||||
|
val frag = ChooseTemplateDialogFragment()
|
||||||
|
val args = Bundle()
|
||||||
|
args.putParcelable(ARG_PARENT_FOLDER, parentFolder)
|
||||||
|
args.putParcelable(ARG_CREATOR, creator)
|
||||||
|
args.putString(ARG_HEADLINE, headline)
|
||||||
|
frag.arguments = args
|
||||||
|
return frag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -627,7 +627,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showTemplate(Creator creator, String headline) {
|
public void showTemplate(@NonNull Creator creator, @NonNull String headline) {
|
||||||
ChooseTemplateDialogFragment.newInstance(mFile, creator, headline).show(requireActivity().getSupportFragmentManager(),
|
ChooseTemplateDialogFragment.newInstance(mFile, creator, headline).show(requireActivity().getSupportFragmentManager(),
|
||||||
DIALOG_CREATE_DOCUMENT);
|
DIALOG_CREATE_DOCUMENT);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue