Эх сурвалжийг харах

Merge pull request #8005 from nextcloud/template

Template Dialog: template needs to be selected
Tobias Kaminsky 4 жил өмнө
parent
commit
afc6b720c3
24 өөрчлөгдсөн 358 нэмэгдсэн , 161 устгасан
  1. BIN
      screenshots/gplay/debug/com.owncloud.android.ui.activity.PassCodeActivityIT_delete.png
  2. BIN
      screenshots/gplay/debug/com.owncloud.android.ui.activity.PassCodeActivityIT_delete_dark_blue.png
  3. BIN
      screenshots/gplay/debug/com.owncloud.android.ui.activity.PassCodeActivityIT_delete_dark_white.png
  4. BIN
      screenshots/gplay/debug/com.owncloud.android.ui.activity.PassCodeActivityIT_delete_light_white.png
  5. BIN
      screenshots/gplay/debug/com.owncloud.android.ui.activity.PassCodeActivityIT_request.png
  6. BIN
      screenshots/gplay/debug/com.owncloud.android.ui.activity.PassCodeActivityIT_request_dark_blue.png
  7. BIN
      screenshots/gplay/debug/com.owncloud.android.ui.activity.PassCodeActivityIT_request_dark_white.png
  8. BIN
      screenshots/gplay/debug/com.owncloud.android.ui.activity.PassCodeActivityIT_request_light_white.png
  9. 1 1
      scripts/analysis/lint-results.txt
  10. 1 1
      src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.java
  11. 1 1
      src/main/java/com/owncloud/android/ui/activity/PassCodeActivity.java
  12. 33 16
      src/main/java/com/owncloud/android/ui/adapter/RichDocumentsTemplateAdapter.java
  13. 35 17
      src/main/java/com/owncloud/android/ui/adapter/TemplateAdapter.java
  14. 94 47
      src/main/java/com/owncloud/android/ui/dialog/ChooseRichDocumentsTemplateDialogFragment.java
  15. 118 43
      src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.java
  16. 2 2
      src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java
  17. 1 1
      src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetActions.java
  18. 1 1
      src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java
  19. 2 2
      src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
  20. 2 4
      src/main/java/com/owncloud/android/utils/ThemeUtils.java
  21. 35 9
      src/main/res/layout/choose_template.xml
  22. 28 16
      src/main/res/layout/template_button.xml
  23. 1 0
      src/main/res/values-night/colors.xml
  24. 3 0
      src/main/res/values/strings.xml

BIN
screenshots/gplay/debug/com.owncloud.android.ui.activity.PassCodeActivityIT_delete.png


BIN
screenshots/gplay/debug/com.owncloud.android.ui.activity.PassCodeActivityIT_delete_dark_blue.png


BIN
screenshots/gplay/debug/com.owncloud.android.ui.activity.PassCodeActivityIT_delete_dark_white.png


BIN
screenshots/gplay/debug/com.owncloud.android.ui.activity.PassCodeActivityIT_delete_light_white.png


BIN
screenshots/gplay/debug/com.owncloud.android.ui.activity.PassCodeActivityIT_request.png


BIN
screenshots/gplay/debug/com.owncloud.android.ui.activity.PassCodeActivityIT_request_dark_blue.png


BIN
screenshots/gplay/debug/com.owncloud.android.ui.activity.PassCodeActivityIT_request_dark_white.png


BIN
screenshots/gplay/debug/com.owncloud.android.ui.activity.PassCodeActivityIT_request_light_white.png


+ 1 - 1
scripts/analysis/lint-results.txt

@@ -1,2 +1,2 @@
 DO NOT TOUCH; GENERATED BY DRONE
-      <span class="mdl-layout-title">Lint Report: 240 warnings</span>
+      <span class="mdl-layout-title">Lint Report: 232 warnings</span>

+ 1 - 1
src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.java

@@ -278,7 +278,7 @@ public class DialogFragmentIT extends AbstractIT {
             }
 
             @Override
-            public void showTemplate(Creator creator) {
+            public void showTemplate(Creator creator, String headline) {
 
             }
 

+ 1 - 1
src/main/java/com/owncloud/android/ui/activity/PassCodeActivity.java

@@ -93,7 +93,7 @@ public class PassCodeActivity extends AppCompatActivity implements Injectable {
 
         int elementColor = ThemeUtils.primaryColor(this, true);
 
-        ThemeUtils.themeDialogActionButton(binding.cancel);
+        ThemeUtils.themeBorderlessButton(binding.cancel, ThemeUtils.primaryColor(this, true));
 
         passCodeEditTexts[0] = binding.txt0;
         ThemeUtils.colorEditText(passCodeEditTexts[0], elementColor);

+ 33 - 16
src/main/java/com/owncloud/android/ui/adapter/RichDocumentsTemplateAdapter.java

@@ -26,16 +26,16 @@ import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
 
 import com.bumptech.glide.Glide;
 import com.nextcloud.client.account.CurrentAccountProvider;
 import com.nextcloud.client.network.ClientFactory;
 import com.owncloud.android.R;
+import com.owncloud.android.databinding.TemplateButtonBinding;
 import com.owncloud.android.datamodel.Template;
 import com.owncloud.android.ui.dialog.ChooseRichDocumentsTemplateDialogFragment;
 import com.owncloud.android.utils.NextcloudServer;
+import com.owncloud.android.utils.ThemeUtils;
 import com.owncloud.android.utils.glide.CustomGlideStreamLoader;
 
 import java.util.ArrayList;
@@ -43,8 +43,6 @@ import java.util.List;
 
 import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView;
-import butterknife.BindView;
-import butterknife.ButterKnife;
 
 /**
  * Adapter for handling Templates, used to create files out of it via RichDocuments app
@@ -57,6 +55,9 @@ public class RichDocumentsTemplateAdapter extends RecyclerView.Adapter<RichDocum
     private ChooseRichDocumentsTemplateDialogFragment.Type type;
     private CurrentAccountProvider currentAccountProvider;
     private ClientFactory clientFactory;
+    private Template selectedTemplate;
+    private final int colorSelected;
+    private final int colorUnselected;
 
     public RichDocumentsTemplateAdapter(
         ChooseRichDocumentsTemplateDialogFragment.Type type,
@@ -70,13 +71,19 @@ public class RichDocumentsTemplateAdapter extends RecyclerView.Adapter<RichDocum
         this.context = context;
         this.currentAccountProvider = currentAccountProvider;
         this.clientFactory = clientFactory;
+        colorSelected = ThemeUtils.primaryColor(context, true);
+        colorUnselected = context.getResources().getColor(R.color.grey_200);
     }
 
     @NonNull
     @Override
     @NextcloudServer(max = 18) // remove entire class
     public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
-        return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.template_button, parent, false));
+        return new RichDocumentsTemplateAdapter.ViewHolder(
+            TemplateButtonBinding.inflate(LayoutInflater.from(parent.getContext()),
+                                          parent,
+                                          false)
+        );
     }
 
     @Override
@@ -88,6 +95,15 @@ public class RichDocumentsTemplateAdapter extends RecyclerView.Adapter<RichDocum
         this.templateList = templateList;
     }
 
+    public void setTemplateAsActive(Template template) {
+        selectedTemplate = template;
+        notifyDataSetChanged();
+    }
+
+    public Template getSelectedTemplate() {
+        return selectedTemplate;
+    }
+
     @Override
     public int getItemCount() {
         return templateList.size();
@@ -95,17 +111,12 @@ public class RichDocumentsTemplateAdapter extends RecyclerView.Adapter<RichDocum
 
     public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
 
-        @BindView(R.id.name)
-        public TextView name;
-
-        @BindView(R.id.thumbnail)
-        public ImageView thumbnail;
-
+        private final TemplateButtonBinding binding;
         private Template template;
 
-        public ViewHolder(View itemView) {
-            super(itemView);
-            ButterKnife.bind(this, itemView);
+        public ViewHolder(@NonNull TemplateButtonBinding binding) {
+            super(binding.getRoot());
+            this.binding = binding;
             itemView.setOnClickListener(this);
         }
 
@@ -143,9 +154,15 @@ public class RichDocumentsTemplateAdapter extends RecyclerView.Adapter<RichDocum
                 load(template.getThumbnailLink())
                 .placeholder(placeholder)
                 .error(placeholder)
-                .into(thumbnail);
+                .into(binding.template);
 
-            name.setText(template.getName());
+            binding.templateName.setText(template.getName());
+
+            if (template == selectedTemplate) {
+                binding.templateContainer.setStrokeColor(colorSelected);
+            } else {
+                binding.templateContainer.setStrokeColor(colorUnselected);
+            }
         }
     }
 

+ 35 - 17
src/main/java/com/owncloud/android/ui/adapter/TemplateAdapter.java

@@ -29,22 +29,20 @@ import android.graphics.drawable.Drawable;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
 
 import com.bumptech.glide.Glide;
 import com.nextcloud.client.account.CurrentAccountProvider;
 import com.nextcloud.client.network.ClientFactory;
 import com.owncloud.android.R;
+import com.owncloud.android.databinding.TemplateButtonBinding;
 import com.owncloud.android.lib.common.Template;
 import com.owncloud.android.lib.common.TemplateList;
 import com.owncloud.android.utils.MimeTypeUtil;
+import com.owncloud.android.utils.ThemeUtils;
 import com.owncloud.android.utils.glide.CustomGlideStreamLoader;
 
 import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView;
-import butterknife.BindView;
-import butterknife.ButterKnife;
 
 /**
  * Adapter for handling Templates, used to create files out of it via RichDocuments app
@@ -57,6 +55,9 @@ public class TemplateAdapter extends RecyclerView.Adapter<TemplateAdapter.ViewHo
     private CurrentAccountProvider currentAccountProvider;
     private ClientFactory clientFactory;
     private String mimetype;
+    private Template selectedTemplate;
+    private final int colorSelected;
+    private final int colorUnselected;
 
     public TemplateAdapter(
         String mimetype,
@@ -70,12 +71,18 @@ public class TemplateAdapter extends RecyclerView.Adapter<TemplateAdapter.ViewHo
         this.context = context;
         this.currentAccountProvider = currentAccountProvider;
         this.clientFactory = clientFactory;
+        colorSelected = ThemeUtils.primaryColor(context, true);
+        colorUnselected = context.getResources().getColor(R.color.grey_200);
     }
 
     @NonNull
     @Override
     public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
-        return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.template_button, parent, false));
+        return new TemplateAdapter.ViewHolder(
+            TemplateButtonBinding.inflate(LayoutInflater.from(parent.getContext()),
+                                          parent,
+                                          false)
+        );
     }
 
     @Override
@@ -87,23 +94,28 @@ public class TemplateAdapter extends RecyclerView.Adapter<TemplateAdapter.ViewHo
         this.templateList = templateList;
     }
 
+    public void setTemplateAsActive(Template template) {
+        selectedTemplate = template;
+        notifyDataSetChanged();
+    }
+
+    public Template getSelectedTemplate() {
+        return selectedTemplate;
+    }
+
     @Override
     public int getItemCount() {
         return templateList.getTemplateList().size();
     }
 
     public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
-        @BindView(R.id.name)
-        public TextView name;
-
-        @BindView(R.id.thumbnail)
-        public ImageView thumbnail;
 
+        private final TemplateButtonBinding binding;
         private Template template;
 
-        public ViewHolder(View itemView) {
-            super(itemView);
-            ButterKnife.bind(this, itemView);
+        public ViewHolder(@NonNull TemplateButtonBinding binding) {
+            super(binding.getRoot());
+            this.binding = binding;
             itemView.setOnClickListener(this);
         }
 
@@ -124,11 +136,17 @@ public class TemplateAdapter extends RecyclerView.Adapter<TemplateAdapter.ViewHo
 
             Glide.with(context).using(new CustomGlideStreamLoader(currentAccountProvider, clientFactory))
                 .load(template.getPreview())
-                    .placeholder(placeholder)
-                    .error(placeholder)
-                    .into(thumbnail);
+                .placeholder(placeholder)
+                .error(placeholder)
+                .into(binding.template);
+
+            binding.templateName.setText(template.getTitle());
 
-            name.setText(template.getTitle());
+            if (template == selectedTemplate) {
+                binding.templateContainer.setStrokeColor(colorSelected);
+            } else {
+                binding.templateContainer.setStrokeColor(colorUnselected);
+            }
         }
     }
 

+ 94 - 47
src/main/java/com/owncloud/android/ui/dialog/ChooseRichDocumentsTemplateDialogFragment.java

@@ -22,19 +22,16 @@
 
 package com.owncloud.android.ui.dialog;
 
-import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.Dialog;
-import android.content.DialogInterface;
 import android.content.Intent;
-import android.graphics.PorterDuff;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.Window;
 import android.view.WindowManager.LayoutParams;
-import android.widget.EditText;
+import android.widget.Button;
 
 import com.nextcloud.client.account.CurrentAccountProvider;
 import com.nextcloud.client.account.User;
@@ -42,6 +39,7 @@ 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.datamodel.Template;
@@ -72,26 +70,25 @@ import androidx.annotation.NonNull;
 import androidx.appcompat.app.AlertDialog;
 import androidx.fragment.app.DialogFragment;
 import androidx.recyclerview.widget.GridLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import butterknife.BindView;
-import butterknife.ButterKnife;
 
 /**
  * Dialog to show templates for new documents/spreadsheets/presentations.
  */
-public class ChooseRichDocumentsTemplateDialogFragment extends DialogFragment implements DialogInterface.OnClickListener,
+public class ChooseRichDocumentsTemplateDialogFragment extends DialogFragment implements View.OnClickListener,
     RichDocumentsTemplateAdapter.ClickListener, Injectable {
 
     private static final String ARG_PARENT_FOLDER = "PARENT_FOLDER";
     private static final String ARG_TYPE = "TYPE";
     private static final String TAG = ChooseRichDocumentsTemplateDialogFragment.class.getSimpleName();
     private static final String DOT = ".";
+    public static final int SINGLE_TEMPLATE = 1;
 
     private RichDocumentsTemplateAdapter adapter;
     private OCFile parentFolder;
     private OwnCloudClient client;
     @Inject CurrentAccountProvider currentAccount;
     @Inject ClientFactory clientFactory;
+    private Button positiveButton;
 
     public enum Type {
         DOCUMENT,
@@ -99,11 +96,7 @@ public class ChooseRichDocumentsTemplateDialogFragment extends DialogFragment im
         PRESENTATION
     }
 
-    @BindView(R.id.list)
-    RecyclerView listView;
-
-    @BindView(R.id.filename)
-    EditText fileName;
+    ChooseTemplateBinding binding;
 
     @NextcloudServer(max = 18) // will be removed in favor of generic direct editing
     public static ChooseRichDocumentsTemplateDialogFragment newInstance(OCFile parentFolder, Type type) {
@@ -113,7 +106,6 @@ public class ChooseRichDocumentsTemplateDialogFragment extends DialogFragment im
         args.putString(ARG_TYPE, type.name());
         frag.setArguments(args);
         return frag;
-
     }
 
     @Override
@@ -124,8 +116,14 @@ public class ChooseRichDocumentsTemplateDialogFragment extends DialogFragment im
 
         AlertDialog alertDialog = (AlertDialog) getDialog();
 
-        alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(color);
-        alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(color);
+        positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+        ThemeUtils.themeBorderlessButton(positiveButton, color);
+        positiveButton.setOnClickListener(this);
+        positiveButton.setEnabled(false);
+
+        ThemeUtils.themeBorderlessButton(alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL), color);
+
+        checkEnablingCreateButton();
     }
 
     @NonNull
@@ -141,37 +139,36 @@ public class ChooseRichDocumentsTemplateDialogFragment extends DialogFragment im
             throw new IllegalArgumentException("Activity may not be null");
         }
 
-        int accentColor = ThemeUtils.primaryAccentColor(getContext());
-
-        parentFolder = arguments.getParcelable(ARG_PARENT_FOLDER);
-
-        // Inflate the layout for the dialog
-        LayoutInflater inflater = activity.getLayoutInflater();
-        @SuppressLint("InflateParams") View view = inflater.inflate(R.layout.choose_template, null);
-        ButterKnife.bind(this, view);
-
-        fileName.requestFocus();
-        fileName.getBackground().setColorFilter(accentColor, PorterDuff.Mode.SRC_ATOP);
-
         try {
             client = clientFactory.create(currentAccount.getUser());
         } catch (ClientFactory.CreationException e) {
             throw new RuntimeException(e); // we'll NPE without the client
         }
 
+        parentFolder = arguments.getParcelable(ARG_PARENT_FOLDER);
+
+        // Inflate the layout for the dialog
+        LayoutInflater inflater = requireActivity().getLayoutInflater();
+        binding = ChooseTemplateBinding.inflate(inflater, null, false);
+        View view = binding.getRoot();
+
+        binding.filename.requestFocus();
+        ThemeUtils.colorTextInput(binding.filenameContainer, binding.filename, ThemeUtils.primaryColor(getContext()));
+
         Type type = Type.valueOf(arguments.getString(ARG_TYPE));
         new FetchTemplateTask(this, client).execute(type);
 
-        listView.setHasFixedSize(true);
-        listView.setLayoutManager(new GridLayoutManager(activity, 2));
+        binding.list.setHasFixedSize(true);
+        binding.list.setLayoutManager(new GridLayoutManager(activity, 2));
         adapter = new RichDocumentsTemplateAdapter(type, this, getContext(), currentAccount, clientFactory);
-        listView.setAdapter(adapter);
+        binding.list.setAdapter(adapter);
 
         // Build the dialog
         AlertDialog.Builder builder = new AlertDialog.Builder(activity);
         builder.setView(view)
-            .setNegativeButton(R.string.common_cancel, this)
-            .setTitle(R.string.select_template);
+            .setPositiveButton(R.string.create, null)
+            .setNeutralButton(R.string.common_cancel, null)
+            .setTitle(getTitle(type));
         Dialog dialog = builder.create();
 
         Window window = dialog.getWindow();
@@ -183,6 +180,24 @@ public class ChooseRichDocumentsTemplateDialogFragment extends DialogFragment im
         return dialog;
     }
 
+    private int getTitle(Type type) {
+        if (type == Type.DOCUMENT) {
+            return R.string.create_new_document;
+        } else if (type == Type.SPREADSHEET) {
+            return R.string.create_new_spreadsheet;
+        } else if (type == Type.PRESENTATION) {
+            return R.string.create_new_presentation;
+        }
+
+        return R.string.select_template;
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        binding = null;
+    }
+
     private void createFromTemplate(Template template, String path) {
         new CreateFileFromTemplateTask(this, client, template, path, currentAccount.getUser()).execute();
     }
@@ -193,22 +208,48 @@ public class ChooseRichDocumentsTemplateDialogFragment extends DialogFragment im
     }
 
     @Override
-    public void onClick(Template template) {
-        String name = fileName.getText().toString();
+    public void onClick(View v) {
+        String name = binding.filename.getText().toString();
         String path = parentFolder.getRemotePath() + name;
 
-        if (name.isEmpty() || name.equalsIgnoreCase(DOT + template.getExtension())) {
-            DisplayUtils.showSnackMessage(listView, R.string.enter_filename);
-        } else if (!name.endsWith(template.getExtension())) {
-            createFromTemplate(template, path + DOT + template.getExtension());
+        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(template, path);
+            createFromTemplate(selectedTemplate, path);
         }
     }
 
     @Override
-    public void onClick(DialogInterface dialog, int which) {
-        // cancel is handled by dialog itself, no other button available
+    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.name, template.extension));
+        }
+        binding.filename.setSelection(binding.filename.getText().toString().lastIndexOf('.'));
+    }
+
+    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> {
@@ -268,7 +309,7 @@ public class ChooseRichDocumentsTemplateDialogFragment extends DialogFragment im
 
             if (fragment != null && fragment.isAdded()) {
                 if (url.isEmpty()) {
-                    DisplayUtils.showSnackMessage(fragment.listView, "Error creating file from template");
+                    DisplayUtils.showSnackMessage(fragment.binding.list, "Error creating file from template");
                 } else {
                     Intent collaboraWebViewIntent = new Intent(MainApp.getAppContext(), RichDocumentsEditorWebView.class);
                     collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_TITLE, "Collabora");
@@ -319,12 +360,18 @@ public class ChooseRichDocumentsTemplateDialogFragment extends DialogFragment im
 
             if (fragment != null) {
                 if (templateList.isEmpty()) {
-                    DisplayUtils.showSnackMessage(fragment.listView, R.string.error_retrieving_templates);
+                    DisplayUtils.showSnackMessage(fragment.binding.list, R.string.error_retrieving_templates);
                 } else {
-                    fragment.setTemplateList(templateList);
+                    if (templateList.size() == SINGLE_TEMPLATE) {
+                        fragment.onTemplateChosen(templateList.get(0));
+                        fragment.binding.list.setVisibility(View.GONE);
+                    } else {
+                        String name = DOT + templateList.get(0).getExtension();
+                        fragment.binding.filename.setText(name);
+                        fragment.binding.helperText.setVisibility(View.VISIBLE);
+                    }
 
-                    String name = DOT + templateList.get(0).getExtension();
-                    fragment.fileName.setText(name);
+                    fragment.setTemplateList(templateList);
                 }
             } else {
                 Log_OC.e(TAG, "Error streaming file: no previewMediaFragment!");

+ 118 - 43
src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.java

@@ -24,20 +24,19 @@
 
 package com.owncloud.android.ui.dialog;
 
-import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.Dialog;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.Intent;
-import android.graphics.PorterDuff;
 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.EditText;
+import android.widget.Button;
 
 import com.nextcloud.android.lib.resources.directediting.DirectEditingCreateFileRemoteOperation;
 import com.nextcloud.android.lib.resources.directediting.DirectEditingObtainListOfTemplatesRemoteOperation;
@@ -47,6 +46,7 @@ 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;
@@ -72,26 +72,27 @@ import androidx.annotation.NonNull;
 import androidx.appcompat.app.AlertDialog;
 import androidx.fragment.app.DialogFragment;
 import androidx.recyclerview.widget.GridLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import butterknife.BindView;
-import butterknife.ButterKnife;
 
 /**
  * Dialog to show templates for new documents/spreadsheets/presentations.
  */
-public class ChooseTemplateDialogFragment extends DialogFragment implements DialogInterface.OnClickListener,
+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;
 
     private TemplateAdapter adapter;
     private OCFile parentFolder;
+    private String title;
     @Inject ClientFactory clientFactory;
     private Creator creator;
     @Inject CurrentAccountProvider currentAccount;
+    private Button positiveButton;
 
     public enum Type {
         DOCUMENT,
@@ -99,20 +100,16 @@ public class ChooseTemplateDialogFragment extends DialogFragment implements Dial
         PRESENTATION
     }
 
-    @BindView(R.id.list)
-    RecyclerView listView;
+    ChooseTemplateBinding binding;
 
-    @BindView(R.id.filename)
-    EditText fileName;
-
-    public static ChooseTemplateDialogFragment newInstance(OCFile parentFolder, Creator creator) {
+    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
@@ -123,8 +120,14 @@ public class ChooseTemplateDialogFragment extends DialogFragment implements Dial
 
         AlertDialog alertDialog = (AlertDialog) getDialog();
 
-        alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(color);
-        alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(color);
+        positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+        ThemeUtils.themeBorderlessButton(positiveButton, color);
+        positiveButton.setOnClickListener(this);
+        positiveButton.setEnabled(false);
+
+        ThemeUtils.themeBorderlessButton(alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL), color);
+
+        checkEnablingCreateButton();
     }
 
     @NonNull
@@ -140,18 +143,45 @@ public class ChooseTemplateDialogFragment extends DialogFragment implements Dial
             throw new IllegalArgumentException("Activity may not be null");
         }
 
-        int accentColor = ThemeUtils.primaryAccentColor(getContext());
-
         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 = activity.getLayoutInflater();
-        @SuppressLint("InflateParams") View view = inflater.inflate(R.layout.choose_template, null);
-        ButterKnife.bind(this, view);
+        LayoutInflater inflater = requireActivity().getLayoutInflater();
+        binding = ChooseTemplateBinding.inflate(inflater, null, false);
+        View view = binding.getRoot();
+
+        binding.filename.requestFocus();
+        ThemeUtils.colorTextInput(binding.filenameContainer, binding.filename, ThemeUtils.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
+            }
 
-        fileName.requestFocus();
-        fileName.getBackground().setColorFilter(accentColor, PorterDuff.Mode.SRC_ATOP);
+            @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();
@@ -160,16 +190,17 @@ public class ChooseTemplateDialogFragment extends DialogFragment implements Dial
             Log_OC.e(TAG, "Loading stream url not possible: " + e);
         }
 
-        listView.setHasFixedSize(true);
-        listView.setLayoutManager(new GridLayoutManager(activity, 2));
+        binding.list.setHasFixedSize(true);
+        binding.list.setLayoutManager(new GridLayoutManager(activity, 2));
         adapter = new TemplateAdapter(creator.getMimetype(), this, getContext(), currentAccount, clientFactory);
-        listView.setAdapter(adapter);
+        binding.list.setAdapter(adapter);
 
         // Build the dialog
         AlertDialog.Builder builder = new AlertDialog.Builder(activity);
         builder.setView(view)
-            .setNegativeButton(R.string.common_cancel, this)
-            .setTitle(R.string.select_template);
+            .setPositiveButton(R.string.create, null)
+            .setNeutralButton(R.string.common_cancel, null)
+            .setTitle(title);
         Dialog dialog = builder.create();
 
         Window window = dialog.getWindow();
@@ -181,6 +212,18 @@ public class ChooseTemplateDialogFragment extends DialogFragment implements Dial
         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();
     }
@@ -192,21 +235,47 @@ public class ChooseTemplateDialogFragment extends DialogFragment implements Dial
 
     @Override
     public void onClick(Template template) {
-        String name = fileName.getText().toString();
-        String path = parentFolder.getRemotePath() + name;
+        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())) {
-            DisplayUtils.showSnackMessage(listView, R.string.enter_filename);
-        } else if (!name.endsWith(template.getExtension())) {
-            createFromTemplate(template, path + DOT + template.getExtension());
-        } else {
-            createFromTemplate(template, path);
+            binding.filename.setText(String.format("%s.%s", template.title, template.extension));
         }
+        binding.filename.setSelection(binding.filename.getText().toString().lastIndexOf('.'));
     }
 
     @Override
-    public void onClick(DialogInterface dialog, int which) {
-        // cancel is handled by dialog itself, no other button available
+    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> {
@@ -284,7 +353,7 @@ public class ChooseTemplateDialogFragment extends DialogFragment implements Dial
 
             if (fragment != null && fragment.isAdded()) {
                 if (url.isEmpty()) {
-                    DisplayUtils.showSnackMessage(fragment.listView, "Error creating file from template");
+                    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");
@@ -345,12 +414,18 @@ public class ChooseTemplateDialogFragment extends DialogFragment implements Dial
 
             if (fragment != null && fragment.isAdded()) {
                 if (templateList.templates.isEmpty()) {
-                    DisplayUtils.showSnackMessage(fragment.listView, R.string.error_retrieving_templates);
+                    DisplayUtils.showSnackMessage(fragment.binding.list, R.string.error_retrieving_templates);
                 } else {
-                    fragment.setTemplateList(templateList);
+                    if (templateList.templates.size() == SINGLE_TEMPLATE) {
+                        fragment.onTemplateChosen(templateList.templates.values().iterator().next());
+                        fragment.binding.list.setVisibility(View.GONE);
+                    } else {
+                        String name = DOT + templateList.templates.values().iterator().next().getExtension();
+                        fragment.binding.filename.setText(name);
+                        fragment.binding.helperText.setVisibility(View.VISIBLE);
+                    }
 
-                    String name = DOT + templateList.templates.values().iterator().next().getExtension();
-                    fragment.fileName.setText(name);
+                    fragment.setTemplateList(templateList);
                 }
             } else {
                 Log_OC.e(TAG, "Error streaming file: no previewMediaFragment!");

+ 2 - 2
src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java

@@ -202,10 +202,10 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
         mNameCollisionPolicySummary = view.findViewById(R.id.setting_instant_name_collision_policy_summary);
 
         mCancel = view.findViewById(R.id.cancel);
-        ThemeUtils.themeDialogActionButton(mCancel);
+        ThemeUtils.themeBorderlessButton(mCancel, accentColor);
 
         mSave = view.findViewById(R.id.save);
-        ThemeUtils.themeDialogActionButton(mSave);
+        ThemeUtils.themeBorderlessButton(mSave, accentColor);
 
         // Set values
         setEnabled(mSyncedFolder.isEnabled());

+ 1 - 1
src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetActions.java

@@ -65,7 +65,7 @@ public interface OCFileListBottomSheetActions {
     /**
      * open template selection for creator @link Creator
      */
-    void showTemplate(Creator creator);
+    void showTemplate(Creator creator, String headline);
 
     /**
      * open editor for rich workspace

+ 1 - 1
src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java

@@ -121,7 +121,7 @@ public class OCFileListBottomSheetDialog extends BottomSheetDialog {
                                                                             getContext()));
 
                     creatorView.setOnClickListener(v -> {
-                        actions.showTemplate(creator);
+                        actions.showTemplate(creator, creatorViewBinding.creatorName.getText().toString());
                         dismiss();
                     });
 

+ 2 - 2
src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -565,8 +565,8 @@ public class OCFileListFragment extends ExtendedListFragment implements
     }
 
     @Override
-    public void showTemplate(Creator creator) {
-        ChooseTemplateDialogFragment.newInstance(mFile, creator).show(requireActivity().getSupportFragmentManager(),
+    public void showTemplate(Creator creator, String headline) {
+        ChooseTemplateDialogFragment.newInstance(mFile, creator, headline).show(requireActivity().getSupportFragmentManager(),
                                                                       DIALOG_CREATE_DOCUMENT);
     }
 

+ 2 - 4
src/main/java/com/owncloud/android/utils/ThemeUtils.java

@@ -45,7 +45,6 @@ import android.widget.ImageView;
 import android.widget.ProgressBar;
 import android.widget.SeekBar;
 
-import com.google.android.material.button.MaterialButton;
 import com.google.android.material.floatingactionbutton.FloatingActionButton;
 import com.google.android.material.snackbar.Snackbar;
 import com.google.android.material.tabs.TabLayout;
@@ -569,13 +568,12 @@ public final class ThemeUtils {
         ));
     }
 
-    public static void themeDialogActionButton(MaterialButton button) {
+    public static void themeBorderlessButton(Button button, int primaryColor) {
         if (button == null) {
             return;
         }
 
         Context context = button.getContext();
-        int accentColor = ThemeUtils.primaryAccentColor(button.getContext());
         int disabledColor = ContextCompat.getColor(context, R.color.disabled_text);
         button.setTextColor(new ColorStateList(
             new int[][]{
@@ -583,7 +581,7 @@ public final class ThemeUtils {
                 new int[]{-android.R.attr.state_enabled}, // disabled
             },
             new int[]{
-                accentColor,
+                primaryColor,
                 disabledColor
             }
         ));

+ 35 - 9
src/main/res/layout/choose_template.xml

@@ -3,6 +3,8 @@
   Nextcloud Android client application
 
   @author Tobias Kaminsky
+  @author Andy Scherzinger
+  Copyright (C) 2020 Andy Scherzinger
   Copyright (C) 2018 Tobias Kaminsky
   Copyright (C) 2018 Nextcloud GmbH.
 
@@ -20,11 +22,22 @@
   along with this program. If not, see <https://www.gnu.org/licenses/>.
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:gravity="clip_horizontal"
-              android:orientation="vertical"
-              android:padding="@dimen/standard_padding">
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="clip_horizontal"
+    android:orientation="vertical"
+    android:paddingStart="@dimen/standard_padding"
+    android:paddingEnd="@dimen/standard_padding">
+
+    <TextView
+        android:id="@+id/helper_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingStart="@dimen/standard_half_padding"
+        android:paddingEnd="@dimen/standard_half_padding"
+        android:text="@string/choose_template_helper_text"
+        android:textColor="@color/secondary_text_color"
+        android:visibility="invisible" />
 
     <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/list"
@@ -32,11 +45,24 @@
         android:layout_height="0dp"
         android:layout_weight="1" />
 
-    <com.google.android.material.textfield.TextInputEditText
-        android:id="@+id/filename"
+    <com.google.android.material.textfield.TextInputLayout
+        android:id="@+id/filename_container"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:hint="@string/filename_hint"
-        android:inputType="text"
-        android:importantForAutofill="no" />
+        android:paddingTop="@dimen/standard_padding">
+
+        <com.google.android.material.textfield.TextInputEditText
+            android:id="@+id/filename"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ems="10"
+            android:gravity="top"
+            android:importantForAutofill="no"
+            android:inputType="text"
+            android:scrollbars="vertical">
+
+        </com.google.android.material.textfield.TextInputEditText>
+
+    </com.google.android.material.textfield.TextInputLayout>
 </LinearLayout>

+ 28 - 16
src/main/res/layout/template_button.xml

@@ -3,6 +3,8 @@
   Nextcloud Android client application
 
   @author Tobias Kaminsky
+  @author Andy Scherzinger
+  Copyright (C) 2020 Andy Scherzinger
   Copyright (C) 2018 Tobias Kaminsky
   Copyright (C) 2018 Nextcloud GmbH.
 
@@ -19,25 +21,35 @@
   You should have received a copy of the GNU General Public License
   along with this program. If not, see <https://www.gnu.org/licenses/>.
 -->
-
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              xmlns:tools="http://schemas.android.com/tools"
-              android:layout_width="match_parent"
-              android:layout_height="wrap_content"
-              android:orientation="vertical"
-              android:paddingTop="@dimen/standard_padding"
-              tools:ignore="UseCompoundDrawables">
-
-    <ImageView
-        android:id="@+id/thumbnail"
-        android:layout_width="@dimen/share_icon_size"
-        android:layout_height="@dimen/share_icon_size"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingTop="@dimen/standard_padding">
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/template_container"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         android:layout_gravity="center_horizontal"
-        android:contentDescription="@string/thumbnail"
-        android:src="@drawable/file_doc"/>
+        app:cardElevation="0dp"
+        app:strokeColor="@color/grey_200"
+        app:strokeWidth="2dp">
+
+        <ImageView
+            android:id="@+id/template"
+            android:layout_width="60dp"
+            android:layout_height="84dp"
+            android:layout_margin="@dimen/standard_margin"
+            android:contentDescription="@string/thumbnail"
+            android:src="@drawable/file_doc" />
+
+    </com.google.android.material.card.MaterialCardView>
 
     <TextView
-        android:id="@+id/name"
+        android:id="@+id/template_name"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
@@ -45,5 +57,5 @@
         android:gravity="center_horizontal"
         android:paddingTop="@dimen/standard_half_padding"
         android:textColor="@color/text_color"
-        tools:text="@tools:sample/lorem" />
+        tools:text="Template" />
 </LinearLayout>

+ 1 - 0
src/main/res/values-night/colors.xml

@@ -22,6 +22,7 @@
 
     <color name="text_color">#E3E3E3</color>
     <color name="text_color_inverse">#000000</color>
+    <color name="disabled_text">#ff6F6F6F</color>
     <color name="secondary_text_color">#A5A5A5</color>
     <color name="list_divider_background">#222222</color>
 

+ 3 - 0
src/main/res/values/strings.xml

@@ -954,4 +954,7 @@
     <string name="direct_login_failed">Login via direct link failed!</string>
     <string name="login_url_helper_text">The link to your %1$s web interface when you open it in the browser.</string>
     <string name="brute_force_delay">Delayed due to too many wrong attempts</string>
+    <string name="create">Create</string>
+    <string name="select_one_template">Please select one template</string>
+    <string name="choose_template_helper_text">Please choose a template and enter a file name.</string>
 </resources>