Browse Source

Direct editing support
- abstract EditorWebView
- support direct editing endpoint
Create new files via direct editing

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>

tobiasKaminsky 5 years ago
parent
commit
59585d0f5b
30 changed files with 1097 additions and 341 deletions
  1. 1 1
      settings.gradle
  2. 3 0
      src/main/java/com/nextcloud/client/appinfo/AppInfo.java
  3. 21 0
      src/main/java/com/nextcloud/client/appinfo/AppInfoImpl.java
  4. 5 0
      src/main/java/com/nextcloud/client/device/DeviceInfo.kt
  5. 4 0
      src/main/java/com/nextcloud/client/di/ComponentsModule.java
  6. 1 1
      src/main/java/com/owncloud/android/db/ProviderMeta.java
  7. 5 4
      src/main/java/com/owncloud/android/files/FetchTemplateOperation.java
  8. 91 71
      src/main/java/com/owncloud/android/files/FileMenuFilter.java
  9. 2 14
      src/main/java/com/owncloud/android/providers/FileContentProvider.java
  10. 29 45
      src/main/java/com/owncloud/android/ui/activity/EditorWebView.java
  11. 13 46
      src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.java
  12. 27 20
      src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt
  13. 6 4
      src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
  14. 155 0
      src/main/java/com/owncloud/android/ui/adapter/RichDocumentsTemplateAdapter.java
  15. 17 34
      src/main/java/com/owncloud/android/ui/adapter/TemplateAdapter.java
  16. 74 0
      src/main/java/com/owncloud/android/ui/asynctasks/RichDocumentsLoadUrlTask.java
  17. 12 20
      src/main/java/com/owncloud/android/ui/asynctasks/TextEditorLoadUrlTask.java
  18. 339 0
      src/main/java/com/owncloud/android/ui/dialog/ChooseRichDocumentsTemplateDialogFragment.java
  19. 112 57
      src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.java
  20. 5 1
      src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
  21. 7 0
      src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetActions.java
  22. 47 1
      src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java
  23. 32 15
      src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
  24. 8 4
      src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java
  25. 5 1
      src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java
  26. 3 1
      src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java
  27. 3 1
      src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java
  28. 51 0
      src/main/res/layout/file_list_actions_bottom_sheet_creator.xml
  29. 18 0
      src/main/res/layout/file_list_actions_bottom_sheet_fragment.xml
  30. 1 0
      src/main/res/values/strings.xml

+ 1 - 1
settings.gradle

@@ -1,2 +1,2 @@
 include ':'
-//include 'nextcloud-android-library'
+//include ':nextcloud-android-library'

+ 3 - 0
src/main/java/com/nextcloud/client/appinfo/AppInfo.java

@@ -19,6 +19,8 @@
  */
 package com.nextcloud.client.appinfo;
 
+import android.content.Context;
+
 /**
  * This class provides general, static information about application
  * build.
@@ -36,4 +38,5 @@ public interface AppInfo {
 
     boolean isDebugBuild();
 
+    String getAppVersion(Context context);
 }

+ 21 - 0
src/main/java/com/nextcloud/client/appinfo/AppInfoImpl.java

@@ -19,7 +19,12 @@
  */
 package com.nextcloud.client.appinfo;
 
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+
 import com.owncloud.android.BuildConfig;
+import com.owncloud.android.lib.common.utils.Log_OC;
 
 class AppInfoImpl implements AppInfo {
 
@@ -32,4 +37,20 @@ class AppInfoImpl implements AppInfo {
     public boolean isDebugBuild() {
         return BuildConfig.DEBUG;
     }
+
+    @Override
+    public String getAppVersion(Context context) {
+        try {
+            PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
+            if (pInfo != null) {
+                return pInfo.versionName;
+            } else {
+                return "n/a";
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            Log_OC.e(this, "Trying to get packageName", e.getCause());
+
+            return "n/a";
+        }
+    }
 }

+ 5 - 0
src/main/java/com/nextcloud/client/device/DeviceInfo.kt

@@ -29,8 +29,13 @@ import java.util.Locale
 class DeviceInfo {
     val vendor: String = Build.MANUFACTURER.toLowerCase(Locale.ROOT)
     val apiLevel: Int = Build.VERSION.SDK_INT
+    val androidVersion = Build.VERSION.RELEASE
 
     fun hasCamera(context: Context): Boolean {
         return context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
     }
+
+    fun editorSupported(): Boolean {
+        return apiLevel < Build.VERSION_CODES.LOLLIPOP
+    }
 }

+ 4 - 0
src/main/java/com/nextcloud/client/di/ComponentsModule.java

@@ -63,6 +63,7 @@ import com.owncloud.android.ui.activity.TextEditorWebView;
 import com.owncloud.android.ui.activity.UploadFilesActivity;
 import com.owncloud.android.ui.activity.UploadListActivity;
 import com.owncloud.android.ui.activity.UserInfoActivity;
+import com.owncloud.android.ui.dialog.ChooseRichDocumentsTemplateDialogFragment;
 import com.owncloud.android.ui.dialog.ChooseTemplateDialogFragment;
 import com.owncloud.android.ui.dialog.MultipleAccountsDialog;
 import com.owncloud.android.ui.fragment.ExtendedListFragment;
@@ -136,6 +137,9 @@ abstract class ComponentsModule {
     @ContributesAndroidInjector abstract FileDetailActivitiesFragment fileDetailActivitiesFragment();
     @ContributesAndroidInjector abstract FileDetailSharingFragment fileDetailSharingFragment();
     @ContributesAndroidInjector abstract ChooseTemplateDialogFragment chooseTemplateDialogFragment();
+
+    @ContributesAndroidInjector
+    abstract ChooseRichDocumentsTemplateDialogFragment chooseRichDocumentsTemplateDialogFragment();
     @ContributesAndroidInjector abstract PreviewImageFragment previewImageFragment();
     @ContributesAndroidInjector abstract ContactListFragment chooseContactListFragment();
     @ContributesAndroidInjector abstract PreviewMediaFragment previewMediaFragment();

+ 1 - 1
src/main/java/com/owncloud/android/db/ProviderMeta.java

@@ -31,7 +31,7 @@ import com.owncloud.android.MainApp;
  */
 public class ProviderMeta {
     public static final String DB_NAME = "filelist";
-    public static final int DB_VERSION = 52;
+    public static final int DB_VERSION = 53;
 
     private ProviderMeta() {
         // No instance

+ 5 - 4
src/main/java/com/owncloud/android/files/FetchTemplateOperation.java

@@ -25,7 +25,7 @@ import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.ui.dialog.ChooseTemplateDialogFragment;
+import com.owncloud.android.ui.dialog.ChooseRichDocumentsTemplateDialogFragment;
 
 import org.apache.commons.httpclient.HttpStatus;
 import org.apache.commons.httpclient.methods.GetMethod;
@@ -41,14 +41,14 @@ public class FetchTemplateOperation extends RemoteOperation {
     private static final int SYNC_CONNECTION_TIMEOUT = 5000;
     private static final String TEMPLATE_URL = "/ocs/v2.php/apps/richdocuments/api/v1/templates/";
 
-    private ChooseTemplateDialogFragment.Type type;
+    private ChooseRichDocumentsTemplateDialogFragment.Type type;
 
     // JSON node names
     private static final String NODE_OCS = "ocs";
     private static final String NODE_DATA = "data";
     private static final String JSON_FORMAT = "?format=json";
 
-    public FetchTemplateOperation(ChooseTemplateDialogFragment.Type type) {
+    public FetchTemplateOperation(ChooseRichDocumentsTemplateDialogFragment.Type type) {
         this.type = type;
     }
 
@@ -81,7 +81,8 @@ public class FetchTemplateOperation extends RemoteOperation {
                     templateArray.add(new Template(templateObject.getInt("id"),
                                                    templateObject.getString("name"),
                                                    templateObject.optString("preview"),
-                                                   Template.Type.valueOf(templateObject.getString("type")),
+                                                   Template.Type.valueOf(templateObject.getString("type")
+                                                                             .toUpperCase(Locale.ROOT)),
                                                    templateObject.getString("extension")));
                 }
 

+ 91 - 71
src/main/java/com/owncloud/android/files/FileMenuFilter.java

@@ -29,6 +29,8 @@ import android.view.Menu;
 import android.view.MenuItem;
 
 import com.google.gson.Gson;
+import com.nextcloud.client.account.User;
+import com.nextcloud.client.device.DeviceInfo;
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.ArbitraryDataProvider;
 import com.owncloud.android.datamodel.OCFile;
@@ -58,45 +60,62 @@ public class FileMenuFilter {
 
     private static final int SINGLE_SELECT_ITEMS = 1;
 
-    private int mNumberOfAllFiles;
-    private Collection<OCFile> mFiles;
-    private ComponentsGetter mComponentsGetter;
-    private Account mAccount;
-    private Context mContext;
-    private boolean mOverflowMenu;
+    private int numberOfAllFiles;
+    private Collection<OCFile> files;
+    private ComponentsGetter componentsGetter;
+    private Account account;
+    private Context context;
+    private boolean overflowMenu;
+    private DeviceInfo deviceInfo;
+    private User user;
 
     /**
      * Constructor
      *
      * @param numberOfAllFiles  Number of all displayed files
-     * @param targetFiles       Collection of {@link OCFile} file targets of the action to filter in the {@link Menu}.
+     * @param files             Collection of {@link OCFile} file targets of the action to filter in the {@link Menu}.
      * @param account           ownCloud {@link Account} holding targetFile.
-     * @param cg                Accessor to app components, needed to access synchronization services
+     * @param componentsGetter  Accessor to app components, needed to access synchronization services
      * @param context           Android {@link Context}, needed to access build setup resources.
      * @param overflowMenu      true if the overflow menu items are being filtered
      */
-    public FileMenuFilter(int numberOfAllFiles, Collection<OCFile> targetFiles, Account account,
-                          ComponentsGetter cg, Context context, boolean overflowMenu) {
-        mNumberOfAllFiles = numberOfAllFiles;
-        mFiles = targetFiles;
-        mAccount = account;
-        mComponentsGetter = cg;
-        mContext = context;
-        mOverflowMenu = overflowMenu;
+    public FileMenuFilter(int numberOfAllFiles,
+                          Collection<OCFile> files,
+                          Account account,
+                          ComponentsGetter componentsGetter,
+                          Context context,
+                          boolean overflowMenu,
+                          DeviceInfo deviceInfo,
+                          User user
+    ) {
+        this.numberOfAllFiles = numberOfAllFiles;
+        this.files = files;
+        this.account = account;
+        this.componentsGetter = componentsGetter;
+        this.context = context;
+        this.overflowMenu = overflowMenu;
+        this.deviceInfo = deviceInfo;
+        this.user = user;
     }
 
     /**
      * Constructor
      *
-     * @param targetFile        {@link OCFile} target of the action to filter in the {@link Menu}.
+     * @param file              {@link OCFile} target of the action to filter in the {@link Menu}.
      * @param account           ownCloud {@link Account} holding targetFile.
-     * @param cg                Accessor to app components, needed to access synchronization services
+     * @param componentsGetter  Accessor to app components, needed to access synchronization services
      * @param context           Android {@link Context}, needed to access build setup resources.
      * @param overflowMenu      true if the overflow menu items are being filtered
      */
-    public FileMenuFilter(OCFile targetFile, Account account, ComponentsGetter cg, Context context,
-                          boolean overflowMenu) {
-        this(1, Collections.singletonList(targetFile), account, cg, context, overflowMenu);
+    public FileMenuFilter(OCFile file,
+                          Account account,
+                          ComponentsGetter componentsGetter,
+                          Context context,
+                          boolean overflowMenu,
+                          DeviceInfo deviceInfo,
+                          User user
+    ) {
+        this(1, Collections.singletonList(file), account, componentsGetter, context, overflowMenu, deviceInfo, user);
     }
 
     /**
@@ -108,7 +127,7 @@ public class FileMenuFilter {
      * @param isMediaSupported      True is media playback is supported for this user
      */
     public void filter(Menu menu, boolean inSingleFileFragment, boolean isMediaSupported) {
-        if (mFiles == null || mFiles.isEmpty()) {
+        if (files == null || files.isEmpty()) {
             hideAll(menu);
         } else {
             List<Integer> toShow = new ArrayList<>();
@@ -177,7 +196,7 @@ public class FileMenuFilter {
                         boolean isMediaSupported,
                         Menu menu) {
         boolean synchronizing = anyFileSynchronizing();
-        OCCapability capability = mComponentsGetter.getStorageManager().getCapability(mAccount.name);
+        OCCapability capability = componentsGetter.getStorageManager().getCapability(account.name);
         boolean endToEndEncryptionEnabled = capability.getEndToEndEncryption().isTrue();
 
         filterEdit(toShow, toHide, capability);
@@ -202,8 +221,8 @@ public class FileMenuFilter {
 
     private void filterShareFile(List<Integer> toShow, List<Integer> toHide, OCCapability capability) {
         if (containsEncryptedFile() || (!isShareViaLinkAllowed() && !isShareWithUsersAllowed()) ||
-            !isSingleSelection() || !isShareApiEnabled(capability) || !mFiles.iterator().next().canReshare()
-            || mOverflowMenu) {
+            !isSingleSelection() || !isShareApiEnabled(capability) || !files.iterator().next().canReshare()
+            || overflowMenu) {
             toHide.add(R.id.action_send_share_file);
         } else {
             toShow.add(R.id.action_send_share_file);
@@ -219,7 +238,7 @@ public class FileMenuFilter {
     }
 
     private void filterFavorite(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
-        if (mFiles.isEmpty() || synchronizing || allFavorites()) {
+        if (files.isEmpty() || synchronizing || allFavorites()) {
             toHide.add(R.id.action_favorite);
         } else {
             toShow.add(R.id.action_favorite);
@@ -227,7 +246,7 @@ public class FileMenuFilter {
     }
 
     private void filterUnfavorite(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
-        if (mFiles.isEmpty() || synchronizing || allNotFavorites()) {
+        if (files.isEmpty() || synchronizing || allNotFavorites()) {
             toHide.add(R.id.action_unset_favorite);
         } else {
             toShow.add(R.id.action_unset_favorite);
@@ -235,7 +254,7 @@ public class FileMenuFilter {
     }
 
     private void filterEncrypt(List<Integer> toShow, List<Integer> toHide, boolean endToEndEncryptionEnabled) {
-        if (mFiles.isEmpty() || !isSingleSelection() || isSingleFile() || isEncryptedFolder()
+        if (files.isEmpty() || !isSingleSelection() || isSingleFile() || isEncryptedFolder()
                 || !endToEndEncryptionEnabled) {
             toHide.add(R.id.action_encrypted);
         } else {
@@ -244,7 +263,7 @@ public class FileMenuFilter {
     }
 
     private void filterUnsetEncrypted(List<Integer> toShow, List<Integer> toHide, boolean endToEndEncryptionEnabled) {
-        if (mFiles.isEmpty() || !isSingleSelection() || isSingleFile() || !isEncryptedFolder()
+        if (files.isEmpty() || !isSingleSelection() || isSingleFile() || !isEncryptedFolder()
                 || !endToEndEncryptionEnabled) {
             toHide.add(R.id.action_unset_encrypted);
         } else {
@@ -253,7 +272,7 @@ public class FileMenuFilter {
     }
 
     private void filterSetPictureAs(List<Integer> toShow, List<Integer> toHide) {
-        if (isSingleImage() && !MimeTypeUtil.isSVG(mFiles.iterator().next())) {
+        if (isSingleImage() && !MimeTypeUtil.isSVG(files.iterator().next())) {
             toShow.add(R.id.action_set_as_wallpaper);
         } else {
             toHide.add(R.id.action_set_as_wallpaper);
@@ -264,15 +283,15 @@ public class FileMenuFilter {
                             List<Integer> toHide,
                             OCCapability capability
     ) {
-        if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+        if (deviceInfo.editorSupported()) {
             toHide.add(R.id.action_edit);
             return;
         }
 
-        String mimeType = mFiles.iterator().next().getMimeType();
+        String mimeType = files.iterator().next().getMimeType();
 
-        if (isRichDocumentEditingSupported(capability, mimeType) || isEditorAvailable(mContext.getContentResolver(),
-                                                                                      mAccount,
+        if (isRichDocumentEditingSupported(capability, mimeType) || isEditorAvailable(context.getContentResolver(),
+                                                                                      user,
                                                                                       mimeType)) {
             toShow.add(R.id.action_edit);
         } else {
@@ -280,13 +299,14 @@ public class FileMenuFilter {
         }
     }
 
-    public static boolean isEditorAvailable(ContentResolver contentResolver, Account account, String mimeType) {
-        return getEditor(contentResolver, account, mimeType) != null;
+    public static boolean isEditorAvailable(ContentResolver contentResolver, User user, String mimeType) {
+        return getEditor(contentResolver, user, mimeType) != null;
     }
 
     @Nullable
-    public static Editor getEditor(ContentResolver contentResolver, Account account, String mimeType) {
-        String json = new ArbitraryDataProvider(contentResolver).getValue(account, ArbitraryDataProvider.DIRECT_EDITING);
+    public static Editor getEditor(ContentResolver contentResolver, User user, String mimeType) {
+        String json = new ArbitraryDataProvider(contentResolver).getValue(user.toPlatformAccount(),
+                                                                          ArbitraryDataProvider.DIRECT_EDITING);
 
         if (json.isEmpty()) {
             return null;
@@ -315,7 +335,7 @@ public class FileMenuFilter {
     }
 
     private void filterSync(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
-        if (mFiles.isEmpty() || (!anyFileDown() && !containsFolder()) || synchronizing) {
+        if (files.isEmpty() || (!anyFileDown() && !containsFolder()) || synchronizing) {
             toHide.add(R.id.action_sync_file);
         } else {
             toShow.add(R.id.action_sync_file);
@@ -323,7 +343,7 @@ public class FileMenuFilter {
     }
 
     private void filterCancelSync(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
-        if (mFiles.isEmpty() || !synchronizing) {
+        if (files.isEmpty() || !synchronizing) {
             toHide.add(R.id.action_cancel_sync);
         } else {
             toShow.add(R.id.action_cancel_sync);
@@ -344,7 +364,7 @@ public class FileMenuFilter {
             toHide.add(R.id.action_deselect_all_action_menu);
         } else {
             // Show only if at least one item is selected.
-            if (mFiles.isEmpty() || mOverflowMenu) {
+            if (files.isEmpty() || overflowMenu) {
                 toHide.add(R.id.action_deselect_all_action_menu);
             } else {
                 toShow.add(R.id.action_deselect_all_action_menu);
@@ -355,7 +375,7 @@ public class FileMenuFilter {
     private void filterSelectAll(List<Integer> toShow, List<Integer> toHide, boolean inSingleFileFragment) {
         if (!inSingleFileFragment) {
             // Show only if at least one item isn't selected.
-            if (mFiles.size() >= mNumberOfAllFiles || mOverflowMenu) {
+            if (files.size() >= numberOfAllFiles || overflowMenu) {
                 toHide.add(R.id.action_select_all_action_menu);
             } else {
                 toShow.add(R.id.action_select_all_action_menu);
@@ -367,7 +387,7 @@ public class FileMenuFilter {
     }
 
     private void filterRemove(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
-        if (mFiles.isEmpty() || synchronizing || containsEncryptedFolder()) {
+        if (files.isEmpty() || synchronizing || containsEncryptedFolder()) {
             toHide.add(R.id.action_remove_file);
         } else {
             toShow.add(R.id.action_remove_file);
@@ -375,7 +395,7 @@ public class FileMenuFilter {
     }
 
     private void filterMoveCopy(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
-        if (mFiles.isEmpty() || synchronizing || containsEncryptedFile() || containsEncryptedFolder()) {
+        if (files.isEmpty() || synchronizing || containsEncryptedFile() || containsEncryptedFolder()) {
             toHide.add(R.id.action_move);
             toHide.add(R.id.action_copy);
         } else {
@@ -393,7 +413,7 @@ public class FileMenuFilter {
     }
 
     private void filterDownload(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
-        if (mFiles.isEmpty() || containsFolder() || anyFileDown() || synchronizing) {
+        if (files.isEmpty() || containsFolder() || anyFileDown() || synchronizing) {
             toHide.add(R.id.action_download_file);
         } else {
             toShow.add(R.id.action_download_file);
@@ -401,7 +421,7 @@ public class FileMenuFilter {
     }
 
     private void filterStream(List<Integer> toShow, List<Integer> toHide, boolean isMediaSupported) {
-        if (mFiles.isEmpty() || !isSingleFile() || !isSingleMedia() || !isMediaSupported) {
+        if (files.isEmpty() || !isSingleFile() || !isSingleMedia() || !isMediaSupported) {
             toHide.add(R.id.action_stream_media);
         } else {
             toShow.add(R.id.action_stream_media);
@@ -410,10 +430,10 @@ public class FileMenuFilter {
 
     private boolean anyFileSynchronizing() {
         boolean synchronizing = false;
-        if (mComponentsGetter != null && !mFiles.isEmpty() && mAccount != null) {
-            OperationsServiceBinder opsBinder = mComponentsGetter.getOperationsServiceBinder();
-            FileUploaderBinder uploaderBinder = mComponentsGetter.getFileUploaderBinder();
-            FileDownloaderBinder downloaderBinder = mComponentsGetter.getFileDownloaderBinder();
+        if (componentsGetter != null && !files.isEmpty() && account != null) {
+            OperationsServiceBinder opsBinder = componentsGetter.getOperationsServiceBinder();
+            FileUploaderBinder uploaderBinder = componentsGetter.getFileUploaderBinder();
+            FileDownloaderBinder downloaderBinder = componentsGetter.getFileDownloaderBinder();
             synchronizing = anyFileSynchronizing(opsBinder) ||      // comparing local and remote
                             anyFileDownloading(downloaderBinder) ||
                             anyFileUploading(uploaderBinder);
@@ -424,8 +444,8 @@ public class FileMenuFilter {
     private boolean anyFileSynchronizing(OperationsServiceBinder opsBinder) {
         boolean synchronizing = false;
         if (opsBinder != null) {
-            for (Iterator<OCFile> iterator = mFiles.iterator(); !synchronizing && iterator.hasNext(); ) {
-                synchronizing = opsBinder.isSynchronizing(mAccount, iterator.next());
+            for (Iterator<OCFile> iterator = files.iterator(); !synchronizing && iterator.hasNext(); ) {
+                synchronizing = opsBinder.isSynchronizing(account, iterator.next());
             }
         }
         return synchronizing;
@@ -434,8 +454,8 @@ public class FileMenuFilter {
     private boolean anyFileDownloading(FileDownloaderBinder downloaderBinder) {
         boolean downloading = false;
         if (downloaderBinder != null) {
-            for (Iterator<OCFile> iterator = mFiles.iterator(); !downloading && iterator.hasNext(); ) {
-                downloading = downloaderBinder.isDownloading(mAccount, iterator.next());
+            for (Iterator<OCFile> iterator = files.iterator(); !downloading && iterator.hasNext(); ) {
+                downloading = downloaderBinder.isDownloading(account, iterator.next());
             }
         }
         return downloading;
@@ -444,8 +464,8 @@ public class FileMenuFilter {
     private boolean anyFileUploading(FileUploaderBinder uploaderBinder) {
         boolean uploading = false;
         if (uploaderBinder != null) {
-            for (Iterator<OCFile> iterator = mFiles.iterator(); !uploading && iterator.hasNext(); ) {
-                uploading = uploaderBinder.isUploading(mAccount, iterator.next());
+            for (Iterator<OCFile> iterator = files.iterator(); !uploading && iterator.hasNext(); ) {
+                uploading = uploaderBinder.isUploading(account, iterator.next());
             }
         }
         return uploading;
@@ -459,26 +479,26 @@ public class FileMenuFilter {
     }
 
     private boolean isShareWithUsersAllowed() {
-        return mContext != null &&
-                mContext.getResources().getBoolean(R.bool.share_with_users_feature);
+        return context != null &&
+            context.getResources().getBoolean(R.bool.share_with_users_feature);
     }
 
     private boolean isShareViaLinkAllowed() {
-        return mContext != null &&
-                mContext.getResources().getBoolean(R.bool.share_via_link_feature);
+        return context != null &&
+            context.getResources().getBoolean(R.bool.share_via_link_feature);
     }
 
     private boolean isSingleSelection() {
-        return mFiles.size() == SINGLE_SELECT_ITEMS;
+        return files.size() == SINGLE_SELECT_ITEMS;
     }
 
     private boolean isSingleFile() {
-        return isSingleSelection() && !mFiles.iterator().next().isFolder();
+        return isSingleSelection() && !files.iterator().next().isFolder();
     }
 
     private boolean isEncryptedFolder() {
         if (isSingleSelection()) {
-            OCFile file = mFiles.iterator().next();
+            OCFile file = files.iterator().next();
 
             return file.isFolder() && file.isEncrypted();
         } else {
@@ -487,16 +507,16 @@ public class FileMenuFilter {
     }
 
     private boolean isSingleImage() {
-        return isSingleSelection() && MimeTypeUtil.isImage(mFiles.iterator().next());
+        return isSingleSelection() && MimeTypeUtil.isImage(files.iterator().next());
     }
 
     private boolean isSingleMedia() {
-        OCFile file = mFiles.iterator().next();
+        OCFile file = files.iterator().next();
         return isSingleSelection() && (MimeTypeUtil.isVideo(file) || MimeTypeUtil.isAudio(file));
     }
 
     private boolean containsEncryptedFile() {
-        for (OCFile file : mFiles) {
+        for (OCFile file : files) {
             if (!file.isFolder() && file.isEncrypted()) {
                 return true;
             }
@@ -505,7 +525,7 @@ public class FileMenuFilter {
     }
 
     private boolean containsEncryptedFolder() {
-        for (OCFile file : mFiles) {
+        for (OCFile file : files) {
             if (file.isFolder() && file.isEncrypted()) {
                 return true;
             }
@@ -514,7 +534,7 @@ public class FileMenuFilter {
     }
 
     private boolean containsFolder() {
-        for (OCFile file : mFiles) {
+        for (OCFile file : files) {
             if (file.isFolder()) {
                 return true;
             }
@@ -523,7 +543,7 @@ public class FileMenuFilter {
     }
 
     private boolean anyFileDown() {
-        for (OCFile file : mFiles) {
+        for (OCFile file : files) {
             if (file.isDown()) {
                 return true;
             }
@@ -532,7 +552,7 @@ public class FileMenuFilter {
     }
 
     private boolean allFavorites() {
-        for (OCFile file : mFiles) {
+        for (OCFile file : files) {
             if (!file.isFavorite()) {
                 return false;
             }
@@ -541,7 +561,7 @@ public class FileMenuFilter {
     }
 
     private boolean allNotFavorites() {
-        for (OCFile file : mFiles) {
+        for (OCFile file : files) {
             if (file.isFavorite()) {
                 return false;
             }

+ 2 - 14
src/main/java/com/owncloud/android/providers/FileContentProvider.java

@@ -2085,8 +2085,8 @@ public class FileContentProvider extends ContentProvider {
                 Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
             }
 
-            if (oldVersion < 52 && newVersion >= 52) {
-                Log_OC.i(SQL, "Entering in the #52 add rich workspace to file table");
+            if (oldVersion < 53 && newVersion >= 53) {
+                Log_OC.i(SQL, "Entering in the #53 add rich workspace to file table");
                 db.beginTransaction();
                 try {
                     db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME +
@@ -2103,17 +2103,5 @@ public class FileContentProvider extends ContentProvider {
                 Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
             }
         }
-
-        @Override
-        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-            if (oldVersion == 25 && newVersion == 24) {
-                db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME +
-                               REMOVE_COLUMN + ProviderTableMeta.FILE_IS_ENCRYPTED);
-                db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME +
-                               REMOVE_COLUMN + ProviderTableMeta.FILE_ENCRYPTED_NAME);
-                db.execSQL(ALTER_TABLE + ProviderTableMeta.CAPABILITIES_TABLE_NAME +
-                               REMOVE_COLUMN + ProviderTableMeta.CAPABILITIES_END_TO_END_ENCRYPTION);
-            }
-        }
     }
 }

+ 29 - 45
src/main/java/com/owncloud/android/ui/activity/EditorWebView.java

@@ -27,7 +27,6 @@ import android.content.Intent;
 import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.os.Handler;
-import android.text.TextUtils;
 import android.view.View;
 import android.webkit.JavascriptInterface;
 import android.widget.ImageView;
@@ -39,8 +38,6 @@ import com.google.android.material.snackbar.Snackbar;
 import com.owncloud.android.R;
 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.asynctasks.LoadUrlTask;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.MimeTypeUtil;
 import com.owncloud.android.utils.ThemeUtils;
@@ -53,32 +50,30 @@ import lombok.Setter;
 
 public abstract class EditorWebView extends ExternalSiteWebView {
     @Getter @Setter protected Snackbar loadingSnackbar;
-    protected OCFile file;
+
+    protected String fileName;
+    protected String mimeType;
 
     @BindView(R.id.progressBar2)
     ProgressBar progressBar;
 
     @BindView(R.id.thumbnail)
-    ImageView thumbnail;
+    ImageView thumbnailView;
 
     @BindView(R.id.filename)
-    TextView fileName;
+    TextView fileNameTextView;
 
     private Unbinder unbinder;
 
     private static final String TAG = EditorWebView.class.getSimpleName();
 
-    protected void loadUrl(String url, OCFile file) {
-        if (TextUtils.isEmpty(url)) {
-            new LoadUrlTask(this, getAccount(), file).execute();
-        } else {
-            webview.loadUrl(url);
-        }
+    protected void loadUrl(String url) {
+        webview.loadUrl(url);
     }
 
     protected void hideLoading() {
-        thumbnail.setVisibility(View.GONE);
-        fileName.setVisibility(View.GONE);
+        thumbnailView.setVisibility(View.GONE);
+        fileNameTextView.setVisibility(View.GONE);
         progressBar.setVisibility(View.GONE);
         webview.setVisibility(View.VISIBLE);
 
@@ -127,19 +122,29 @@ public abstract class EditorWebView extends ExternalSiteWebView {
 
         unbinder = ButterKnife.bind(this);
 
-        file = getIntent().getParcelableExtra(ExternalSiteWebView.EXTRA_FILE);
+        setFile(getIntent().getParcelableExtra(ExternalSiteWebView.EXTRA_FILE));
+
+        if (getFile() == null) {
+            Toast.makeText(getApplicationContext(),
+                           R.string.richdocuments_failed_to_load_document, Toast.LENGTH_LONG).show();
+            finish();
+        }
+
+        if (getFile() != null) {
+            fileName = getFile().getFileName();
+        }
 
         initLoadingScreen();
     }
 
     protected void initLoadingScreen() {
-        setThumbnail(file, thumbnail);
-        fileName.setText(file.getFileName());
+        setThumbnailView();
+        fileNameTextView.setText(fileName);
     }
 
     private void openShareDialog() {
         Intent intent = new Intent(this, ShareActivity.class);
-        intent.putExtra(FileActivity.EXTRA_FILE, file);
+        intent.putExtra(FileActivity.EXTRA_FILE, getFile());
         intent.putExtra(FileActivity.EXTRA_ACCOUNT, getAccount());
         startActivity(intent);
     }
@@ -152,12 +157,15 @@ public abstract class EditorWebView extends ExternalSiteWebView {
         super.onDestroy();
     }
 
-    protected void setThumbnail(OCFile file, ImageView thumbnailView) {
+    protected void setThumbnailView() {
         // Todo minimize: only icon by mimetype
-
+        OCFile file = getFile();
         if (file.isFolder()) {
             thumbnailView.setImageDrawable(MimeTypeUtil.getFolderTypeIcon(file.isSharedWithMe() ||
-                                                                              file.isSharedWithSharee(), file.isSharedViaLink(), file.isEncrypted(), file.getMountType(),
+                                                                              file.isSharedWithSharee(),
+                                                                          file.isSharedViaLink(),
+                                                                          file.isEncrypted(),
+                                                                          file.getMountType(),
                                                                           this));
         } else {
             if ((MimeTypeUtil.isImage(file) || MimeTypeUtil.isVideo(file)) && file.getRemoteId() != null) {
@@ -172,30 +180,6 @@ public abstract class EditorWebView extends ExternalSiteWebView {
                     } else {
                         thumbnailView.setImageBitmap(thumbnail);
                     }
-                } else {
-                    // generate new thumbnail
-                    if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, thumbnailView)) {
-                        try {
-                            final ThumbnailsCacheManager.ThumbnailGenerationTask task =
-                                new ThumbnailsCacheManager.ThumbnailGenerationTask(thumbnailView,
-                                                                                   getStorageManager(), getAccount());
-
-                            if (thumbnail == null) {
-                                if (MimeTypeUtil.isVideo(file)) {
-                                    thumbnail = ThumbnailsCacheManager.mDefaultVideo;
-                                } else {
-                                    thumbnail = ThumbnailsCacheManager.mDefaultImg;
-                                }
-                            }
-                            final ThumbnailsCacheManager.AsyncThumbnailDrawable asyncDrawable =
-                                new ThumbnailsCacheManager.AsyncThumbnailDrawable(getResources(), thumbnail, task);
-                            thumbnailView.setImageDrawable(asyncDrawable);
-                            task.execute(new ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file,
-                                                                                                  file.getRemoteId()));
-                        } catch (IllegalArgumentException e) {
-                            Log_OC.d(TAG, "ThumbnailGenerationTask : " + e.getMessage());
-                        }
-                    }
                 }
 
                 if ("image/png".equalsIgnoreCase(file.getMimeType())) {

+ 13 - 46
src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.java

@@ -32,6 +32,7 @@ import android.content.Intent;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
+import android.text.TextUtils;
 import android.view.KeyEvent;
 import android.webkit.JavascriptInterface;
 import android.webkit.ValueCallback;
@@ -39,26 +40,23 @@ import android.webkit.WebChromeClient;
 import android.webkit.WebView;
 import android.widget.Toast;
 
-import com.bumptech.glide.Glide;
 import com.nextcloud.client.account.CurrentAccountProvider;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.network.ClientFactory;
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.datamodel.Template;
 import com.owncloud.android.lib.common.OwnCloudAccount;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.operations.RichDocumentsCreateAssetOperation;
 import com.owncloud.android.ui.asynctasks.PrintAsyncTask;
+import com.owncloud.android.ui.asynctasks.RichDocumentsLoadUrlTask;
 import com.owncloud.android.ui.fragment.OCFileListFragment;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.FileStorageUtils;
-import com.owncloud.android.utils.glide.CustomGlideStreamLoader;
 
 import org.json.JSONException;
 import org.json.JSONObject;
-import org.parceler.Parcels;
 
 import java.io.File;
 import java.lang.ref.WeakReference;
@@ -130,49 +128,9 @@ public class RichDocumentsEditorWebView extends EditorWebView {
         });
 
         // load url in background
-        loadUrl(getIntent().getStringExtra(EXTRA_URL), file);
+        loadUrl(getIntent().getStringExtra(EXTRA_URL));
     }
 
-    @Override
-    protected void initLoadingScreen() {
-        if (file == null) {
-            fileName.setText(R.string.create_file_from_template);
-
-            Template template = Parcels.unwrap(getIntent().getParcelableExtra(EXTRA_TEMPLATE));
-
-            int placeholder;
-
-            switch (template.getType()) {
-                case DOCUMENT:
-                    placeholder = R.drawable.file_doc;
-                    break;
-
-                case SPREADSHEET:
-                    placeholder = R.drawable.file_xls;
-                    break;
-
-                case PRESENTATION:
-                    placeholder = R.drawable.file_ppt;
-                    break;
-
-                default:
-                    placeholder = R.drawable.file;
-                    break;
-            }
-
-            Glide.with(this).using(new CustomGlideStreamLoader(currentAccountProvider, clientFactory))
-                .load(template.getThumbnailLink())
-                .placeholder(placeholder)
-                .error(placeholder)
-                .into(thumbnail);
-        } else {
-            setThumbnail(file, thumbnail);
-            fileName.setText(file.getFileName());
-        }
-    }
-
-
-
     @Override
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
@@ -292,6 +250,15 @@ public class RichDocumentsEditorWebView extends EditorWebView {
         downloadmanager.enqueue(request);
     }
 
+    @Override
+    protected void loadUrl(String url) {
+        if (TextUtils.isEmpty(url)) {
+            new RichDocumentsLoadUrlTask(this, getUser().get(), getFile()).execute();
+        } else {
+            super.loadUrl(url);
+        }
+    }
+
     private class RichDocumentsMobileInterface extends MobileInterface {
         @JavascriptInterface
         public void insertGraphic() {
@@ -328,7 +295,7 @@ public class RichDocumentsEditorWebView extends EditorWebView {
             try {
                 JSONObject renameJson = new JSONObject(renameString);
                 String newName = renameJson.getString(NEW_NAME);
-                file.setFileName(newName);
+                getFile().setFileName(newName);
             } catch (JSONException e) {
                 Log_OC.e(this, "Failed to parse rename json message: " + e);
             }

+ 27 - 20
src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt

@@ -21,24 +21,33 @@
 
 package com.owncloud.android.ui.activity
 
-import android.annotation.SuppressLint
-import android.content.pm.PackageManager.NameNotFoundException
 import android.os.Build
 import android.os.Bundle
+import android.widget.Toast
 import androidx.annotation.RequiresApi
+import com.nextcloud.client.appinfo.AppInfo
+import com.nextcloud.client.device.DeviceInfo
 import com.owncloud.android.R
 import com.owncloud.android.files.FileMenuFilter
-import com.owncloud.android.lib.common.utils.Log_OC
+import com.owncloud.android.ui.asynctasks.TextEditorLoadUrlTask
+import javax.inject.Inject
 
 @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
 class TextEditorWebView : EditorWebView() {
+    @Inject
+    lateinit var appInfo: AppInfo
+    @Inject
+    lateinit var deviceInfo: DeviceInfo
 
-    @SuppressLint("AddJavascriptInterface")
-    // suppress warning as webview is only used >= Lollipop
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
-        val editor = FileMenuFilter.getEditor(contentResolver, account, file.mimeType)
+        if (!user.isPresent) {
+            Toast.makeText(this, getString(R.string.failed_to_start_editor), Toast.LENGTH_LONG).show()
+            finish()
+        }
+
+        val editor = FileMenuFilter.getEditor(contentResolver, user.get(), file.mimeType)
 
         if (editor != null && editor.id == "onlyoffice") {
             webview.settings.userAgentString = generateOnlyOfficeUserAgent()
@@ -46,22 +55,20 @@ class TextEditorWebView : EditorWebView() {
 
         webview.addJavascriptInterface(MobileInterface(), "DirectEditingMobileInterface")
 
-        loadUrl(intent.getStringExtra(ExternalSiteWebView.EXTRA_URL), file)
+        loadUrl(intent.getStringExtra(ExternalSiteWebView.EXTRA_URL))
     }
 
-    private fun generateOnlyOfficeUserAgent(): String {
-        val appString = applicationContext.resources.getString(R.string.only_office_user_agent)
-        val packageName = applicationContext.packageName
-        val androidVersion = Build.VERSION.RELEASE
-        var appVersion = ""
-        try {
-            val pInfo = applicationContext.packageManager.getPackageInfo(packageName, 0)
-            if (pInfo != null) {
-                appVersion = pInfo.versionName
-            }
-        } catch (e: NameNotFoundException) {
-            Log_OC.e(this, "Trying to get packageName", e.cause)
+    override fun loadUrl(url: String?) {
+        if (url.isNullOrEmpty()) {
+            TextEditorLoadUrlTask(this, user.get(), file).execute()
+        } else {
+            super.loadUrl(url)
         }
-        return String.format(appString, androidVersion, appVersion)
+    }
+
+    private fun generateOnlyOfficeUserAgent(): String {
+        val userAgent = applicationContext.resources.getString(R.string.only_office_user_agent)
+
+        return String.format(userAgent, deviceInfo.androidVersion, appInfo.getAppVersion(this))
     }
 }

+ 6 - 4
src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java

@@ -673,11 +673,13 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
     }
 
     public OCFile getItem(int position) {
-        if (shouldShowHeader()) {
-            return mFiles.get(position - 1);
-        } else {
-            return mFiles.get(position);
+        int newPosition = position;
+
+        if (shouldShowHeader() && position > 0) {
+            newPosition = position - 1;
         }
+
+        return mFiles.get(newPosition);
     }
 
     private boolean shouldShowHeader() {

+ 155 - 0
src/main/java/com/owncloud/android/ui/adapter/RichDocumentsTemplateAdapter.java

@@ -0,0 +1,155 @@
+/*
+ *
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2019 Tobias Kaminsky
+ * Copyright (C) 2019 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.ui.adapter;
+
+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.datamodel.Template;
+import com.owncloud.android.ui.dialog.ChooseRichDocumentsTemplateDialogFragment;
+import com.owncloud.android.utils.NextcloudServer;
+import com.owncloud.android.utils.glide.CustomGlideStreamLoader;
+
+import java.util.ArrayList;
+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
+ */
+public class RichDocumentsTemplateAdapter extends RecyclerView.Adapter<RichDocumentsTemplateAdapter.ViewHolder> {
+
+    private List<Template> templateList = new ArrayList<>();
+    private ClickListener clickListener;
+    private Context context;
+    private ChooseRichDocumentsTemplateDialogFragment.Type type;
+    private CurrentAccountProvider currentAccountProvider;
+    private ClientFactory clientFactory;
+
+    public RichDocumentsTemplateAdapter(
+        ChooseRichDocumentsTemplateDialogFragment.Type type,
+        ClickListener clickListener,
+        Context context,
+        CurrentAccountProvider currentAccountProvider,
+        ClientFactory clientFactory
+    ) {
+        this.clickListener = clickListener;
+        this.type = type;
+        this.context = context;
+        this.currentAccountProvider = currentAccountProvider;
+        this.clientFactory = clientFactory;
+    }
+
+    @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));
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+        holder.setData(templateList.get(position));
+    }
+
+    public void setTemplateList(List<Template> templateList) {
+        this.templateList = templateList;
+    }
+
+    @Override
+    public int getItemCount() {
+        return templateList.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 Template template;
+
+        public ViewHolder(View itemView) {
+            super(itemView);
+            ButterKnife.bind(this, itemView);
+            itemView.setOnClickListener(this);
+        }
+
+        @Override
+        public void onClick(View v) {
+            if (clickListener != null) {
+                clickListener.onClick(template);
+            }
+        }
+
+        public void setData(Template template) {
+            this.template = template;
+
+            int placeholder;
+
+            switch (type) {
+                case DOCUMENT:
+                    placeholder = R.drawable.file_doc;
+                    break;
+
+                case SPREADSHEET:
+                    placeholder = R.drawable.file_xls;
+                    break;
+
+                case PRESENTATION:
+                    placeholder = R.drawable.file_ppt;
+                    break;
+
+                default:
+                    placeholder = R.drawable.file;
+                    break;
+            }
+
+            Glide.with(context).using(new CustomGlideStreamLoader(currentAccountProvider, clientFactory)).
+                load(template.getThumbnailLink())
+                .placeholder(placeholder)
+                .error(placeholder)
+                .into(thumbnail);
+
+            name.setText(template.getName());
+        }
+    }
+
+    public interface ClickListener {
+        void onClick(Template template);
+    }
+}

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

@@ -25,6 +25,7 @@
 package com.owncloud.android.ui.adapter;
 
 import android.content.Context;
+import android.graphics.drawable.Drawable;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -35,13 +36,11 @@ 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.datamodel.Template;
-import com.owncloud.android.ui.dialog.ChooseTemplateDialogFragment;
+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.glide.CustomGlideStreamLoader;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView;
 import butterknife.BindView;
@@ -52,22 +51,22 @@ import butterknife.ButterKnife;
  */
 public class TemplateAdapter extends RecyclerView.Adapter<TemplateAdapter.ViewHolder> {
 
-    private List<Template> templateList = new ArrayList<>();
+    private TemplateList templateList = new TemplateList();
     private ClickListener clickListener;
     private Context context;
-    private ChooseTemplateDialogFragment.Type type;
     private CurrentAccountProvider currentAccountProvider;
     private ClientFactory clientFactory;
+    private String mimetype;
 
     public TemplateAdapter(
-        ChooseTemplateDialogFragment.Type type,
+        String mimetype,
         ClickListener clickListener,
         Context context,
         CurrentAccountProvider currentAccountProvider,
         ClientFactory clientFactory
     ) {
+        this.mimetype = mimetype;
         this.clickListener = clickListener;
-        this.type = type;
         this.context = context;
         this.currentAccountProvider = currentAccountProvider;
         this.clientFactory = clientFactory;
@@ -81,20 +80,19 @@ public class TemplateAdapter extends RecyclerView.Adapter<TemplateAdapter.ViewHo
 
     @Override
     public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
-        holder.setData(templateList.get(position));
+        holder.setData(templateList.getTemplateList().get(position));
     }
 
-    public void setTemplateList(List<Template> templateList) {
+    public void setTemplateList(TemplateList templateList) {
         this.templateList = templateList;
     }
 
     @Override
     public int getItemCount() {
-        return templateList.size();
+        return templateList.getTemplateList().size();
     }
 
     public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
-
         @BindView(R.id.name)
         public TextView name;
 
@@ -119,33 +117,18 @@ public class TemplateAdapter extends RecyclerView.Adapter<TemplateAdapter.ViewHo
         public void setData(Template template) {
             this.template = template;
 
-            int placeholder;
-
-            switch (type) {
-                case DOCUMENT:
-                    placeholder = R.drawable.file_doc;
-                    break;
-
-                case SPREADSHEET:
-                    placeholder = R.drawable.file_xls;
-                    break;
-
-                case PRESENTATION:
-                    placeholder = R.drawable.file_ppt;
-                    break;
-
-                default:
-                    placeholder = R.drawable.file;
-                    break;
-            }
+            Drawable placeholder = MimeTypeUtil.getFileTypeIcon(mimetype,
+                                                                template.getTitle(),
+                                                                currentAccountProvider.getUser().toPlatformAccount(),
+                                                                context);
 
             Glide.with(context).using(new CustomGlideStreamLoader(currentAccountProvider, clientFactory))
-                    .load(template.getThumbnailLink())
+                .load(template.getPreview())
                     .placeholder(placeholder)
                     .error(placeholder)
                     .into(thumbnail);
 
-            name.setText(template.getName());
+            name.setText(template.getTitle());
         }
     }
 

+ 74 - 0
src/main/java/com/owncloud/android/ui/asynctasks/RichDocumentsLoadUrlTask.java

@@ -0,0 +1,74 @@
+/*
+ *
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2020 Tobias Kaminsky
+ * Copyright (C) 2020 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.asynctasks;
+
+import android.accounts.Account;
+import android.os.AsyncTask;
+
+import com.nextcloud.client.account.User;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.operations.RichDocumentsUrlOperation;
+import com.owncloud.android.ui.activity.EditorWebView;
+
+import java.lang.ref.WeakReference;
+
+public class RichDocumentsLoadUrlTask extends AsyncTask<Void, Void, String> {
+
+    private Account account;
+    private WeakReference<EditorWebView> editorWebViewWeakReference;
+    private OCFile file;
+
+    public RichDocumentsLoadUrlTask(EditorWebView editorWebView, User user, OCFile file) {
+        this.account = user.toPlatformAccount();
+        this.editorWebViewWeakReference = new WeakReference<>(editorWebView);
+        this.file = file;
+    }
+
+    @Override
+    protected String doInBackground(Void... voids) {
+        final EditorWebView editorWebView = editorWebViewWeakReference.get();
+
+        if (editorWebView == null) {
+            return "";
+        }
+
+        RemoteOperationResult result = new RichDocumentsUrlOperation(file.getLocalId()).execute(account, editorWebView);
+
+        if (!result.isSuccess()) {
+            return "";
+        }
+
+        return (String) result.getData().get(0);
+    }
+
+    @Override
+    protected void onPostExecute(String url) {
+        EditorWebView editorWebView = editorWebViewWeakReference.get();
+
+        if (editorWebView == null) {
+            return;
+        }
+
+        editorWebView.onUrlLoaded(url);
+    }
+}

+ 12 - 20
src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java → src/main/java/com/owncloud/android/ui/asynctasks/TextEditorLoadUrlTask.java

@@ -24,25 +24,25 @@ import android.accounts.Account;
 import android.os.AsyncTask;
 
 import com.nextcloud.android.lib.resources.directediting.DirectEditingOpenFileRemoteOperation;
+import com.nextcloud.client.account.User;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.files.FileMenuFilter;
 import com.owncloud.android.lib.common.Editor;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
-import com.owncloud.android.operations.RichDocumentsUrlOperation;
 import com.owncloud.android.ui.activity.EditorWebView;
-import com.owncloud.android.ui.activity.RichDocumentsEditorWebView;
-import com.owncloud.android.ui.activity.TextEditorWebView;
 
 import java.lang.ref.WeakReference;
 
-public class LoadUrlTask extends AsyncTask<Void, Void, String> {
+public class TextEditorLoadUrlTask extends AsyncTask<Void, Void, String> {
 
     private Account account;
     private WeakReference<EditorWebView> editorWebViewWeakReference;
     private OCFile file;
+    private User user;
 
-    public LoadUrlTask(EditorWebView editorWebView, Account account, OCFile file) {
-        this.account = account;
+    public TextEditorLoadUrlTask(EditorWebView editorWebView, User user, OCFile file) {
+        this.user = user;
+        this.account = user.toPlatformAccount();
         this.editorWebViewWeakReference = new WeakReference<>(editorWebView);
         this.file = file;
     }
@@ -55,24 +55,16 @@ public class LoadUrlTask extends AsyncTask<Void, Void, String> {
             return "";
         }
 
-        RemoteOperationResult result;
+        Editor editor = FileMenuFilter.getEditor(editorWebView.getContentResolver(), user, file.getMimeType());
 
-
-        if (editorWebView instanceof RichDocumentsEditorWebView) {
-            result = new RichDocumentsUrlOperation(file.getLocalId()).execute(account, editorWebView);
-        } else if (editorWebView instanceof TextEditorWebView) {
-            Editor editor = FileMenuFilter.getEditor(editorWebView.getContentResolver(), account, file.getMimeType());
-
-            if (editor == null) {
-                return "";
-            }
-
-            result = new DirectEditingOpenFileRemoteOperation(file.getRemotePath(), editor.id)
-                .execute(account, editorWebViewWeakReference.get());
-        } else {
+        if (editor == null) {
             return "";
         }
 
+        RemoteOperationResult result = new DirectEditingOpenFileRemoteOperation(file.getRemotePath(), editor.id)
+            .execute(account, editorWebViewWeakReference.get());
+
+
         if (!result.isSuccess()) {
             return "";
         }

+ 339 - 0
src/main/java/com/owncloud/android/ui/dialog/ChooseRichDocumentsTemplateDialogFragment.java

@@ -0,0 +1,339 @@
+/*
+ *
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2019 Tobias Kaminsky
+ * Copyright (C) 2019 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.ui.dialog;
+
+import android.accounts.Account;
+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 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.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.Template;
+import com.owncloud.android.files.CreateFileFromTemplateOperation;
+import com.owncloud.android.files.FetchTemplateOperation;
+import com.owncloud.android.lib.common.OwnCloudAccount;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
+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.RichDocumentsEditorWebView;
+import com.owncloud.android.ui.adapter.RichDocumentsTemplateAdapter;
+import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.FileStorageUtils;
+import com.owncloud.android.utils.NextcloudServer;
+import com.owncloud.android.utils.ThemeUtils;
+
+import org.parceler.Parcels;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+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,
+    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 = ".";
+
+    private RichDocumentsTemplateAdapter adapter;
+    private OCFile parentFolder;
+    private OwnCloudClient client;
+    @Inject CurrentAccountProvider currentAccount;
+    @Inject ClientFactory clientFactory;
+
+    public enum Type {
+        DOCUMENT,
+        SPREADSHEET,
+        PRESENTATION
+    }
+
+    @BindView(R.id.list)
+    RecyclerView listView;
+
+    @BindView(R.id.filename)
+    EditText fileName;
+
+    @NextcloudServer(max = 18) // will be removed in favor of generic direct editing
+    public static ChooseRichDocumentsTemplateDialogFragment newInstance(OCFile parentFolder, Type type) {
+        ChooseRichDocumentsTemplateDialogFragment frag = new ChooseRichDocumentsTemplateDialogFragment();
+        Bundle args = new Bundle();
+        args.putParcelable(ARG_PARENT_FOLDER, parentFolder);
+        args.putString(ARG_TYPE, type.name());
+        frag.setArguments(args);
+        return frag;
+
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        int color = ThemeUtils.primaryAccentColor(getContext());
+
+        AlertDialog alertDialog = (AlertDialog) getDialog();
+
+        alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(color);
+        alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(color);
+    }
+
+    @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");
+        }
+
+        int accentColor = ThemeUtils.primaryAccentColor(getContext());
+
+        parentFolder = arguments.getParcelable(ARG_PARENT_FOLDER);
+        Type type = Type.valueOf(arguments.getString(ARG_TYPE));
+
+        // 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 {
+            Account account = currentAccount.getCurrentAccount();
+            OwnCloudAccount ocAccount = new OwnCloudAccount(account, activity);
+            client = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, getContext());
+
+            new FetchTemplateTask(this, client).execute(type);
+        } catch (Exception e) {
+            Log_OC.e(TAG, "Loading stream url not possible: " + e);
+        }
+
+        listView.setHasFixedSize(true);
+        listView.setLayoutManager(new GridLayoutManager(activity, 2));
+        adapter = new RichDocumentsTemplateAdapter(type, this, getContext(), currentAccount, clientFactory);
+        listView.setAdapter(adapter);
+
+        // Build the dialog
+        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+        builder.setView(view)
+            .setNegativeButton(R.string.common_cancel, this)
+            .setTitle(ThemeUtils.getColoredTitle(getResources().getString(R.string.select_template), accentColor));
+        Dialog dialog = builder.create();
+
+        Window window = dialog.getWindow();
+
+        if (window != null) {
+            window.setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+        }
+
+        return dialog;
+    }
+
+    private void createFromTemplate(Template template, String path) {
+        new CreateFileFromTemplateTask(this, client, template, path, currentAccount.getUser()).execute();
+    }
+
+    public void setTemplateList(List<Template> templateList) {
+        adapter.setTemplateList(templateList);
+        adapter.notifyDataSetChanged();
+    }
+
+    @Override
+    public void onClick(Template template) {
+        String name = 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());
+        } else {
+            createFromTemplate(template, path);
+        }
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        // cancel is handled by dialog itself, no other button available
+    }
+
+    private static class CreateFileFromTemplateTask extends AsyncTask<Void, Void, String> {
+        private OwnCloudClient client;
+        private WeakReference<ChooseRichDocumentsTemplateDialogFragment> chooseTemplateDialogFragmentWeakReference;
+        private Template template;
+        private String path;
+        private User user;
+        private OCFile file;
+
+        CreateFileFromTemplateTask(ChooseRichDocumentsTemplateDialogFragment chooseTemplateDialogFragment,
+                                   OwnCloudClient client,
+                                   Template template,
+                                   String path,
+                                   User user
+        ) {
+            this.client = client;
+            this.chooseTemplateDialogFragmentWeakReference = new WeakReference<>(chooseTemplateDialogFragment);
+            this.template = template;
+            this.path = path;
+            this.user = user;
+        }
+
+        @Override
+        protected String doInBackground(Void... voids) {
+            RemoteOperationResult result = new CreateFileFromTemplateOperation(path, template.getId()).execute(client);
+
+            if (result.isSuccess()) {
+                // get file
+                RemoteOperationResult newFileResult = new ReadFileRemoteOperation(path).execute(client);
+
+                if (newFileResult.isSuccess()) {
+                    OCFile temp = FileStorageUtils.fillOCFile((RemoteFile) newFileResult.getData().get(0));
+
+                    if (chooseTemplateDialogFragmentWeakReference.get() != null) {
+                        FileDataStorageManager storageManager = new FileDataStorageManager(
+                            user.toPlatformAccount(),
+                            chooseTemplateDialogFragmentWeakReference.get().requireContext().getContentResolver());
+                        storageManager.saveFile(temp);
+                        file = storageManager.getFileByPath(path);
+
+                        return result.getData().get(0).toString();
+                    } else {
+                        return "";
+                    }
+                } else {
+                    return "";
+                }
+            } else {
+                return "";
+            }
+        }
+
+        @Override
+        protected void onPostExecute(String url) {
+            ChooseRichDocumentsTemplateDialogFragment fragment = chooseTemplateDialogFragmentWeakReference.get();
+
+            if (fragment != null && fragment.isAdded()) {
+                if (url.isEmpty()) {
+                    DisplayUtils.showSnackMessage(fragment.listView, "Error creating file from template");
+                } else {
+                    Intent collaboraWebViewIntent = new Intent(MainApp.getAppContext(), RichDocumentsEditorWebView.class);
+                    collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_TITLE, "Collabora");
+                    collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_URL, url);
+                    collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_FILE, file);
+                    collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_SHOW_SIDEBAR, false);
+                    collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_TEMPLATE, Parcels.wrap(template));
+                    fragment.startActivity(collaboraWebViewIntent);
+
+                    fragment.dismiss();
+                }
+            } else {
+                Log_OC.e(TAG, "Error creating file from template!");
+            }
+        }
+    }
+
+    private static class FetchTemplateTask extends AsyncTask<Type, Void, List<Template>> {
+
+        private OwnCloudClient client;
+        private WeakReference<ChooseRichDocumentsTemplateDialogFragment> chooseTemplateDialogFragmentWeakReference;
+
+        FetchTemplateTask(ChooseRichDocumentsTemplateDialogFragment chooseTemplateDialogFragment, OwnCloudClient client) {
+            this.client = client;
+            this.chooseTemplateDialogFragmentWeakReference = new WeakReference<>(chooseTemplateDialogFragment);
+        }
+
+        @Override
+        protected List<Template> doInBackground(Type... type) {
+            FetchTemplateOperation fetchTemplateOperation = new FetchTemplateOperation(type[0]);
+            RemoteOperationResult result = fetchTemplateOperation.execute(client);
+
+            if (!result.isSuccess()) {
+                return new ArrayList<>();
+            }
+
+            List<Template> templateList = new ArrayList<>();
+            for (Object object : result.getData()) {
+                templateList.add((Template) object);
+            }
+
+            return templateList;
+        }
+
+        @Override
+        protected void onPostExecute(List<Template> templateList) {
+            ChooseRichDocumentsTemplateDialogFragment fragment = chooseTemplateDialogFragmentWeakReference.get();
+
+            if (fragment != null) {
+                if (templateList.isEmpty()) {
+                    DisplayUtils.showSnackMessage(fragment.listView, R.string.error_retrieving_templates);
+                } else {
+                    fragment.setTemplateList(templateList);
+
+                    String name = DOT + templateList.get(0).getExtension();
+                    fragment.fileName.setText(name);
+                }
+            } else {
+                Log_OC.e(TAG, "Error streaming file: no previewMediaFragment!");
+            }
+        }
+    }
+}

+ 112 - 57
src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.java

@@ -38,30 +38,32 @@ import android.view.Window;
 import android.view.WindowManager.LayoutParams;
 import android.widget.EditText;
 
+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.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.datamodel.Template;
-import com.owncloud.android.files.CreateFileFromTemplateOperation;
-import com.owncloud.android.files.FetchTemplateOperation;
+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.RichDocumentsEditorWebView;
+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.ThemeUtils;
 
-import org.parceler.Parcels;
-
 import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
 
 import javax.inject.Inject;
 
@@ -80,15 +82,15 @@ public class ChooseTemplateDialogFragment extends DialogFragment implements Dial
     TemplateAdapter.ClickListener, Injectable {
 
     private static final String ARG_PARENT_FOLDER = "PARENT_FOLDER";
-    private static final String ARG_TYPE = "TYPE";
+    private static final String ARG_CREATOR = "CREATOR";
     private static final String TAG = ChooseTemplateDialogFragment.class.getSimpleName();
     private static final String DOT = ".";
 
     private TemplateAdapter adapter;
     private OCFile parentFolder;
-    private OwnCloudClient client;
-    @Inject CurrentAccountProvider currentUser;
     @Inject ClientFactory clientFactory;
+    private Creator creator;
+    @Inject CurrentAccountProvider currentAccount;
 
     public enum Type {
         DOCUMENT,
@@ -102,11 +104,11 @@ public class ChooseTemplateDialogFragment extends DialogFragment implements Dial
     @BindView(R.id.filename)
     EditText fileName;
 
-    public static ChooseTemplateDialogFragment newInstance(OCFile parentFolder, Type type) {
+    public static ChooseTemplateDialogFragment newInstance(OCFile parentFolder, Creator creator) {
         ChooseTemplateDialogFragment frag = new ChooseTemplateDialogFragment();
         Bundle args = new Bundle();
         args.putParcelable(ARG_PARENT_FOLDER, parentFolder);
-        args.putString(ARG_TYPE, type.name());
+        args.putParcelable(ARG_CREATOR, creator);
         frag.setArguments(args);
         return frag;
 
@@ -140,7 +142,7 @@ public class ChooseTemplateDialogFragment extends DialogFragment implements Dial
         int accentColor = ThemeUtils.primaryAccentColor(getContext());
 
         parentFolder = arguments.getParcelable(ARG_PARENT_FOLDER);
-        Type type = Type.valueOf(arguments.getString(ARG_TYPE));
+        creator = arguments.getParcelable(ARG_CREATOR);
 
         // Inflate the layout for the dialog
         LayoutInflater inflater = activity.getLayoutInflater();
@@ -151,17 +153,15 @@ public class ChooseTemplateDialogFragment extends DialogFragment implements Dial
         fileName.getBackground().setColorFilter(accentColor, PorterDuff.Mode.SRC_ATOP);
 
         try {
-            User user = currentUser.getUser();
-            client = clientFactory.create(user);
-
-            new FetchTemplateTask(this, client).execute(type);
+            User user = currentAccount.getUser();
+            new FetchTemplateTask(this, clientFactory, user, creator).execute();
         } catch (Exception e) {
             Log_OC.e(TAG, "Loading stream url not possible: " + e);
         }
 
         listView.setHasFixedSize(true);
         listView.setLayoutManager(new GridLayoutManager(activity, 2));
-        adapter = new TemplateAdapter(type, this, getContext(), currentUser, clientFactory);
+        adapter = new TemplateAdapter(creator.getMimetype(), this, getContext(), currentAccount, clientFactory);
         listView.setAdapter(adapter);
 
         // Build the dialog
@@ -181,10 +181,10 @@ public class ChooseTemplateDialogFragment extends DialogFragment implements Dial
     }
 
     private void createFromTemplate(Template template, String path) {
-        new CreateFileFromTemplateTask(this, client, template, path).execute();
+        new CreateFileFromTemplateTask(this, clientFactory, currentAccount.getUser(), template, path, creator).execute();
     }
 
-    public void setTemplateList(List<Template> templateList) {
+    public void setTemplateList(TemplateList templateList) {
         adapter.setTemplateList(templateList);
         adapter.notifyDataSetChanged();
     }
@@ -209,26 +209,69 @@ public class ChooseTemplateDialogFragment extends DialogFragment implements Dial
     }
 
     private static class CreateFileFromTemplateTask extends AsyncTask<Void, Void, String> {
-        private OwnCloudClient client;
+        private ClientFactory clientFactory;
         private WeakReference<ChooseTemplateDialogFragment> chooseTemplateDialogFragmentWeakReference;
         private Template template;
         private String path;
-
-        CreateFileFromTemplateTask(ChooseTemplateDialogFragment chooseTemplateDialogFragment, OwnCloudClient client,
-                                   Template template, String path) {
-            this.client = client;
+        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) {
-            RemoteOperationResult result = new CreateFileFromTemplateOperation(path, template.getId()).execute(client);
-
-            if (result.isSuccess()) {
-                return result.getData().get(0).toString();
-            } else {
+            try {
+                OwnCloudClient client = clientFactory.create(user);
+
+                RemoteOperationResult result =
+                    new DirectEditingCreateFileRemoteOperation(path,
+                                                               creator.getEditor(),
+                                                               creator.getId(),
+                                                               template.getTitle())
+                        .execute(client);
+
+                if (result.isSuccess()) {
+                    // get file
+                    RemoteOperationResult newFileResult = new ReadFileRemoteOperation(path)
+                            .execute(client);
+
+                    if (newFileResult.isSuccess()) {
+                        OCFile temp = FileStorageUtils.fillOCFile((RemoteFile) newFileResult.getData().get(0));
+
+                        if (chooseTemplateDialogFragmentWeakReference.get() != null) {
+                            FileDataStorageManager storageManager = new FileDataStorageManager(
+                                user.toPlatformAccount(),
+                                chooseTemplateDialogFragmentWeakReference.get().requireContext().getContentResolver());
+                            storageManager.saveFile(temp);
+                            file = storageManager.getFileByPath(path);
+
+                            return result.getData().get(0).toString();
+                        }
+                         else {
+                             return "";
+                        }
+                    } else {
+                        return "";
+                    }
+                } else {
+                    return "";
+                }
+            } catch (ClientFactory.CreationException e) {
+                Log_OC.e(TAG, "Error creating file from template!", e);
                 return "";
             }
         }
@@ -241,12 +284,13 @@ public class ChooseTemplateDialogFragment extends DialogFragment implements Dial
                 if (url.isEmpty()) {
                     DisplayUtils.showSnackMessage(fragment.listView, "Error creating file from template");
                 } else {
-                    Intent collaboraWebViewIntent = new Intent(MainApp.getAppContext(), RichDocumentsEditorWebView.class);
-                    collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_TITLE, "Collabora");
-                    collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_URL, url);
-                    collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_SHOW_SIDEBAR, false);
-                    collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_TEMPLATE, Parcels.wrap(template));
-                    fragment.startActivity(collaboraWebViewIntent);
+                    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 {
@@ -255,44 +299,55 @@ public class ChooseTemplateDialogFragment extends DialogFragment implements Dial
         }
     }
 
-    private static class FetchTemplateTask extends AsyncTask<Type, Void, List<Template>> {
+    private static class FetchTemplateTask extends AsyncTask<Void, Void, TemplateList> {
 
-        private OwnCloudClient client;
+        private User user;
+        private ClientFactory clientFactory;
         private WeakReference<ChooseTemplateDialogFragment> chooseTemplateDialogFragmentWeakReference;
-
-        FetchTemplateTask(ChooseTemplateDialogFragment chooseTemplateDialogFragment, OwnCloudClient client) {
-            this.client = client;
+        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 List<Template> doInBackground(Type... type) {
-            FetchTemplateOperation fetchTemplateOperation = new FetchTemplateOperation(type[0]);
-            RemoteOperationResult result = fetchTemplateOperation.execute(client);
+        protected TemplateList doInBackground(Void... voids) {
 
-            if (!result.isSuccess()) {
-                return new ArrayList<>();
-            }
+            try {
+                OwnCloudClient client = clientFactory.create(user);
+                RemoteOperationResult result = new DirectEditingObtainListOfTemplatesRemoteOperation(creator.getEditor(),
+                                                                                                     creator.getId())
+                    .execute(client);
 
-            List<Template> templateList = new ArrayList<>();
-            for (Object object : result.getData()) {
-                templateList.add((Template) object);
-            }
+                if (!result.isSuccess()) {
+                    return new TemplateList();
+                }
 
-            return templateList;
+                return (TemplateList) result.getSingleData();
+            } catch (ClientFactory.CreationException e) {
+                Log_OC.e(TAG, "Could not fetch template", e);
+
+                return new TemplateList();
+            }
         }
 
         @Override
-        protected void onPostExecute(List<Template> templateList) {
+        protected void onPostExecute(TemplateList templateList) {
             ChooseTemplateDialogFragment fragment = chooseTemplateDialogFragmentWeakReference.get();
 
-            if (fragment != null) {
-                if (templateList.isEmpty()) {
+            if (fragment != null && fragment.isAdded()) {
+                if (templateList.templates.isEmpty()) {
                     DisplayUtils.showSnackMessage(fragment.listView, R.string.error_retrieving_templates);
                 } else {
                     fragment.setTemplateList(templateList);
 
-                    String name = DOT + templateList.get(0).getExtension();
+                    String name = DOT + templateList.templates.values().iterator().next().getExtension();
                     fragment.fileName.setText(name);
                 }
             } else {

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

@@ -45,6 +45,7 @@ import android.widget.TextView;
 
 import com.google.android.material.tabs.TabLayout;
 import com.nextcloud.client.account.UserAccountManager;
+import com.nextcloud.client.device.DeviceInfo;
 import com.nextcloud.client.di.Injectable;
 import com.nextcloud.client.network.ConnectivityService;
 import com.nextcloud.client.preferences.AppPreferences;
@@ -152,6 +153,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
     @Inject AppPreferences preferences;
     @Inject ConnectivityService connectivityService;
     @Inject UserAccountManager accountManager;
+    @Inject DeviceInfo deviceInfo;
 
     /**
      * Public factory method to create new FileDetailFragment instances.
@@ -418,7 +420,9 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
                 currentAccount,
                 containerActivity,
                 getActivity(),
-                false
+                false,
+                deviceInfo,
+                accountManager.getUser()
             );
 
             mf.filter(menu,

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

@@ -20,6 +20,8 @@
 
 package com.owncloud.android.ui.fragment;
 
+import com.owncloud.android.lib.common.Creator;
+
 /**
  * Actions interface to be implemented by any class that makes use of
  * {@link com.owncloud.android.ui.fragment.OCFileListBottomSheetDialog}.
@@ -59,4 +61,9 @@ public interface OCFileListBottomSheetActions {
      * offers direct camera upload to the current folder.
      */
     void directCameraUpload();
+
+    /**
+     * open template selection for creator @link Creator
+     */
+    void showTemplate(Creator creator);
 }

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

@@ -20,19 +20,28 @@
 
 package com.owncloud.android.ui.fragment;
 
+import android.content.Context;
 import android.os.Build;
 import android.os.Bundle;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import com.google.android.material.bottomsheet.BottomSheetBehavior;
 import com.google.android.material.bottomsheet.BottomSheetDialog;
+import com.google.gson.Gson;
+import com.nextcloud.client.account.User;
 import com.nextcloud.client.device.DeviceInfo;
 import com.owncloud.android.R;
+import com.owncloud.android.datamodel.ArbitraryDataProvider;
+import com.owncloud.android.lib.common.Creator;
+import com.owncloud.android.lib.common.DirectEditing;
 import com.owncloud.android.lib.resources.status.OCCapability;
 import com.owncloud.android.ui.activity.FileActivity;
+import com.owncloud.android.utils.MimeTypeUtil;
 import com.owncloud.android.utils.ThemeUtils;
 
 import butterknife.BindView;
@@ -60,6 +69,9 @@ public class OCFileListBottomSheetDialog extends BottomSheetDialog {
     @BindView(R.id.templates)
     public View templates;
 
+    @BindView(R.id.creators)
+    public LinearLayout creators;
+
     @BindView(R.id.menu_direct_camera_upload)
     public View cameraView;
 
@@ -67,14 +79,17 @@ public class OCFileListBottomSheetDialog extends BottomSheetDialog {
     private OCFileListBottomSheetActions actions;
     private FileActivity fileActivity;
     private DeviceInfo deviceInfo;
+    private User user;
 
     public OCFileListBottomSheetDialog(FileActivity fileActivity,
                                        OCFileListBottomSheetActions actions,
-                                       DeviceInfo deviceInfo) {
+                                       DeviceInfo deviceInfo,
+                                       User user) {
         super(fileActivity);
         this.actions = actions;
         this.fileActivity = fileActivity;
         this.deviceInfo = deviceInfo;
+        this.user = user;
     }
 
     @Override
@@ -105,6 +120,37 @@ public class OCFileListBottomSheetDialog extends BottomSheetDialog {
             templates.setVisibility(View.VISIBLE);
         }
 
+        String json = new ArbitraryDataProvider(getContext().getContentResolver())
+            .getValue(user.toPlatformAccount(), ArbitraryDataProvider.DIRECT_EDITING);
+
+        if (!json.isEmpty()) {
+            DirectEditing directEditing = new Gson().fromJson(json, DirectEditing.class);
+
+            if (!directEditing.getCreators().isEmpty()) {
+                creators.setVisibility(View.VISIBLE);
+
+                LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+                for (Creator creator : directEditing.getCreators().values()) {
+                    View creatorView = vi.inflate(R.layout.file_list_actions_bottom_sheet_creator, null);
+                    ((TextView) creatorView.findViewById(R.id.creator_name)).setText(creator.getName());
+                    ImageView thumbnail = creatorView.findViewById(R.id.creator_thumbnail);
+
+                    thumbnail.setImageDrawable(MimeTypeUtil.getFileTypeIcon(creator.getMimetype(),
+                                                                            creator.getExtension(),
+                                                                            user.toPlatformAccount(),
+                                                                            getContext()));
+
+                    creatorView.setOnClickListener(v -> {
+                        actions.showTemplate(creator);
+                        dismiss();
+                    });
+
+                    creators.addView(creatorView);
+                }
+            }
+        }
+
         if (!deviceInfo.hasCamera(getContext())) {
             cameraView.setVisibility(View.GONE);
         }

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

@@ -58,6 +58,7 @@ import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.VirtualFolderType;
 import com.owncloud.android.files.FileMenuFilter;
+import com.owncloud.android.lib.common.Creator;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
@@ -74,6 +75,7 @@ import com.owncloud.android.ui.activity.OnEnforceableRefreshListener;
 import com.owncloud.android.ui.activity.ToolbarActivity;
 import com.owncloud.android.ui.activity.UploadFilesActivity;
 import com.owncloud.android.ui.adapter.OCFileListAdapter;
+import com.owncloud.android.ui.dialog.ChooseRichDocumentsTemplateDialogFragment;
 import com.owncloud.android.ui.dialog.ChooseTemplateDialogFragment;
 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
 import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
@@ -396,9 +398,11 @@ public class OCFileListFragment extends ExtendedListFragment implements
      */
     private void registerFabListener() {
         FileActivity activity = (FileActivity) getActivity();
-        getFabMain().setOnClickListener(v -> {
-            new OCFileListBottomSheetDialog(activity, this, deviceInfo).show();
-        });
+        getFabMain().setOnClickListener(v -> new OCFileListBottomSheetDialog(activity,
+                                                                             this,
+                                                                             deviceInfo,
+                                                                             accountManager.getUser())
+            .show());
     }
 
     @Override
@@ -470,7 +474,9 @@ public class OCFileListFragment extends ExtendedListFragment implements
                                                Collections.singleton(file),
                                                currentAccount,
                                                mContainerActivity, getActivity(),
-                                               true);
+                                               true,
+                                               deviceInfo,
+                                               accountManager.getUser());
         mf.filter(popup.getMenu(),
                   true,
                   accountManager.isMediaStreamingSupported(currentAccount));
@@ -484,19 +490,22 @@ public class OCFileListFragment extends ExtendedListFragment implements
 
     @Override
     public void newDocument() {
-        ChooseTemplateDialogFragment.newInstance(mFile, ChooseTemplateDialogFragment.Type.DOCUMENT)
+        ChooseRichDocumentsTemplateDialogFragment.newInstance(mFile,
+                                                              ChooseRichDocumentsTemplateDialogFragment.Type.DOCUMENT)
                 .show(requireActivity().getSupportFragmentManager(), DIALOG_CREATE_DOCUMENT);
     }
 
     @Override
     public void newSpreadsheet() {
-        ChooseTemplateDialogFragment.newInstance(mFile, ChooseTemplateDialogFragment.Type.SPREADSHEET)
+        ChooseRichDocumentsTemplateDialogFragment.newInstance(mFile,
+                                                              ChooseRichDocumentsTemplateDialogFragment.Type.SPREADSHEET)
                 .show(requireActivity().getSupportFragmentManager(), DIALOG_CREATE_DOCUMENT);
     }
 
     @Override
     public void newPresentation() {
-        ChooseTemplateDialogFragment.newInstance(mFile, ChooseTemplateDialogFragment.Type.PRESENTATION)
+        ChooseRichDocumentsTemplateDialogFragment.newInstance(mFile,
+                                                              ChooseRichDocumentsTemplateDialogFragment.Type.PRESENTATION)
                 .show(requireActivity().getSupportFragmentManager(), DIALOG_CREATE_DOCUMENT);
     }
 
@@ -505,6 +514,12 @@ public class OCFileListFragment extends ExtendedListFragment implements
         ((FileDisplayActivity) mContainerActivity).startRichWorkspacePreview(getCurrentFile());
     }
 
+    @Override
+    public void showTemplate(Creator creator) {
+        ChooseTemplateDialogFragment.newInstance(mFile, creator).show(requireActivity().getSupportFragmentManager(),
+                                                                      DIALOG_CREATE_DOCUMENT);
+    }
+
     /**
      * Handler for multiple selection mode.
      * <p>
@@ -614,12 +629,14 @@ public class OCFileListFragment extends ExtendedListFragment implements
             mode.setTitle(title);
             Account currentAccount = ((FileActivity) getActivity()).getAccount();
             FileMenuFilter mf = new FileMenuFilter(
-                    mAdapter.getFiles().size(),
-                    checkedFiles,
-                    currentAccount,
-                    mContainerActivity,
-                    getActivity(),
-                    false
+                mAdapter.getFiles().size(),
+                checkedFiles,
+                currentAccount,
+                mContainerActivity,
+                getActivity(),
+                false,
+                deviceInfo,
+                accountManager.getUser()
             );
 
             mf.filter(menu,
@@ -954,7 +971,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
                             // stream media preview on >= NC14
                             ((FileDisplayActivity) mContainerActivity).startMediaPreview(file, 0, true, true, true);
                         } else if (FileMenuFilter.isEditorAvailable(requireContext().getContentResolver(),
-                                                                    account.toPlatformAccount(),
+                                                                    accountManager.getUser(),
                                                                     file.getMimeType()) &&
                             android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                             mContainerActivity.getFileOperationsHelper().openFileWithTextEditor(file, getContext());
@@ -1030,7 +1047,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
                     // should not be necessary, as menu item is filtered, but better play safe
                     if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                         if (FileMenuFilter.isEditorAvailable(requireContext().getContentResolver(),
-                                                             account,
+                                                             accountManager.getUser(),
                                                              singleFile.getMimeType())) {
                             mContainerActivity.getFileOperationsHelper().openFileWithTextEditor(singleFile,
                                                                                                 getContext());

+ 8 - 4
src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java

@@ -54,6 +54,7 @@ import com.caverock.androidsvg.SVGParseException;
 import com.github.chrisbanes.photoview.PhotoView;
 import com.google.android.material.snackbar.Snackbar;
 import com.nextcloud.client.account.UserAccountManager;
+import com.nextcloud.client.device.DeviceInfo;
 import com.nextcloud.client.di.Injectable;
 import com.nextcloud.client.network.ConnectivityService;
 import com.owncloud.android.MainApp;
@@ -127,6 +128,7 @@ public class PreviewImageFragment extends FileFragment implements Injectable {
 
     @Inject ConnectivityService connectivityService;
     @Inject UserAccountManager accountManager;
+    @Inject DeviceInfo deviceInfo;
 
     /**
      * Public factory method to create a new fragment that previews an image.
@@ -331,11 +333,13 @@ public class PreviewImageFragment extends FileFragment implements Injectable {
 
             Account currentAccount = containerActivity.getStorageManager().getAccount();
             FileMenuFilter mf = new FileMenuFilter(
-                    getFile(),
-                    currentAccount,
+                getFile(),
+                currentAccount,
                 containerActivity,
-                    getActivity(),
-                    false
+                getActivity(),
+                false,
+                deviceInfo,
+                accountManager.getUser()
             );
 
             mf.filter(menu,

+ 5 - 1
src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java

@@ -53,6 +53,7 @@ import android.widget.TextView;
 import android.widget.VideoView;
 
 import com.nextcloud.client.account.UserAccountManager;
+import com.nextcloud.client.device.DeviceInfo;
 import com.nextcloud.client.di.Injectable;
 import com.nextcloud.client.media.ErrorFormat;
 import com.nextcloud.client.media.PlayerServiceConnection;
@@ -125,6 +126,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
 
     private Uri mVideoUri;
     @Inject UserAccountManager accountManager;
+    @Inject DeviceInfo deviceInfo;
 
     /**
      * Creates a fragment to preview a file.
@@ -348,7 +350,9 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
                 currentAccount,
                 containerActivity,
                 getActivity(),
-                false
+                false,
+                deviceInfo,
+                accountManager.getUser()
             );
 
             mf.filter(menu,

+ 3 - 1
src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java

@@ -263,7 +263,9 @@ public class PreviewTextFileFragment extends PreviewTextFragment {
                 currentAccount,
                 containerActivity,
                 getActivity(),
-                false
+                false,
+                deviceInfo,
+                accountManager.getUser()
             );
             mf.filter(menu,
                       true,

+ 3 - 1
src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java

@@ -40,6 +40,7 @@ import android.widget.RelativeLayout;
 import android.widget.TextView;
 
 import com.nextcloud.client.account.UserAccountManager;
+import com.nextcloud.client.device.DeviceInfo;
 import com.nextcloud.client.di.Injectable;
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.OCFile;
@@ -95,6 +96,7 @@ public abstract class PreviewTextFragment extends FileFragment implements Search
     private ProgressBar mMultiListProgress;
 
     @Inject UserAccountManager accountManager;
+    @Inject DeviceInfo deviceInfo;
 
     /**
      * {@inheritDoc}
@@ -172,7 +174,7 @@ public abstract class PreviewTextFragment extends FileFragment implements Search
                         mTextPreview.setText(Html.fromHtml(coloredText.replace("\n", "<br \\>")));
                     }
                 } else {
-                    setText(mTextPreview, mOriginalText, getFile(), requireActivity());
+                    setText(mTextPreview, mOriginalText, getFile(), getActivity());
                 }
             }, delay);
         }

+ 51 - 0
src/main/res/layout/file_list_actions_bottom_sheet_creator.xml

@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~
+  ~ Nextcloud Android client application
+  ~
+  ~ @author Tobias Kaminsky
+  ~ Copyright (C) 2019 Tobias Kaminsky
+  ~ Copyright (C) 2019 Nextcloud GmbH
+  ~
+  ~ This program is free software: you can redistribute it and/or modify
+  ~ it under the terms of the GNU Affero 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 Affero General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU Affero 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="horizontal"
+    android:paddingLeft="@dimen/standard_padding"
+    android:paddingTop="@dimen/standard_half_padding"
+    android:paddingRight="@dimen/standard_padding"
+    android:paddingBottom="@dimen/standard_padding"
+    tools:ignore="UseCompoundDrawables">
+
+    <ImageView
+        android:id="@+id/creator_thumbnail"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:contentDescription="@null"
+        android:src="@drawable/file_ppt" />
+
+    <TextView
+        android:id="@+id/creator_name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginStart="@dimen/standard_margin"
+        android:layout_marginLeft="@dimen/standard_margin"
+        android:text="@string/create_new_presentation"
+        android:textColor="@color/text_color"
+        android:textSize="@dimen/bottom_sheet_text_size" />
+</LinearLayout>

+ 18 - 0
src/main/res/layout/file_list_actions_bottom_sheet_fragment.xml

@@ -281,4 +281,22 @@
         </LinearLayout>
     </LinearLayout>
 
+    <LinearLayout
+        android:id="@+id/creators"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:visibility="gone">
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:layout_marginLeft="@dimen/standard_margin"
+            android:layout_marginTop="@dimen/standard_half_margin"
+            android:layout_marginRight="@dimen/standard_margin"
+            android:layout_marginBottom="@dimen/standard_half_margin"
+            android:background="@color/list_divider_background" />
+
+    </LinearLayout>
+
 </LinearLayout>

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

@@ -908,4 +908,5 @@
     <string name="file">file</string>
     <string name="share_internal_link">Share internal link</string>
     <string name="action_edit">Edit</string>
+    <string name="failed_to_start_editor">Failed to start editor</string>
 </resources>