Kaynağa Gözat

Add support for richdocuments

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
tobiasKaminsky 6 yıl önce
ebeveyn
işleme
3a7ef82632
32 değiştirilmiş dosya ile 2065 ekleme ve 147 silme
  1. 5 5
      build.gradle
  2. 9 0
      src/main/AndroidManifest.xml
  3. 12 0
      src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java
  4. 71 0
      src/main/java/com/owncloud/android/datamodel/Template.java
  5. 3 2
      src/main/java/com/owncloud/android/db/ProviderMeta.java
  6. 95 0
      src/main/java/com/owncloud/android/files/CreateFileFromTemplateOperation.java
  7. 105 0
      src/main/java/com/owncloud/android/files/FetchTemplateOperation.java
  8. 11 0
      src/main/java/com/owncloud/android/files/FileMenuFilter.java
  9. 91 0
      src/main/java/com/owncloud/android/operations/RichDocumentsCreateAssetOperation.java
  10. 91 0
      src/main/java/com/owncloud/android/operations/RichDocumentsUrlOperation.java
  11. 23 1
      src/main/java/com/owncloud/android/providers/FileContentProvider.java
  12. 28 19
      src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java
  13. 64 0
      src/main/java/com/owncloud/android/ui/activity/FilePickerActivity.java
  14. 69 52
      src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java
  15. 403 0
      src/main/java/com/owncloud/android/ui/activity/RichDocumentsWebView.java
  16. 19 4
      src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
  17. 139 0
      src/main/java/com/owncloud/android/ui/adapter/TemplateAdapter.java
  18. 298 0
      src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.java
  19. 1 3
      src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java
  20. 15 0
      src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetActions.java
  21. 32 3
      src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java
  22. 84 39
      src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
  23. 23 1
      src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java
  24. 0 3
      src/main/java/com/owncloud/android/utils/DisplayUtils.java
  25. 43 0
      src/main/res/layout/choose_template.xml
  26. 3 2
      src/main/res/layout/externalsite_webview.xml
  27. 117 12
      src/main/res/layout/file_list_actions_bottom_sheet_fragment.xml
  28. 67 0
      src/main/res/layout/files_picker.xml
  29. 79 0
      src/main/res/layout/richdocuments_webview.xml
  30. 48 0
      src/main/res/layout/template_button.xml
  31. 6 0
      src/main/res/menu/file_actions_menu.xml
  32. 11 1
      src/main/res/values/strings.xml

+ 5 - 5
build.gradle

@@ -96,9 +96,9 @@ android {
         if (versionBuild > 98) {
             versionName "${versionMajor}.${versionMinor}.${versionPatch}"
         } else if (versionBuild > 50) {
-            versionName "${versionMajor}.${versionMinor}.${versionPatch} RC"+(versionBuild-50)
+            versionName "${versionMajor}.${versionMinor}.${versionPatch} RC" + (versionBuild - 50)
         } else {
-            versionName "${versionMajor}.${versionMinor}.${versionPatch} Alpha"+(versionBuild+1)
+            versionName "${versionMajor}.${versionMinor}.${versionPatch} Alpha" + (versionBuild + 1)
         }
 
         // adapt structure from Eclipse to Gradle/Android Studio expectations;
@@ -211,7 +211,7 @@ dependencies {
 //    implementation project('nextcloud-android-library')
     genericImplementation "com.github.nextcloud:android-library:master-SNAPSHOT"
     gplayImplementation "com.github.nextcloud:android-library:master-SNAPSHOT"
-    versionDevImplementation "com.github.nextcloud:android-library:master-SNAPSHOT" // use always latest master
+    versionDevImplementation "com.github.nextcloud:android-library:master-SNAPSHOT"
     implementation 'com.android.support.constraint:constraint-layout:1.1.3'
     implementation "com.android.support:support-v4:${supportLibraryVersion}"
     implementation "com.android.support:design:${supportLibraryVersion}"
@@ -236,7 +236,7 @@ dependencies {
 
     implementation 'org.parceler:parceler-api:1.1.11'
     annotationProcessor 'org.parceler:parceler:1.1.11'
-    implementation ('com.github.bumptech.glide:glide:3.7.0') {
+    implementation('com.github.bumptech.glide:glide:3.7.0') {
         exclude group: "com.android.support"
     }
     implementation 'com.caverock:androidsvg:1.3'
@@ -309,6 +309,6 @@ task combinedTestReport(type: JacocoReport) {
     sourceDirectories = files([mainSrc])
     classDirectories = files([debugTree])
     executionData = fileTree(dir: project.buildDir, includes: [
-            'jacoco/testGplayDebugUnitTest.exec', 'outputs/code-coverage/connected/flavors/GPLAY/*coverage.ec'
+        'jacoco/testGplayDebugUnitTest.exec', 'outputs/code-coverage/connected/flavors/GPLAY/*coverage.ec'
     ])
 }

+ 9 - 0
src/main/AndroidManifest.xml

@@ -109,6 +109,9 @@
         <activity android:name=".ui.activity.UploadFilesActivity" />
         <activity android:name=".ui.activity.ExternalSiteWebView"
                   android:configChanges="orientation|screenSize|keyboardHidden" />
+        <activity
+            android:name=".ui.activity.RichDocumentsWebView"
+            android:configChanges="orientation|screenSize|keyboardHidden"/>
         <activity android:name=".ui.activity.ContactsPreferenceActivity"
             android:launchMode="singleInstance"/>
         <activity android:name=".ui.activity.ReceiveExternalFilesActivity"
@@ -282,6 +285,12 @@
         <activity
             android:name=".ui.activity.FolderPickerActivity"
             android:label="@string/app_name" />
+        <activity
+            android:name=".ui.activity.FilePickerActivity"
+            android:label="@string/app_name"
+            android:theme="@style/Theme.ownCloud.Dialog.NoTitle"
+            android:launchMode="singleTop"
+            android:windowSoftInputMode="adjustResize"/>
         <activity
             android:name=".ui.activity.UploadPathActivity"
             android:label="@string/app_name" />

+ 12 - 0
src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java

@@ -34,6 +34,7 @@ import android.net.Uri;
 import android.os.RemoteException;
 import android.provider.MediaStore;
 import android.support.annotation.Nullable;
+import android.text.TextUtils;
 
 import com.owncloud.android.MainApp;
 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
@@ -53,6 +54,7 @@ import com.owncloud.android.utils.MimeTypeUtil;
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -1929,6 +1931,9 @@ public class FileDataStorageManager {
         cv.put(ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_PLAIN, capability.getServerBackgroundPlain()
                 .getValue());
         cv.put(ProviderTableMeta.CAPABILITIES_ACTIVITY, capability.isActivityEnabled().getValue());
+        cv.put(ProviderTableMeta.CAPABILITIES_RICHDOCUMENT, capability.getRichDocuments().getValue());
+        cv.put(ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_MIMETYPE_LIST,
+                TextUtils.join(",", capability.getRichDocumentsMimeTypeList()));
 
         if (capabilityExists(account.name)) {
             if (getContentResolver() != null) {
@@ -2084,6 +2089,13 @@ public class FileDataStorageManager {
                     c.getInt(c.getColumnIndex(ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_PLAIN))));
             capability.setActivity(CapabilityBooleanType.fromValue(
                     c.getInt(c.getColumnIndex(ProviderTableMeta.CAPABILITIES_ACTIVITY))));
+            capability.setRichDocuments(CapabilityBooleanType.fromValue(c.getInt(
+                    c.getColumnIndex(ProviderTableMeta.CAPABILITIES_RICHDOCUMENT))));
+            String mimetypes = c.getString(c.getColumnIndex(ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_MIMETYPE_LIST));
+            if (mimetypes == null) {
+                mimetypes = "";
+            }
+            capability.setRichDocumentsMimeTypeList(Arrays.asList(mimetypes.split(",")));
         }
         return capability;
     }

+ 71 - 0
src/main/java/com/owncloud/android/datamodel/Template.java

@@ -0,0 +1,71 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2018 Tobias Kaminsky
+ * Copyright (C) 2018 Nextcloud GmbH.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.datamodel;
+
+import org.parceler.Parcel;
+import org.parceler.ParcelConstructor;
+
+/**
+ * Template for creating a file from it via RichDocuments app
+ */
+
+@Parcel
+public class Template {
+
+    private int id;
+    private String name;
+    private String thumbnailLink;
+    private String type;
+    private String extension;
+
+    @ParcelConstructor
+    public Template(int id, String name, String thumbnailLink, String type, String extension) {
+        this.id = id;
+        this.name = name;
+        this.thumbnailLink = thumbnailLink;
+        this.type = type;
+        this.extension = extension;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getThumbnailLink() {
+        return thumbnailLink;
+    }
+
+    public String getExtension() {
+        return extension;
+    }
+
+    public void setExtension(String extension) {
+        this.extension = extension;
+    }
+}

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

@@ -32,7 +32,7 @@ import com.owncloud.android.MainApp;
 public class ProviderMeta {
 
     public static final String DB_NAME = "filelist";
-    public static final int DB_VERSION = 37;
+    public static final int DB_VERSION = 38;
 
     private ProviderMeta() {
     }
@@ -173,9 +173,10 @@ public class ProviderMeta {
         public static final String CAPABILITIES_SERVER_SLOGAN = "server_slogan";
         public static final String CAPABILITIES_SERVER_BACKGROUND_DEFAULT = "background_default";
         public static final String CAPABILITIES_SERVER_BACKGROUND_PLAIN = "background_plain";
-
         public static final String CAPABILITIES_END_TO_END_ENCRYPTION = "end_to_end_encryption";
         public static final String CAPABILITIES_ACTIVITY = "activity";
+        public static final String CAPABILITIES_RICHDOCUMENT = "richdocument";
+        public static final String CAPABILITIES_RICHDOCUMENT_MIMETYPE_LIST = "richdocument_mimetype_list";
 
         public static final String CAPABILITIES_DEFAULT_SORT_ORDER = CAPABILITIES_ACCOUNT_NAME
                 + " collate nocase asc";

+ 95 - 0
src/main/java/com/owncloud/android/files/CreateFileFromTemplateOperation.java

@@ -0,0 +1,95 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2018 Tobias Kaminsky
+ * Copyright (C) 2018 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 <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.files;
+
+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 org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+
+public class CreateFileFromTemplateOperation extends RemoteOperation {
+    private static final String TAG = CreateFileFromTemplateOperation.class.getSimpleName();
+    private static final int SYNC_READ_TIMEOUT = 40000;
+    private static final int SYNC_CONNECTION_TIMEOUT = 5000;
+    private static final String NEW_FROM_TEMPLATE_URL = "/ocs/v2.php/apps/richdocuments/api/v1/templates/new";
+
+    private String path;
+    private int templateId;
+
+    // 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 CreateFileFromTemplateOperation(String path, int templateId) {
+        this.path = path;
+        this.templateId = templateId;
+    }
+
+    protected RemoteOperationResult run(OwnCloudClient client) {
+        RemoteOperationResult result;
+        PostMethod postMethod = null;
+
+        try {
+
+            postMethod = new PostMethod(client.getBaseUri() + NEW_FROM_TEMPLATE_URL + JSON_FORMAT);
+            postMethod.setParameter("path", path);
+            postMethod.setParameter("template", String.valueOf(templateId));
+
+            // remote request
+            postMethod.addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE);
+
+            int status = client.executeMethod(postMethod, SYNC_READ_TIMEOUT, SYNC_CONNECTION_TIMEOUT);
+
+            if (status == HttpStatus.SC_OK) {
+                String response = postMethod.getResponseBodyAsString();
+
+                // Parse the response
+                JSONObject respJSON = new JSONObject(response);
+                String url = respJSON.getJSONObject(NODE_OCS).getJSONObject(NODE_DATA).getString("url");
+
+                ArrayList<Object> templateArray = new ArrayList<>();
+                templateArray.add(url);
+
+                result = new RemoteOperationResult(true, postMethod);
+                result.setData(templateArray);
+            } else {
+                result = new RemoteOperationResult(false, postMethod);
+                client.exhaustResponse(postMethod.getResponseBodyAsStream());
+            }
+        } catch (Exception e) {
+            result = new RemoteOperationResult(e);
+            Log_OC.e(TAG, "Create file from template " + templateId + " failed: " + result.getLogMessage(),
+                    result.getException());
+        } finally {
+            if (postMethod != null) {
+                postMethod.releaseConnection();
+            }
+        }
+        return result;
+    }
+}

+ 105 - 0
src/main/java/com/owncloud/android/files/FetchTemplateOperation.java

@@ -0,0 +1,105 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2018 Tobias Kaminsky
+ * Copyright (C) 2018 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 <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.files;
+
+import com.owncloud.android.datamodel.Template;
+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 org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+public class FetchTemplateOperation extends RemoteOperation {
+    private static final String TAG = FetchTemplateOperation.class.getSimpleName();
+    private static final int SYNC_READ_TIMEOUT = 40000;
+    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;
+
+    // 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) {
+        this.type = type;
+    }
+
+    protected RemoteOperationResult run(OwnCloudClient client) {
+        RemoteOperationResult result;
+        GetMethod getMethod = null;
+
+        try {
+
+            getMethod = new GetMethod(client.getBaseUri() + TEMPLATE_URL + type.toString().toLowerCase(Locale.ENGLISH) +
+                JSON_FORMAT);
+
+            // remote request
+            getMethod.addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE);
+
+            int status = client.executeMethod(getMethod, SYNC_READ_TIMEOUT, SYNC_CONNECTION_TIMEOUT);
+
+            if (status == HttpStatus.SC_OK) {
+                String response = getMethod.getResponseBodyAsString();
+
+                // Parse the response
+                JSONObject respJSON = new JSONObject(response);
+                JSONArray templates = respJSON.getJSONObject(NODE_OCS).getJSONArray(NODE_DATA);
+
+                ArrayList<Object> templateArray = new ArrayList<>();
+
+                for (int i = 0; i < templates.length(); i++) {
+                    JSONObject templateObject = templates.getJSONObject(i);
+
+                    templateArray.add(new Template(templateObject.getInt("id"),
+                        templateObject.getString("name"),
+                        templateObject.optString("preview"),
+                        templateObject.getString("type"),
+                        templateObject.getString("extension")));
+                }
+
+                result = new RemoteOperationResult(true, getMethod);
+                result.setData(templateArray);
+            } else {
+                result = new RemoteOperationResult(false, getMethod);
+                client.exhaustResponse(getMethod.getResponseBodyAsStream());
+            }
+        } catch (Exception e) {
+            result = new RemoteOperationResult(e);
+            Log_OC.e(TAG, "Get templates for typ " + type + " failed: " + result.getLogMessage(),
+                result.getException());
+        } finally {
+            if (getMethod != null) {
+                getMethod.releaseConnection();
+            }
+        }
+        return result;
+    }
+}

+ 11 - 0
src/main/java/com/owncloud/android/files/FileMenuFilter.java

@@ -34,6 +34,7 @@ import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.lib.resources.status.OCCapability;
 import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
 import com.owncloud.android.ui.activity.ComponentsGetter;
+import com.owncloud.android.ui.activity.RichDocumentsWebView;
 import com.owncloud.android.utils.MimeTypeUtil;
 
 import java.util.ArrayList;
@@ -185,6 +186,7 @@ public class FileMenuFilter {
         filterUnsetEncrypted(toShow, toHide, endToEndEncryptionEnabled);
         filterSetPictureAs(toShow, toHide);
         filterStream(toShow, toHide);
+        filterOpenAsRichDocument(toShow, toHide, capability);
     }
 
     private void filterShareFile(List<Integer> toShow, List<Integer> toHide, OCCapability capability) {
@@ -263,6 +265,15 @@ public class FileMenuFilter {
         }
     }
 
+    private void filterOpenAsRichDocument(List<Integer> toShow, List<Integer> toHide, OCCapability capability) {
+        if (isSingleFile() && android.os.Build.VERSION.SDK_INT >= RichDocumentsWebView.MINIMUM_API &&
+                capability.getRichDocumentsMimeTypeList().contains(mFiles.iterator().next().getMimeType())) {
+            toShow.add(R.id.action_open_file_as_richdocument);
+        } else {
+            toHide.add(R.id.action_open_file_as_richdocument);
+        }
+    }
+
     private void filterSync(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
         if (mFiles.isEmpty() || (!anyFileDown() && !containsFolder()) || synchronizing) {
             toHide.add(R.id.action_sync_file);

+ 91 - 0
src/main/java/com/owncloud/android/operations/RichDocumentsCreateAssetOperation.java

@@ -0,0 +1,91 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2018 Tobias Kaminsky
+ * Copyright (C) 2018 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 <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.operations;
+
+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 org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.json.JSONObject;
+
+/**
+ * Create asset for RichDocuments app from file, which is already stored on Nextcloud server
+ */
+
+public class RichDocumentsCreateAssetOperation extends RemoteOperation {
+    private static final String TAG = RichDocumentsCreateAssetOperation.class.getSimpleName();
+    private static final int SYNC_READ_TIMEOUT = 40000;
+    private static final int SYNC_CONNECTION_TIMEOUT = 5000;
+    private static final String ASSET_URL = "/apps/richdocuments/assets";
+
+    private static final String NODE_URL = "url";
+    private static final String PARAMETER_PATH = "path";
+    private static final String PARAMETER_FORMAT = "format";
+    private static final String PARAMETER_FORMAT_VALUE = "json";
+
+    private String path;
+
+    public RichDocumentsCreateAssetOperation(String path) {
+        this.path = path;
+    }
+
+    protected RemoteOperationResult run(OwnCloudClient client) {
+        RemoteOperationResult result;
+        PostMethod postMethod = null;
+
+        try {
+            postMethod = new PostMethod(client.getBaseUri() + ASSET_URL);
+            postMethod.setParameter(PARAMETER_PATH, path);
+            postMethod.setParameter(PARAMETER_FORMAT, PARAMETER_FORMAT_VALUE);
+
+            // remote request
+            postMethod.addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE);
+
+            int status = client.executeMethod(postMethod, SYNC_READ_TIMEOUT, SYNC_CONNECTION_TIMEOUT);
+
+            if (status == HttpStatus.SC_OK) {
+                String response = postMethod.getResponseBodyAsString();
+
+                // Parse the response
+                JSONObject respJSON = new JSONObject(response);
+                String url = respJSON.getString(NODE_URL);
+
+                result = new RemoteOperationResult(true, postMethod);
+                result.setSingleData(url);
+            } else {
+                result = new RemoteOperationResult(false, postMethod);
+                client.exhaustResponse(postMethod.getResponseBodyAsStream());
+            }
+        } catch (Exception e) {
+            result = new RemoteOperationResult(e);
+            Log_OC.e(TAG, "Create asset for richdocuments with path " + path + " failed: " + result.getLogMessage(),
+                    result.getException());
+        } finally {
+            if (postMethod != null) {
+                postMethod.releaseConnection();
+            }
+        }
+        return result;
+    }
+}

+ 91 - 0
src/main/java/com/owncloud/android/operations/RichDocumentsUrlOperation.java

@@ -0,0 +1,91 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2018 Tobias Kaminsky
+ * Copyright (C) 2018 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 <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.operations;
+
+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 org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.json.JSONObject;
+
+/**
+ * Edit a file with Richdocuments. Returns URL which can be shown in WebView.
+ */
+public class RichDocumentsUrlOperation extends RemoteOperation {
+    private static final String TAG = RichDocumentsUrlOperation.class.getSimpleName();
+    private static final int SYNC_READ_TIMEOUT = 40000;
+    private static final int SYNC_CONNECTION_TIMEOUT = 5000;
+    private static final String DOCUMENT_URL = "/ocs/v2.php/apps/richdocuments/api/v1/document";
+    private static final String FILE_ID = "fileId";
+
+    // JSON node names
+    private static final String NODE_OCS = "ocs";
+    private static final String NODE_DATA = "data";
+    private static final String NODE_URL = "url";
+    private static final String JSON_FORMAT = "?format=json";
+
+    private String fileID;
+
+    public RichDocumentsUrlOperation(String fileID) {
+        this.fileID = fileID;
+    }
+
+    protected RemoteOperationResult run(OwnCloudClient client) {
+        RemoteOperationResult result;
+        PostMethod postMethod = null;
+
+        try {
+            postMethod = new PostMethod(client.getBaseUri() + DOCUMENT_URL + JSON_FORMAT);
+            postMethod.setParameter(FILE_ID, fileID);
+
+            // remote request
+            postMethod.addRequestHeader(OCS_API_HEADER, OCS_API_HEADER_VALUE);
+
+            int status = client.executeMethod(postMethod, SYNC_READ_TIMEOUT, SYNC_CONNECTION_TIMEOUT);
+
+            if (status == HttpStatus.SC_OK) {
+                String response = postMethod.getResponseBodyAsString();
+
+                // Parse the response
+                JSONObject respJSON = new JSONObject(response);
+                String url = respJSON.getJSONObject(NODE_OCS).getJSONObject(NODE_DATA).getString(NODE_URL);
+
+                result = new RemoteOperationResult(true, postMethod);
+                result.setSingleData(url);
+            } else {
+                result = new RemoteOperationResult(false, postMethod);
+                client.exhaustResponse(postMethod.getResponseBodyAsStream());
+            }
+        } catch (Exception e) {
+            result = new RemoteOperationResult(e);
+            Log_OC.e(TAG, "Get rich document url for file with id " + fileID + " failed: " + result.getLogMessage(),
+                    result.getException());
+        } finally {
+            if (postMethod != null) {
+                postMethod.releaseConnection();
+            }
+        }
+        return result;
+    }
+}

+ 23 - 1
src/main/java/com/owncloud/android/providers/FileContentProvider.java

@@ -813,7 +813,9 @@ public class FileContentProvider extends ContentProvider {
                 + ProviderTableMeta.CAPABILITIES_END_TO_END_ENCRYPTION + INTEGER
                 + ProviderTableMeta.CAPABILITIES_ACTIVITY + INTEGER
                 + ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_DEFAULT + INTEGER
-                + ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_PLAIN + " INTEGER );");
+                + ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_PLAIN + INTEGER
+                + ProviderTableMeta.CAPABILITIES_RICHDOCUMENT + INTEGER
+                + ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_MIMETYPE_LIST + " TEXT );");
     }
 
     private void createUploadsTable(SQLiteDatabase db) {
@@ -1802,6 +1804,26 @@ public class FileContentProvider extends ContentProvider {
             if (!upgraded) {
                 Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
             }
+
+            if (oldVersion < 38 && newVersion >= 38) {
+                Log_OC.i(SQL, "Entering in the #38 add richdocuments");
+                db.beginTransaction();
+                try {
+                    db.execSQL(ALTER_TABLE + ProviderTableMeta.CAPABILITIES_TABLE_NAME +
+                            ADD_COLUMN + ProviderTableMeta.CAPABILITIES_RICHDOCUMENT + " INTEGER "); // boolean
+                    db.execSQL(ALTER_TABLE + ProviderTableMeta.CAPABILITIES_TABLE_NAME +
+                            ADD_COLUMN + ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_MIMETYPE_LIST + " TEXT "); // string
+
+                    upgraded = true;
+                    db.setTransactionSuccessful();
+                } finally {
+                    db.endTransaction();
+                }
+            }
+
+            if (!upgraded) {
+                Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
+            }
         }
 
         @Override

+ 28 - 19
src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java

@@ -1,22 +1,22 @@
-/**
+/*
  * Nextcloud Android client application
  *
  * @author Tobias Kaminsky
  * Copyright (C) 2017 Tobias Kaminsky
  * Copyright (C) 2017 Nextcloud GmbH.
- * <p>
+ *
  * 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
+ * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
- * at your option) any later version.
- * <p>
+ * (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.
- * <p>
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
 
 package com.owncloud.android.ui.activity;
@@ -50,12 +50,16 @@ public class ExternalSiteWebView extends FileActivity {
     public static final String EXTRA_URL = "URL";
     public static final String EXTRA_SHOW_SIDEBAR = "SHOW_SIDEBAR";
     public static final String EXTRA_MENU_ITEM_ID = "MENU_ITEM_ID";
+    public static final String EXTRA_TEMPLATE = "TEMPLATE";
 
     private static final String TAG = ExternalSiteWebView.class.getSimpleName();
 
     private boolean showSidebar;
+    protected boolean showToolbar = true;
     private int menuItemId;
-    private WebView webview;
+    protected WebView webview;
+    protected int webViewLayout = R.layout.externalsite_webview;
+    String url;
 
     @SuppressLint("SetJavaScriptEnabled")
     @Override
@@ -64,7 +68,7 @@ public class ExternalSiteWebView extends FileActivity {
 
         Bundle extras = getIntent().getExtras();
         String title = extras.getString(EXTRA_TITLE);
-        String url = extras.getString(EXTRA_URL);
+        url = extras.getString(EXTRA_URL);
         menuItemId = extras.getInt(EXTRA_MENU_ITEM_ID);
         showSidebar = extras.getBoolean(EXTRA_SHOW_SIDEBAR);
 
@@ -75,7 +79,8 @@ public class ExternalSiteWebView extends FileActivity {
         }
 
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.externalsite_webview);
+
+        setContentView(webViewLayout);
 
         webview = findViewById(R.id.webView);
         final WebSettings webSettings = webview.getSettings();
@@ -85,7 +90,9 @@ public class ExternalSiteWebView extends FileActivity {
         webview.setClickable(true);
 
         // setup toolbar
-        setupToolbar();
+        if (showToolbar) {
+            setupToolbar();
+        }
 
         // setup drawer
         setupDrawer(menuItemId);
@@ -130,11 +137,13 @@ public class ExternalSiteWebView extends FileActivity {
 
         final ProgressBar progressBar = findViewById(R.id.progressBar);
 
-        webview.setWebChromeClient(new WebChromeClient() {
-            public void onProgressChanged(WebView view, int progress) {
-                progressBar.setProgress(progress * 1000);
-            }
-        });
+        if (progressBar != null) {
+            webview.setWebChromeClient(new WebChromeClient() {
+                public void onProgressChanged(WebView view, int progress) {
+                    progressBar.setProgress(progress * 1000);
+                }
+            });
+        }
 
         webview.setWebViewClient(new WebViewClient() {
             public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {

+ 64 - 0
src/main/java/com/owncloud/android/ui/activity/FilePickerActivity.java

@@ -0,0 +1,64 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2018 Tobias Kaminsky
+ * Copyright (C) 2018 Nextcloud GmbH.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.ui.activity;
+
+import android.os.Bundle;
+import android.support.v4.app.FragmentTransaction;
+import android.view.View;
+
+import com.owncloud.android.R;
+import com.owncloud.android.ui.dialog.SortingOrderDialogFragment;
+import com.owncloud.android.ui.fragment.OCFileListFragment;
+import com.owncloud.android.utils.FileSortOrder;
+
+/**
+ * File picker of remote files
+ */
+public class FilePickerActivity extends FolderPickerActivity implements
+    SortingOrderDialogFragment.OnSortingOrderListener {
+
+    @Override
+    public void onClick(View v) {
+        super.onClick(v);
+    }
+
+    @Override
+    protected void createFragments() {
+        OCFileListFragment listOfFiles = new OCFileListFragment();
+        Bundle args = new Bundle();
+        args.putBoolean(OCFileListFragment.ARG_ONLY_FOLDERS_CLICKABLE, true);
+        args.putBoolean(OCFileListFragment.ARG_HIDE_FAB, true);
+        args.putBoolean(OCFileListFragment.ARG_HIDE_ITEM_OPTIONS, true);
+        args.putBoolean(OCFileListFragment.ARG_SEARCH_ONLY_FOLDER, false);
+        args.putBoolean(OCFileListFragment.ARG_FILE_SELECTABLE, true);
+        args.putString(OCFileListFragment.ARG_MIMETYPE, getIntent().getStringExtra(OCFileListFragment.ARG_MIMETYPE));
+        listOfFiles.setArguments(args);
+        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+        transaction.add(R.id.fragment_container, listOfFiles, TAG_LIST_OF_FOLDERS);
+        transaction.commit();
+    }
+
+    @Override
+    public void onSortingOrderChosen(FileSortOrder selection) {
+        getListOfFilesFragment().sortFiles(selection);
+    }
+}

+ 69 - 52
src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java

@@ -32,6 +32,7 @@ import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentTransaction;
 import android.support.v7.app.ActionBar;
 import android.util.Log;
@@ -52,6 +53,7 @@ import com.owncloud.android.operations.CreateFolderOperation;
 import com.owncloud.android.operations.RefreshFolderOperation;
 import com.owncloud.android.syncadapter.FileSyncAdapter;
 import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
+import com.owncloud.android.ui.dialog.SortingOrderDialogFragment;
 import com.owncloud.android.ui.fragment.FileFragment;
 import com.owncloud.android.ui.fragment.OCFileListFragment;
 import com.owncloud.android.utils.DataHolderUtil;
@@ -61,6 +63,8 @@ import com.owncloud.android.utils.ThemeUtils;
 
 import java.util.ArrayList;
 
+import static com.owncloud.android.db.PreferenceManager.getSortOrderByFolder;
+
 public class FolderPickerActivity extends FileActivity implements FileFragment.ContainerActivity,
     OnClickListener, OnEnforceableRefreshListener {
 
@@ -76,8 +80,8 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
 
     private static final String TAG = FolderPickerActivity.class.getSimpleName();
 
-    private static final String TAG_LIST_OF_FOLDERS = "LIST_OF_FOLDERS";
-       
+    protected static final String TAG_LIST_OF_FOLDERS = "LIST_OF_FOLDERS";
+
     private boolean mSyncInProgress;
 
     private boolean mSearchOnlyFolders;
@@ -93,15 +97,18 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
 
         super.onCreate(savedInstanceState);
 
-        setContentView(R.layout.files_folder_picker);
-
+        if (this instanceof FilePickerActivity) {
+            setContentView(R.layout.files_picker);
+        } else {
+            setContentView(R.layout.files_folder_picker);
+        }
 
         // sets callback listeners for UI elements
         initControls();
 
         // Action bar setup
         setupToolbar();
-        
+
         if (getIntent().getStringExtra(EXTRA_ACTION) != null) {
             switch (getIntent().getStringExtra(EXTRA_ACTION)) {
                 case MOVE:
@@ -137,7 +144,7 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
 
         setIndeterminate(mSyncInProgress);
         // always AFTER setContentView(...) ; to work around bug in its implementation
-        
+
         // sets message for empty list of folders
         setBackgroundText();
 
@@ -156,23 +163,23 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
     protected void onAccountSet(boolean stateWasRecovered) {
         super.onAccountSet(stateWasRecovered);
         if (getAccount() != null) {
-            
+
             updateFileFromDB();
-            
+
             OCFile folder = getFile();
             if (folder == null || !folder.isFolder()) {
                 // fall back to root folder
                 setFile(getStorageManager().getFileByPath(OCFile.ROOT_PATH));
                 folder = getFile();
             }
-            
+
             if (!stateWasRecovered) {
-                OCFileListFragment listOfFolders = getListOfFilesFragment(); 
+                OCFileListFragment listOfFolders = getListOfFilesFragment();
                 listOfFolders.listDirectory(folder, false, false);
-                
+
                 startSyncFolderOperation(folder, false);
             }
-            
+
             updateNavigationElementsInActionBar();
         }
     }
@@ -181,7 +188,7 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
         return this;
     }
 
-    private void createFragments() {
+    protected void createFragments() {
         OCFileListFragment listOfFiles = new OCFileListFragment();
         Bundle args = new Bundle();
         args.putBoolean(OCFileListFragment.ARG_ONLY_FOLDERS_CLICKABLE, true);
@@ -224,10 +231,10 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
         Log_OC.e(TAG, "Access to non existing list of files fragment!!");
         return null;
     }
-    
+
     /**
      * {@inheritDoc}
-     * 
+     *
      * Updates action bar and second fragment, if in dual pane mode.
      */
     @Override
@@ -242,12 +249,12 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
     public void onSavedCertificate() {
         startSyncFolderOperation(getCurrentDir(), false);
     }
-    
+
     public void startSyncFolderOperation(OCFile folder, boolean ignoreETag) {
-        long currentSyncTime = System.currentTimeMillis(); 
-        
+        long currentSyncTime = System.currentTimeMillis();
+
         mSyncInProgress = true;
-                
+
         // perform folder synchronization
         RemoteOperation refreshFolderOperation = new RefreshFolderOperation(folder, currentSyncTime, false,
                 ignoreETag, getStorageManager(), getAccount(), getApplicationContext());
@@ -273,10 +280,10 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
         syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
         mSyncBroadcastReceiver = new SyncBroadcastReceiver();
         registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
-        
+
         Log_OC.d(TAG, "onResume() end");
     }
-    
+
     @Override
     protected void onPause() {
         Log_OC.e(TAG, "onPause() start");
@@ -285,11 +292,11 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
             //LocalBroadcastManager.getInstance(this).unregisterReceiver(mSyncBroadcastReceiver);
             mSyncBroadcastReceiver = null;
         }
-        
+
         Log_OC.d(TAG, "onPause() end");
         super.onPause();
     }
-    
+
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         MenuInflater inflater = getMenuInflater();
@@ -297,7 +304,7 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
         menu.findItem(R.id.action_switch_view).setVisible(false);
         menu.findItem(R.id.action_sync_account).setVisible(false);
         menu.findItem(R.id.action_select_all).setVisible(false);
-        menu.findItem(R.id.action_sort).setVisible(false);
+        // menu.findItem(R.id.action_sort).setVisible(false);
         return true;
     }
 
@@ -306,12 +313,8 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
         boolean retval = true;
         switch (item.getItemId()) {
         case R.id.action_create_dir: {
-            CreateFolderDialogFragment dialog = 
-                    CreateFolderDialogFragment.newInstance(getCurrentFolder());
-            dialog.show(
-                    getSupportFragmentManager(), 
-                    CreateFolderDialogFragment.CREATE_FOLDER_FRAGMENT
-            );
+            CreateFolderDialogFragment dialog = CreateFolderDialogFragment.newInstance(getCurrentFolder());
+            dialog.show(getSupportFragmentManager(), CreateFolderDialogFragment.CREATE_FOLDER_FRAGMENT);
             break;
         }
         case android.R.id.home: {
@@ -321,6 +324,17 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
             }
             break;
         }
+            case R.id.action_sort: {
+                FragmentManager fm = getSupportFragmentManager();
+                FragmentTransaction ft = fm.beginTransaction();
+                ft.addToBackStack(null);
+
+                SortingOrderDialogFragment mSortingOrderDialogFragment = SortingOrderDialogFragment.newInstance(
+                    getSortOrderByFolder(this, getListOfFilesFragment().getCurrentFile()));
+                mSortingOrderDialogFragment.show(ft, SortingOrderDialogFragment.SORTING_ORDER_FRAGMENT);
+
+                break;
+            }
         default:
             retval = super.onOptionsItemSelected(item);
             break;
@@ -341,7 +355,7 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
         }
         return null;
     }
-    
+
     public void refreshListOfFilesFragment(boolean fromSearch) {
         OCFileListFragment fileListFragment = getListOfFilesFragment();
         if (fileListFragment != null) {
@@ -350,7 +364,7 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
     }
 
     public void browseToRoot() {
-        OCFileListFragment listOfFiles = getListOfFilesFragment(); 
+        OCFileListFragment listOfFiles = getListOfFilesFragment();
         if (listOfFiles != null) {  // should never be null, indeed
             OCFile root = getStorageManager().getFileByPath(OCFile.ROOT_PATH);
             listOfFiles.listDirectory(root, false, false);
@@ -398,10 +412,13 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
         mCancelBtn = findViewById(R.id.folder_picker_btn_cancel);
         mCancelBtn.setOnClickListener(this);
         mChooseBtn = findViewById(R.id.folder_picker_btn_choose);
-        mChooseBtn.getBackground().setColorFilter(ThemeUtils.primaryColor(this, true), PorterDuff.Mode.SRC_ATOP);
-        mChooseBtn.setOnClickListener(this);
+
+        if (mChooseBtn != null) {
+            mChooseBtn.getBackground().setColorFilter(ThemeUtils.primaryColor(this, true), PorterDuff.Mode.SRC_ATOP);
+            mChooseBtn.setOnClickListener(this);
+        }
     }
-    
+
     @Override
     public void onClick(View v) {
         if (v.equals(mCancelBtn)) {
@@ -418,30 +435,30 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
             finish();
         }
     }
-    
-    
+
+
     @Override
     public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
         super.onRemoteOperationFinish(operation, result);
-        
+
         if (operation instanceof CreateFolderOperation) {
             onCreateFolderOperationFinish((CreateFolderOperation) operation, result);
-            
+
         }
     }
-    
-    
+
+
     /**
-     * Updates the view associated to the activity after the finish of an operation trying 
+     * Updates the view associated to the activity after the finish of an operation trying
      * to create a new folder.
-     * 
+     *
      * @param operation     Creation operation performed.
      * @param result        Result of the creation.
      */
     private void onCreateFolderOperationFinish(
             CreateFolderOperation operation, RemoteOperationResult result
             ) {
-        
+
         if (result.isSuccess()) {
             refreshListOfFilesFragment(false);
         } else {
@@ -455,7 +472,7 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
             }
         }
     }
-    
+
     private class SyncBroadcastReceiver extends BroadcastReceiver {
 
         /**
@@ -472,16 +489,16 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
                         DataHolderUtil.getInstance().retrieve(intent.getStringExtra(FileSyncAdapter.EXTRA_RESULT));
                 boolean sameAccount = getAccount() != null && accountName.equals(getAccount().name)
                         && getStorageManager() != null;
-    
+
                 if (sameAccount) {
                     if (FileSyncAdapter.EVENT_FULL_SYNC_START.equals(event)) {
                         mSyncInProgress = true;
                     } else {
                         OCFile currentFile = (getFile() == null) ? null :
                                 getStorageManager().getFileByPath(getFile().getRemotePath());
-                        OCFile currentDir = (getCurrentFolder() == null) ? null : 
+                        OCFile currentDir = (getCurrentFolder() == null) ? null :
                             getStorageManager().getFileByPath(getCurrentFolder().getRemotePath());
-    
+
                         if (currentDir == null) {
                             // current folder was removed from the server
                             DisplayUtils.showSnackMessage(getActivity(), R.string.sync_current_folder_was_removed,
@@ -501,8 +518,8 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
                             }
                             setFile(currentFile);
                         }
-                        
-                        mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) && 
+
+                        mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) &&
                                 !RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
 
                         if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.equals(event) &&
@@ -527,9 +544,9 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
 
                     setBackgroundText();
                 }
-                
+
             } catch (RuntimeException e) {
-                // avoid app crashes after changing the serial id of RemoteOperationResult 
+                // avoid app crashes after changing the serial id of RemoteOperationResult
                 // in owncloud library with broadcast notifications pending to process
                 removeStickyBroadcast(intent);
                 DataHolderUtil.getInstance().delete(intent.getStringExtra(FileSyncAdapter.EXTRA_RESULT));

+ 403 - 0
src/main/java/com/owncloud/android/ui/activity/RichDocumentsWebView.java

@@ -0,0 +1,403 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2018 Tobias Kaminsky
+ * Copyright (C) 2018 Nextcloud GmbH.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.ui.activity;
+
+import android.accounts.Account;
+import android.annotation.SuppressLint;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.RequiresApi;
+import android.text.TextUtils;
+import android.view.View;
+import android.webkit.JavascriptInterface;
+import android.webkit.ValueCallback;
+import android.webkit.WebChromeClient;
+import android.webkit.WebView;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.bumptech.glide.Glide;
+import com.owncloud.android.R;
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.Template;
+import com.owncloud.android.datamodel.ThumbnailsCacheManager;
+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.operations.RichDocumentsUrlOperation;
+import com.owncloud.android.ui.fragment.OCFileListFragment;
+import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.MimeTypeUtil;
+import com.owncloud.android.utils.glide.CustomGlideStreamLoader;
+
+import org.parceler.Parcels;
+
+import java.lang.ref.WeakReference;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.Unbinder;
+
+/**
+ * Opens document for editing via Richdocuments app in a web view
+ */
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class RichDocumentsWebView extends ExternalSiteWebView {
+
+    private static final String TAG = RichDocumentsWebView.class.getSimpleName();
+    private static final int REQUEST_REMOTE_FILE = 100;
+
+    public static final int REQUEST_LOCAL_FILE = 101;
+
+    public static final int MINIMUM_API = Build.VERSION_CODES.LOLLIPOP;
+
+    private Unbinder unbinder;
+    private OCFile file;
+
+    public ValueCallback<Uri[]> uploadMessage;
+
+    @BindView(R.id.progressBar2)
+    ProgressBar progressBar;
+
+    @BindView(R.id.thumbnail)
+    ImageView thumbnail;
+
+    @BindView(R.id.filename)
+    TextView fileName;
+
+    @SuppressLint("AddJavascriptInterface") // suppress warning as webview is only used >= Lollipop
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        showToolbar = false;
+        webViewLayout = R.layout.richdocuments_webview;
+        super.onCreate(savedInstanceState);
+
+        unbinder = ButterKnife.bind(this);
+
+        file = getIntent().getParcelableExtra(EXTRA_FILE);
+
+        // TODO make file nullable
+        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()).load(template.getThumbnailLink())
+                .placeholder(placeholder)
+                .error(placeholder)
+                .into(thumbnail);
+        } else {
+            setThumbnail(file, thumbnail);
+            fileName.setText(file.getFileName());
+        }
+
+        webview.addJavascriptInterface(new RichDocumentsMobileInterface(), "RichDocumentsMobileInterface");
+
+        webview.setWebChromeClient(new WebChromeClient() {
+            RichDocumentsWebView activity = RichDocumentsWebView.this;
+
+            @Override
+            public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
+                                             FileChooserParams fileChooserParams) {
+                if (uploadMessage != null) {
+                    uploadMessage.onReceiveValue(null);
+                    uploadMessage = null;
+                }
+
+                activity.uploadMessage = filePathCallback;
+
+                Intent intent = fileChooserParams.createIntent();
+                intent.setType("image/*");
+                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+                try {
+                    activity.startActivityForResult(intent, REQUEST_LOCAL_FILE);
+                } catch (ActivityNotFoundException e) {
+                    uploadMessage = null;
+                    Toast.makeText(getBaseContext(), "Cannot open file chooser", Toast.LENGTH_LONG).show();
+                    return false;
+                }
+
+                return true;
+            }
+        });
+
+        // load url in background
+        url = getIntent().getStringExtra(EXTRA_URL);
+        if (TextUtils.isEmpty(url)) {
+            new LoadUrl(this, getAccount()).execute(file.getLocalId());
+        } else {
+            webview.loadUrl(url);
+        }
+    }
+
+    private void setThumbnail(OCFile file, ImageView thumbnailView) {
+        // Todo minimize: only icon by mimetype
+
+        if (file.isFolder()) {
+            thumbnailView.setImageDrawable(MimeTypeUtil.getFolderTypeIcon(file.isSharedWithMe() ||
+                    file.isSharedWithSharee(), file.isSharedViaLink(), file.isEncrypted(), file.getMountType(),
+                this));
+        } else {
+            if ((MimeTypeUtil.isImage(file) || MimeTypeUtil.isVideo(file)) && file.getRemoteId() != null) {
+                // Thumbnail in cache?
+                Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
+                    ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.getRemoteId());
+
+                if (thumbnail != null && !file.isUpdateThumbnailNeeded()) {
+                    if (MimeTypeUtil.isVideo(file)) {
+                        Bitmap withOverlay = ThumbnailsCacheManager.addVideoOverlay(thumbnail);
+                        thumbnailView.setImageBitmap(withOverlay);
+                    } 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())) {
+                    thumbnailView.setBackgroundColor(getResources().getColor(R.color.background_color));
+                }
+            } else {
+                thumbnailView.setImageDrawable(MimeTypeUtil.getFileTypeIcon(file.getMimeType(), file.getFileName(),
+                    getAccount(), this));
+            }
+        }
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+    }
+
+    private void openFileChooser() {
+        Intent action = new Intent(this, FilePickerActivity.class);
+        action.putExtra(OCFileListFragment.ARG_MIMETYPE, "image/");
+        startActivityForResult(action, REQUEST_REMOTE_FILE);
+    }
+
+    private void openShareDialog() {
+        Intent intent = new Intent(this, ShareActivity.class);
+        intent.putExtra(FileActivity.EXTRA_FILE, file);
+        intent.putExtra(FileActivity.EXTRA_ACCOUNT, getAccount());
+        startActivity(intent);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (RESULT_OK != resultCode) {
+            // TODO
+            return;
+        }
+
+        switch (requestCode) {
+            case REQUEST_LOCAL_FILE:
+                handleLocalFile(data, resultCode);
+                break;
+
+            case REQUEST_REMOTE_FILE:
+                handleRemoteFile(data);
+                break;
+
+            default:
+                // unexpected, do nothing
+                break;
+        }
+
+        super.onActivityResult(requestCode, resultCode, data);
+    }
+
+    private void handleLocalFile(Intent data, int resultCode) {
+        if (uploadMessage == null) {
+            return;
+        }
+
+        uploadMessage.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
+        uploadMessage = null;
+    }
+
+    private void handleRemoteFile(Intent data) {
+        OCFile file = data.getParcelableExtra(FolderPickerActivity.EXTRA_FILES);
+
+        new Thread(() -> {
+            Account account = AccountUtils.getCurrentOwnCloudAccount(this);
+            RichDocumentsCreateAssetOperation operation = new RichDocumentsCreateAssetOperation(file.getRemotePath());
+            RemoteOperationResult result = operation.execute(account, this);
+
+            if (result.isSuccess()) {
+                String asset = (String) result.getSingleData();
+
+                runOnUiThread(() -> webview.evaluateJavascript("OCA.RichDocuments.documentsMain.postAsset('" +
+                    file.getFileName() + "', '" + asset + "');", null));
+            } else {
+                runOnUiThread(() -> DisplayUtils.showSnackMessage(this, "Inserting image failed!"));
+            }
+        }).start();
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        outState.putString(EXTRA_URL, url);
+        super.onSaveInstanceState(outState);
+    }
+
+    @Override
+    public void onRestoreInstanceState(Bundle savedInstanceState) {
+        url = savedInstanceState.getString(EXTRA_URL);
+        super.onRestoreInstanceState(savedInstanceState);
+    }
+
+    @Override
+    protected void onDestroy() {
+        unbinder.unbind();
+        webview.destroy();
+
+        super.onDestroy();
+    }
+
+    private void closeView() {
+        webview.destroy();
+        finish();
+    }
+
+    private void hideLoading() {
+        thumbnail.setVisibility(View.GONE);
+        fileName.setVisibility(View.GONE);
+        progressBar.setVisibility(View.GONE);
+        webview.setVisibility(View.VISIBLE);
+    }
+
+    private class RichDocumentsMobileInterface {
+        @JavascriptInterface
+        public void close() {
+            runOnUiThread(RichDocumentsWebView.this::closeView);
+        }
+
+        @JavascriptInterface
+        public void insertGraphic() {
+            openFileChooser();
+        }
+
+        @JavascriptInterface
+        public void share() {
+            openShareDialog();
+        }
+
+        @JavascriptInterface
+        public void documentLoaded() {
+            runOnUiThread(RichDocumentsWebView.this::hideLoading);
+        }
+    }
+
+    private static class LoadUrl extends AsyncTask<String, Void, String> {
+
+        private Account account;
+        private WeakReference<RichDocumentsWebView> richDocumentsWebViewWeakReference;
+
+        LoadUrl(RichDocumentsWebView richDocumentsWebView, Account account) {
+            this.account = account;
+            this.richDocumentsWebViewWeakReference = new WeakReference<>(richDocumentsWebView);
+        }
+
+        @Override
+        protected String doInBackground(String... fileId) {
+            if (richDocumentsWebViewWeakReference.get() == null) {
+                return "";
+            }
+            RichDocumentsUrlOperation richDocumentsUrlOperation = new RichDocumentsUrlOperation(fileId[0]);
+            RemoteOperationResult result = richDocumentsUrlOperation.execute(account,
+                richDocumentsWebViewWeakReference.get());
+
+            if (!result.isSuccess()) {
+                return "";
+            }
+
+            return (String) result.getData().get(0);
+        }
+
+        @Override
+        protected void onPostExecute(String url) {
+            RichDocumentsWebView richDocumentsWebView = richDocumentsWebViewWeakReference.get();
+
+            if (richDocumentsWebView == null) {
+                return;
+            }
+
+            if (!url.isEmpty()) {
+                richDocumentsWebView.webview.loadUrl(url);
+            } else {
+                Toast.makeText(richDocumentsWebView.getApplicationContext(),
+                    R.string.richdocuments_failed_to_load_document, Toast.LENGTH_LONG).show();
+                richDocumentsWebView.finish();
+            }
+        }
+    }
+}

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

@@ -511,15 +511,15 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
 
     /**
      * Change the adapted directory for a new one
-     *
-     * @param directory             New folder to adapt. Can be NULL, meaning
+     *  @param directory             New folder to adapt. Can be NULL, meaning
      *                              "no content to adapt".
      * @param updatedStorageManager Optional updated storage manager; used to replace
-     *                              mStorageManager if is different (and not NULL)
+     * @param limitToMimeType
      */
     public void swapDirectory(OCFile directory, FileDataStorageManager updatedStorageManager,
-                              boolean onlyOnDevice) {
+                              boolean onlyOnDevice, String limitToMimeType) {
         this.onlyOnDevice = onlyOnDevice;
+
         if (updatedStorageManager != null && !updatedStorageManager.equals(mStorageManager)) {
             mStorageManager = updatedStorageManager;
             mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext);
@@ -530,6 +530,9 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
             if (!PreferenceManager.showHiddenFilesEnabled(mContext)) {
                 mFiles = filterHiddenFiles(mFiles);
             }
+            if (!limitToMimeType.isEmpty()) {
+                mFiles = filterByMimeType(mFiles, limitToMimeType);
+            }
             FileSortOrder sortOrder = PreferenceManager.getSortOrderByFolder(mContext, directory);
             mFiles = sortOrder.sortCloudFiles(mFiles);
             mFilesAll.clear();
@@ -792,6 +795,18 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
         return ret;
     }
 
+    private List<OCFile> filterByMimeType(List<OCFile> files, String mimeType) {
+        List<OCFile> ret = new ArrayList<>();
+
+        for (OCFile file : files) {
+            if (file.isFolder() || file.getMimeType().startsWith(mimeType)) {
+                ret.add(file);
+            }
+        }
+
+        return ret;
+    }
+
     public void cancelAllPendingTasks() {
         for (ThumbnailsCacheManager.ThumbnailGenerationTask task : asyncTasks) {
             if (task != null) {

+ 139 - 0
src/main/java/com/owncloud/android/ui/adapter/TemplateAdapter.java

@@ -0,0 +1,139 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2018 Tobias Kaminsky
+ * Copyright (C) 2018 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 <http://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.ui.adapter;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+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.owncloud.android.R;
+import com.owncloud.android.datamodel.Template;
+import com.owncloud.android.ui.dialog.ChooseTemplateDialogFragment;
+import com.owncloud.android.utils.glide.CustomGlideStreamLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+/**
+ * Adapter for handling Templates, used to create files out of it via RichDocuments app
+ */
+public class TemplateAdapter extends RecyclerView.Adapter<TemplateAdapter.ViewHolder> {
+
+    private List<Template> templateList = new ArrayList<>();
+    private ClickListener clickListener;
+    private Context context;
+    private ChooseTemplateDialogFragment.Type type;
+
+    public TemplateAdapter(ChooseTemplateDialogFragment.Type type, ClickListener clickListener, Context context) {
+        this.clickListener = clickListener;
+        this.type = type;
+        this.context = context;
+    }
+
+    @NonNull
+    @Override
+    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()).load(template.getThumbnailLink())
+                    .placeholder(placeholder)
+                    .error(placeholder)
+                    .into(thumbnail);
+
+            name.setText(template.getName());
+        }
+    }
+
+    public interface ClickListener {
+        void onClick(Template template);
+    }
+}

+ 298 - 0
src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.java

@@ -0,0 +1,298 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2018 Tobias Kaminsky
+ * Copyright (C) 2018 Nextcloud GmbH.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.ui.dialog;
+
+import android.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.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager.LayoutParams;
+import android.widget.EditText;
+
+import com.owncloud.android.MainApp;
+import com.owncloud.android.R;
+import com.owncloud.android.authentication.AccountUtils;
+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.ui.activity.ExternalSiteWebView;
+import com.owncloud.android.ui.activity.RichDocumentsWebView;
+import com.owncloud.android.ui.adapter.TemplateAdapter;
+import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.ThemeUtils;
+
+import org.parceler.Parcels;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+/**
+ * Dialog to show templates for new documents/spreadsheets/presentations.
+ */
+public class ChooseTemplateDialogFragment extends DialogFragment implements DialogInterface.OnClickListener,
+    TemplateAdapter.ClickListener {
+
+    private static final String ARG_PARENT_FOLDER = "PARENT_FOLDER";
+    private static final String ARG_TYPE = "TYPE";
+    private static final String TAG = ChooseTemplateDialogFragment.class.getSimpleName();
+    private static final String DOT = ".";
+
+    private TemplateAdapter adapter;
+    private OCFile parentFolder;
+    private OwnCloudClient client;
+
+    public enum Type {
+        DOCUMENT,
+        SPREADSHEET,
+        PRESENTATION
+    }
+
+    @BindView(R.id.list)
+    RecyclerView listView;
+
+    @BindView(R.id.filename)
+    EditText fileName;
+
+    public static ChooseTemplateDialogFragment newInstance(OCFile parentFolder, Type type) {
+        ChooseTemplateDialogFragment frag = new ChooseTemplateDialogFragment();
+        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 = AccountUtils.getCurrentOwnCloudAccount(activity);
+            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 TemplateAdapter(type, this, getContext());
+        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).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<ChooseTemplateDialogFragment> chooseTemplateDialogFragmentWeakReference;
+        private Template template;
+        private String path;
+
+        CreateFileFromTemplateTask(ChooseTemplateDialogFragment chooseTemplateDialogFragment, OwnCloudClient client,
+                                   Template template, String path) {
+            this.client = client;
+            this.chooseTemplateDialogFragmentWeakReference = new WeakReference<>(chooseTemplateDialogFragment);
+            this.template = template;
+            this.path = path;
+        }
+
+        @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 {
+                return "";
+            }
+        }
+
+        @Override
+        protected void onPostExecute(String url) {
+            ChooseTemplateDialogFragment fragment = chooseTemplateDialogFragmentWeakReference.get();
+
+            if (fragment != null) {
+                if (url.isEmpty()) {
+                    DisplayUtils.showSnackMessage(fragment.listView, "Error creating file from template");
+                } else {
+                    Intent collaboraWebViewIntent = new Intent(MainApp.getAppContext(), RichDocumentsWebView.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);
+
+                    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<ChooseTemplateDialogFragment> chooseTemplateDialogFragmentWeakReference;
+
+        FetchTemplateTask(ChooseTemplateDialogFragment 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) {
+            ChooseTemplateDialogFragment 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!");
+            }
+        }
+    }
+}

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

@@ -495,7 +495,7 @@ public class ExtendedListFragment extends Fragment
      */
     protected void restoreIndexAndTopPosition() {
         if (mIndexes.size() > 0) {
-            // needs to be checked; not every browse-up had a browse-down before 
+            // needs to be checked; not every browse-up had a browse-down before
 
             int index = mIndexes.remove(mIndexes.size() - 1);
             final int firstPosition = mFirstPositions.remove(mFirstPositions.size() - 1);
@@ -617,8 +617,6 @@ public class ExtendedListFragment extends Fragment
     public void setFabEnabled(final boolean enabled) {
         if (getActivity() != null) {
             getActivity().runOnUiThread(() -> {
-                mFabMain.show();
-
                 if (enabled) {
                     mFabMain.setEnabled(true);
                     ThemeUtils.tintDrawable(mFabMain.getBackground(), ThemeUtils.primaryColor(getContext()));

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

@@ -39,4 +39,19 @@ public interface OCFileListBottomSheetActions {
      * offers a file upload with the app file picker to the current folder.
      */
     void uploadFiles();
+
+    /**
+     * opens template selection for documents
+     */
+    void newDocument();
+
+    /**
+     * opens template selection for spreadsheets
+     */
+    void newSpreadsheet();
+
+    /**
+     * opens template selection for presentations
+     */
+    void newPresentation();
 }

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

@@ -31,6 +31,7 @@ import android.widget.ImageView;
 import android.widget.TextView;
 
 import com.owncloud.android.R;
+import com.owncloud.android.lib.resources.status.OCCapability;
 import com.owncloud.android.utils.ThemeUtils;
 
 import butterknife.BindView;
@@ -44,21 +45,28 @@ import butterknife.Unbinder;
 public class OCFileListBottomSheetDialog extends BottomSheetDialog {
     @BindView(R.id.menu_icon_upload_files)
     public ImageView iconUploadFiles;
+
     @BindView(R.id.menu_icon_upload_from_app)
     public ImageView iconUploadFromApp;
+
     @BindView(R.id.menu_icon_mkdir)
     public ImageView iconMakeDir;
+
     @BindView(R.id.add_to_cloud)
     public TextView headline;
 
-    private Unbinder unbinder;
+    @BindView(R.id.templates)
+    public View templates;
 
+    private Unbinder unbinder;
     private OCFileListBottomSheetActions actions;
+    private OCCapability capability;
 
-
-    public OCFileListBottomSheetDialog(@NonNull Context context, OCFileListBottomSheetActions actions) {
+    public OCFileListBottomSheetDialog(@NonNull Context context, OCCapability capability,
+                                       OCFileListBottomSheetActions actions) {
         super(context);
         this.actions = actions;
+        this.capability = capability;
     }
 
     @Override
@@ -81,6 +89,10 @@ public class OCFileListBottomSheetDialog extends BottomSheetDialog {
         headline.setText(getContext().getResources().getString(R.string.add_to_cloud,
                 ThemeUtils.getDefaultDisplayNameForRootFolder(getContext())));
 
+        if (capability.getRichDocuments().isTrue()) {
+            templates.setVisibility(View.VISIBLE);
+        }
+
         setOnShowListener(d ->
                 BottomSheetBehavior.from((View) view.getParent()).setPeekHeight(view.getMeasuredHeight())
         );
@@ -104,6 +116,23 @@ public class OCFileListBottomSheetDialog extends BottomSheetDialog {
         dismiss();
     }
 
+    @OnClick(R.id.menu_new_document)
+    public void newDocument() {
+        actions.newDocument();
+        dismiss();
+    }
+
+    @OnClick(R.id.menu_new_spreadsheet)
+    public void newSpreadsheet() {
+        actions.newSpreadsheet();
+        dismiss();
+    }
+
+    @OnClick(R.id.menu_new_presentation)
+    public void newPresentation() {
+        actions.newPresentation();
+        dismiss();
+    }
     @Override
     protected void onStop() {
         super.onStop();

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

@@ -83,9 +83,11 @@ import com.owncloud.android.ui.activity.FileActivity;
 import com.owncloud.android.ui.activity.FileDisplayActivity;
 import com.owncloud.android.ui.activity.FolderPickerActivity;
 import com.owncloud.android.ui.activity.OnEnforceableRefreshListener;
+import com.owncloud.android.ui.activity.RichDocumentsWebView;
 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.ChooseTemplateDialogFragment;
 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
 import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
 import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment;
@@ -133,10 +135,12 @@ public class OCFileListFragment extends ExtendedListFragment implements
             OCFileListFragment.class.getPackage().getName() : "com.owncloud.android.ui.fragment";
 
     public final static String ARG_ONLY_FOLDERS_CLICKABLE = MY_PACKAGE + ".ONLY_FOLDERS_CLICKABLE";
+    public final static String ARG_FILE_SELECTABLE = MY_PACKAGE + ".FILE_SELECTABLE";
     public final static String ARG_ALLOW_CONTEXTUAL_ACTIONS = MY_PACKAGE + ".ALLOW_CONTEXTUAL";
     public final static String ARG_HIDE_FAB = MY_PACKAGE + ".HIDE_FAB";
     public final static String ARG_HIDE_ITEM_OPTIONS = MY_PACKAGE + ".HIDE_ITEM_OPTIONS";
     public final static String ARG_SEARCH_ONLY_FOLDER = MY_PACKAGE + ".SEARCH_ONLY_FOLDER";
+    public final static String ARG_MIMETYPE = MY_PACKAGE + ".MIMETYPE";
 
     public static final String DOWNLOAD_BEHAVIOUR = "DOWNLOAD_BEHAVIOUR";
     public static final String DOWNLOAD_SEND = "DOWNLOAD_SEND";
@@ -151,6 +155,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
     private static final String KEY_CURRENT_SEARCH_TYPE = "CURRENT_SEARCH_TYPE";
 
     private static final String DIALOG_CREATE_FOLDER = "DIALOG_CREATE_FOLDER";
+    private static final String DIALOG_CREATE_DOCUMENT = "DIALOG_CREATE_DOCUMENT";
 
     private static final int SINGLE_SELECTION = 1;
 
@@ -159,6 +164,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
     private OCFile mFile;
     private OCFileListAdapter mAdapter;
     private boolean mOnlyFoldersClickable;
+    private boolean mFileSelectable;
 
     private int mSystemBarActionModeColor;
     private int mSystemBarColor;
@@ -175,6 +181,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
     private boolean searchFragment;
     private SearchEvent searchEvent;
     private AsyncTask remoteOperationAsyncTask;
+    private String mLimitToMimeType;
 
     private enum MenuItemAddRemove {
         DO_NOTHING, REMOVE_SORT, REMOVE_GRID_AND_SORT, ADD_SORT, ADD_GRID_AND_SORT, ADD_GRID_AND_SORT_WITH_SEARCH,
@@ -326,6 +333,8 @@ public class OCFileListFragment extends ExtendedListFragment implements
 
         Bundle args = getArguments();
         mOnlyFoldersClickable = args != null && args.getBoolean(ARG_ONLY_FOLDERS_CLICKABLE, false);
+        mFileSelectable = args != null && args.getBoolean(ARG_FILE_SELECTABLE, false);
+        mLimitToMimeType = args != null ? args.getString(ARG_MIMETYPE, "") : "";
         boolean hideItemOptions = args != null && args.getBoolean(ARG_HIDE_ITEM_OPTIONS, false);
 
         mAdapter = new OCFileListAdapter(getActivity(), mContainerActivity, this, hideItemOptions,
@@ -380,8 +389,9 @@ public class OCFileListFragment extends ExtendedListFragment implements
      * register listener on FAB.
      */
     private void registerFabListener() {
+        OCCapability capability = ((FileActivity) getActivity()).getCapabilities();
         getFabMain().setOnClickListener(v -> {
-            new OCFileListBottomSheetDialog(getContext(), this).show();
+            new OCFileListBottomSheetDialog(getContext(), capability, this).show();
         });
     }
 
@@ -438,6 +448,24 @@ public class OCFileListFragment extends ExtendedListFragment implements
         popup.show();
     }
 
+    @Override
+    public void newDocument() {
+        ChooseTemplateDialogFragment.newInstance(mFile, ChooseTemplateDialogFragment.Type.DOCUMENT)
+                .show(requireActivity().getSupportFragmentManager(), DIALOG_CREATE_DOCUMENT);
+    }
+
+    @Override
+    public void newSpreadsheet() {
+        ChooseTemplateDialogFragment.newInstance(mFile, ChooseTemplateDialogFragment.Type.SPREADSHEET)
+                .show(requireActivity().getSupportFragmentManager(), DIALOG_CREATE_DOCUMENT);
+    }
+
+    @Override
+    public void newPresentation() {
+        ChooseTemplateDialogFragment.newInstance(mFile, ChooseTemplateDialogFragment.Type.PRESENTATION)
+                .show(requireActivity().getSupportFragmentManager(), DIALOG_CREATE_DOCUMENT);
+    }
+
     /**
      * Handler for multiple selection mode.
      * <p>
@@ -776,27 +804,27 @@ public class OCFileListFragment extends ExtendedListFragment implements
                         // check if API >= 19
                         if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) {
                             Snackbar.make(getRecyclerView(), R.string.end_to_end_encryption_not_supported,
-                                    Snackbar.LENGTH_LONG).show();
+                                Snackbar.LENGTH_LONG).show();
                             return;
                         }
 
-                    Account account = ((FileActivity) mContainerActivity).getAccount();
+                        Account account = ((FileActivity) mContainerActivity).getAccount();
 
-                    // check if e2e app is enabled
-                    OCCapability ocCapability = mContainerActivity.getStorageManager().getCapability(account.name);
+                        // check if e2e app is enabled
+                        OCCapability ocCapability = mContainerActivity.getStorageManager().getCapability(account.name);
 
-                    if (ocCapability.getEndToEndEncryption().isFalse() ||
-                            ocCapability.getEndToEndEncryption().isUnknown()) {
-                        Snackbar.make(getRecyclerView(), R.string.end_to_end_encryption_not_enabled,
-                                Snackbar.LENGTH_LONG).show();
-                        return;
-                    }// check if keys are stored
-                    ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(
-                            getContext().getContentResolver());
+                        if (ocCapability.getEndToEndEncryption().isFalse() ||
+                                ocCapability.getEndToEndEncryption().isUnknown()) {
+                            Snackbar.make(getRecyclerView(), R.string.end_to_end_encryption_not_enabled,
+                                    Snackbar.LENGTH_LONG).show();
+                            return;
+                        }// check if keys are stored
+                        ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(
+                                getContext().getContentResolver());
 
 
-                    String publicKey = arbitraryDataProvider.getValue(account, EncryptionUtils.PUBLIC_KEY);
-                    String privateKey = arbitraryDataProvider.getValue(account, EncryptionUtils.PRIVATE_KEY);
+                        String publicKey = arbitraryDataProvider.getValue(account, EncryptionUtils.PUBLIC_KEY);
+                        String privateKey = arbitraryDataProvider.getValue(account, EncryptionUtils.PRIVATE_KEY);
 
                         if (publicKey.isEmpty() || privateKey.isEmpty()) {
                             Log_OC.d(TAG, "no public key for " + account.name);
@@ -809,30 +837,35 @@ public class OCFileListFragment extends ExtendedListFragment implements
                             // update state and view of this fragment
                             searchFragment = false;
 
-                        if (mContainerActivity instanceof FolderPickerActivity &&
-                                ((FolderPickerActivity) mContainerActivity)
-                                        .isDoNotEnterEncryptedFolder()) {
-                            Snackbar.make(getRecyclerView(),
-                                    R.string.copy_move_to_encrypted_folder_not_supported,
-                                    Snackbar.LENGTH_LONG).show();
-                        } else {
-                            listDirectory(file, MainApp.isOnlyOnDevice(), false);
-                            // then, notify parent activity to let it update its state and view
-                            mContainerActivity.onBrowsedDownTo(file);
-                            // save index and top position
-                            saveIndexAndTopPosition(position);
+                            if (mContainerActivity instanceof FolderPickerActivity &&
+                                    ((FolderPickerActivity) mContainerActivity)
+                                            .isDoNotEnterEncryptedFolder()) {
+                                Snackbar.make(getRecyclerView(),
+                                        R.string.copy_move_to_encrypted_folder_not_supported,
+                                        Snackbar.LENGTH_LONG).show();
+                            } else {
+                                listDirectory(file, MainApp.isOnlyOnDevice(), false);
+                                // then, notify parent activity to let it update its state and view
+                                mContainerActivity.onBrowsedDownTo(file);
+                                // save index and top position
+                                saveIndexAndTopPosition(position);
+                            }
                         }
+                    } else {
+                        // update state and view of this fragment
+                        searchFragment = false;
+                        listDirectory(file, MainApp.isOnlyOnDevice(), false);
+                        // then, notify parent activity to let it update its state and view
+                        mContainerActivity.onBrowsedDownTo(file);
+                        // save index and top position
+                        saveIndexAndTopPosition(position);
                     }
-                } else {
-                    // update state and view of this fragment
-                    searchFragment = false;
-                    listDirectory(file, MainApp.isOnlyOnDevice(), false);
-                    // then, notify parent activity to let it update its state and view
-                    mContainerActivity.onBrowsedDownTo(file);
-                    // save index and top position
-                    saveIndexAndTopPosition(position);
-                }
 
+                } else if (mFileSelectable) {
+                    Intent intent = new Intent();
+                    intent.putExtra(FolderPickerActivity.EXTRA_FILES, file);
+                    getActivity().setResult(Activity.RESULT_OK, intent);
+                    getActivity().finish();
                 } else if (!mOnlyFoldersClickable) { // Click on a file
                     if (PreviewImageFragment.canBePreviewed(file)) {
                         // preview image - it handles the download, if needed
@@ -865,10 +898,18 @@ public class OCFileListFragment extends ExtendedListFragment implements
                             mContainerActivity.getFileOperationsHelper().openFile(file);
                         }
                     } else {
-                        if (PreviewMediaFragment.canBePreviewed(file) && AccountUtils.getServerVersion(
-                                AccountUtils.getCurrentOwnCloudAccount(getContext())).isMediaStreamingSupported()) {
+                        Account account = AccountUtils.getCurrentOwnCloudAccount(getContext());
+                        OCCapability capability = mContainerActivity.getStorageManager().getCapability(account.name);
+
+                        if (PreviewMediaFragment.canBePreviewed(file) && AccountUtils.getServerVersion(account)
+                                .isMediaStreamingSupported()) {
                             // stream media preview on >= NC14
                             ((FileDisplayActivity) mContainerActivity).startMediaPreview(file, 0, true, true, true);
+                        }
+
+                        if (capability.getRichDocumentsMimeTypeList().contains(file.getMimeType()) &&
+                            android.os.Build.VERSION.SDK_INT >= RichDocumentsWebView.MINIMUM_API) {
+                            mContainerActivity.getFileOperationsHelper().openFileAsRichDocument(file, getContext());
                         } else {
                             // automatic download, preview on finish
                             ((FileDisplayActivity) mContainerActivity).startDownloadForPreview(file);
@@ -930,6 +971,10 @@ public class OCFileListFragment extends ExtendedListFragment implements
                     mContainerActivity.getFileOperationsHelper().streamMediaFile(singleFile);
                     return true;
                 }
+                case R.id.action_open_file_as_richdocument: {
+                    mContainerActivity.getFileOperationsHelper().openFileAsRichDocument(singleFile, getContext());
+                    return true;
+                }
                 case R.id.action_rename_file: {
                     RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(singleFile);
                     dialog.show(getFragmentManager(), FileDetailFragment.FTAG_RENAME_FILE);
@@ -1097,7 +1142,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
                     });
                 }
 
-                mAdapter.swapDirectory(directory, storageManager, onlyOnDevice);
+                mAdapter.swapDirectory(directory, storageManager, onlyOnDevice, mLimitToMimeType);
                 if (mFile == null || !mFile.equals(directory)) {
                     getRecyclerView().scrollToPosition(0);
                 }

+ 23 - 1
src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java

@@ -61,7 +61,9 @@ import com.owncloud.android.lib.resources.status.OCCapability;
 import com.owncloud.android.operations.SynchronizeFileOperation;
 import com.owncloud.android.services.OperationsService;
 import com.owncloud.android.ui.activity.ConflictsResolveActivity;
+import com.owncloud.android.ui.activity.ExternalSiteWebView;
 import com.owncloud.android.ui.activity.FileActivity;
+import com.owncloud.android.ui.activity.RichDocumentsWebView;
 import com.owncloud.android.ui.activity.ShareActivity;
 import com.owncloud.android.ui.dialog.SendShareDialog;
 import com.owncloud.android.ui.events.EncryptionEvent;
@@ -249,6 +251,18 @@ public class FileOperationsHelper {
             List<ResolveInfo> launchables = mFileActivity.getPackageManager().
                     queryIntentActivities(openFileWithIntent, PackageManager.GET_RESOLVED_FILTER);
 
+            if (launchables.isEmpty()) {
+                Account account = mFileActivity.getAccount();
+                OCCapability capability = mFileActivity.getStorageManager().getCapability(account.name);
+                if (capability.getRichDocumentsMimeTypeList().contains(file.getMimeType())) {
+                    openFileAsRichDocument(file, mFileActivity);
+                    return;
+                } else {
+                    DisplayUtils.showSnackMessage(mFileActivity, R.string.file_list_no_app_for_file_type);
+                    return;
+                }
+            }
+
             mFileActivity.showLoadingDialog(mFileActivity.getResources().getString(R.string.sync_in_progress));
             new Thread(new Runnable() {
                 @Override
@@ -272,7 +286,7 @@ public class FileOperationsHelper {
                         i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, account);
                         mFileActivity.startActivity(i);
                     } else {
-                        if (launchables != null && launchables.size() > 0) {
+                        if (!launchables.isEmpty()) {
                             try {
                                 if (!result.isSuccess()) {
                                     DisplayUtils.showSnackMessage(mFileActivity, R.string.file_not_synced);
@@ -307,6 +321,14 @@ public class FileOperationsHelper {
         }
     }
 
+    public void openFileAsRichDocument(OCFile file, Context context) {
+        Intent collaboraWebViewIntent = new Intent(context, RichDocumentsWebView.class);
+        collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_TITLE, "Collabora");
+        collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_FILE, file);
+        collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_SHOW_SIDEBAR, false);
+        context.startActivity(collaboraWebViewIntent);
+    }
+
     @NonNull
     private Intent createOpenFileIntent(OCFile file) {
         String storagePath = file.getStoragePath();

+ 0 - 3
src/main/java/com/owncloud/android/utils/DisplayUtils.java

@@ -36,7 +36,6 @@ import android.graphics.Point;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.PictureDrawable;
 import android.net.Uri;
-import android.os.Build;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.StringRes;
@@ -739,7 +738,6 @@ public final class DisplayUtils {
     // Copied from https://raw.githubusercontent.com/nextcloud/talk-android/8ec8606bc61878e87e3ac8ad32c8b72d4680013c/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java
     // under GPL3
     public static void useCompatVectorIfNeeded() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
             try {
                 @SuppressLint("RestrictedApi") AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();
                 Class<?> inflateDelegateClass = Class.forName("android.support.v7.widget.AppCompatDrawableManager$InflateDelegate");
@@ -756,7 +754,6 @@ public final class DisplayUtils {
             } catch (Exception e) {
                 Log.e(TAG, "Failed to use reflection to enable proper vector scaling");
             }
-        }
     }
 
     public static int convertDpToPixel(float dp, Context context) {

+ 43 - 0
src/main/res/layout/choose_template.xml

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Nextcloud Android client application
+
+  @author Tobias Kaminsky
+  Copyright (C) 2018 Tobias Kaminsky
+  Copyright (C) 2018 Nextcloud GmbH.
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program. If not, see <https://www.gnu.org/licenses/>.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:gravity="clip_horizontal"
+              android:orientation="vertical"
+              android:padding="@dimen/standard_padding">
+
+    <android.support.v7.widget.RecyclerView
+        android:id="@+id/list"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1">
+    </android.support.v7.widget.RecyclerView>
+
+    <EditText
+        android:id="@+id/filename"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:hint="@string/filename_hint"
+        android:inputType="text"
+        android:importantForAutofill="no" />
+</LinearLayout>

+ 3 - 2
src/main/res/layout/externalsite_webview.xml

@@ -23,7 +23,8 @@
                                         android:layout_width="match_parent"
                                         android:layout_height="match_parent"
                                         android:clickable="true"
-                                        android:fitsSystemWindows="true">
+                                        android:fitsSystemWindows="true"
+                                        android:focusable="true">
 
     <!-- The main content view -->
     <LinearLayout
@@ -47,4 +48,4 @@
         android:layout_height="match_parent"
         android:layout_gravity="start"/>
 
-</android.support.v4.widget.DrawerLayout>
+</android.support.v4.widget.DrawerLayout>

+ 117 - 12
src/main/res/layout/file_list_actions_bottom_sheet_fragment.xml

@@ -17,10 +17,10 @@
   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical">
+              xmlns:tools="http://schemas.android.com/tools"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:orientation="vertical">
 
     <TextView
         android:id="@+id/add_to_cloud"
@@ -28,7 +28,7 @@
         android:layout_height="wrap_content"
         android:padding="@dimen/standard_padding"
         android:text="@string/add_to_cloud"
-        android:textSize="@dimen/bottom_sheet_text_size" />
+        android:textSize="@dimen/bottom_sheet_text_size"/>
 
     <LinearLayout
         android:id="@+id/menu_upload_files"
@@ -47,7 +47,7 @@
             android:layout_height="wrap_content"
             android:contentDescription="@null"
             android:src="@drawable/ic_action_upload"
-            android:tint="@color/primary" />
+            android:tint="@color/primary"/>
 
         <TextView
             android:layout_width="wrap_content"
@@ -57,7 +57,7 @@
             android:layout_marginStart="@dimen/standard_margin"
             android:text="@string/upload_files"
             android:textColor="@color/black"
-            android:textSize="@dimen/bottom_sheet_text_size" />
+            android:textSize="@dimen/bottom_sheet_text_size"/>
 
     </LinearLayout>
 
@@ -78,7 +78,7 @@
             android:layout_height="wrap_content"
             android:contentDescription="@null"
             android:src="@drawable/ic_import"
-            android:tint="@color/primary" />
+            android:tint="@color/primary"/>
 
         <TextView
             android:layout_width="wrap_content"
@@ -88,7 +88,7 @@
             android:layout_marginStart="@dimen/standard_margin"
             android:text="@string/upload_content_from_other_apps"
             android:textColor="@color/black"
-            android:textSize="@dimen/bottom_sheet_text_size" />
+            android:textSize="@dimen/bottom_sheet_text_size"/>
 
     </LinearLayout>
 
@@ -100,7 +100,7 @@
         android:layout_marginLeft="@dimen/standard_margin"
         android:layout_marginRight="@dimen/standard_margin"
         android:layout_marginTop="@dimen/standard_half_margin"
-        android:background="@color/list_divider_background" />
+        android:background="@color/list_divider_background"/>
 
     <LinearLayout
         android:id="@+id/menu_mkdir"
@@ -119,7 +119,7 @@
             android:layout_height="wrap_content"
             android:contentDescription="@null"
             android:src="@drawable/ic_action_create_dir"
-            android:tint="@color/primary" />
+            android:tint="@color/primary"/>
 
         <TextView
             android:layout_width="wrap_content"
@@ -129,8 +129,113 @@
             android:layout_marginStart="@dimen/standard_margin"
             android:text="@string/create_new_folder"
             android:textColor="@color/black"
-            android:textSize="@dimen/bottom_sheet_text_size" />
+            android:textSize="@dimen/bottom_sheet_text_size"/>
 
     </LinearLayout>
 
+    <LinearLayout
+        android:id="@+id/templates"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:visibility="gone">
+
+        <View
+            android:id="@+id/dividerCreateTemplates"
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:layout_marginBottom="@dimen/standard_half_margin"
+            android:layout_marginLeft="@dimen/standard_margin"
+            android:layout_marginRight="@dimen/standard_margin"
+            android:layout_marginTop="@dimen/standard_half_margin"
+            android:background="@color/list_divider_background"/>
+
+        <LinearLayout
+            android:id="@+id/menu_new_document"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:paddingBottom="@dimen/standard_padding"
+            android:paddingLeft="@dimen/standard_padding"
+            android:paddingRight="@dimen/standard_padding"
+            android:paddingTop="@dimen/standard_half_padding"
+            tools:ignore="UseCompoundDrawables">
+
+            <ImageView
+                android:id="@+id/menu_icon_new_document"
+                android:layout_width="16dp"
+                android:layout_height="16dp"
+                android:contentDescription="@null"
+                android:src="@drawable/file_doc"/>
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_marginLeft="@dimen/standard_margin"
+                android:layout_marginStart="@dimen/standard_margin"
+                android:text="@string/create_new_document"
+                android:textColor="@color/black"
+                android:textSize="@dimen/bottom_sheet_text_size"/>
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/menu_new_spreadsheet"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:paddingBottom="@dimen/standard_padding"
+            android:paddingLeft="@dimen/standard_padding"
+            android:paddingRight="@dimen/standard_padding"
+            android:paddingTop="@dimen/standard_half_padding"
+            tools:ignore="UseCompoundDrawables">
+
+            <ImageView
+                android:id="@+id/menu_icon_new_spreadsheet"
+                android:layout_width="16dp"
+                android:layout_height="16dp"
+                android:contentDescription="@null"
+                android:src="@drawable/file_xls"/>
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_marginLeft="@dimen/standard_margin"
+                android:layout_marginStart="@dimen/standard_margin"
+                android:text="@string/create_new_spreadsheet"
+                android:textColor="@color/black"
+                android:textSize="@dimen/bottom_sheet_text_size"/>
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/menu_new_presentation"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:paddingBottom="@dimen/standard_padding"
+            android:paddingLeft="@dimen/standard_padding"
+            android:paddingRight="@dimen/standard_padding"
+            android:paddingTop="@dimen/standard_half_padding"
+            tools:ignore="UseCompoundDrawables">
+
+            <ImageView
+                android:id="@+id/menu_icon_new_presentation"
+                android:layout_width="16dp"
+                android:layout_height="16dp"
+                android:contentDescription="@null"
+                android:src="@drawable/file_ppt"/>
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_marginLeft="@dimen/standard_margin"
+                android:layout_marginStart="@dimen/standard_margin"
+                android:text="@string/create_new_presentation"
+                android:textColor="@color/black"
+                android:textSize="@dimen/bottom_sheet_text_size"/>
+        </LinearLayout>
+    </LinearLayout>
+
 </LinearLayout>

+ 67 - 0
src/main/res/layout/files_picker.xml

@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Nextcloud Android client application
+
+  @author Tobias Kaminsky
+  Copyright (C) 2018 Tobias Kaminsky
+  Copyright (C) 2018 Nextcloud GmbH.
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program. If not, see <https://www.gnu.org/licenses/>.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical">
+
+    <include
+        layout="@layout/toolbar_standard"/>
+
+    <FrameLayout
+        android:id="@+id/fragment_container"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"/>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:contentDescription="@null"
+            android:src="@drawable/uploader_list_separator"/>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:orientation="horizontal"
+        android:padding="@dimen/standard_padding">
+
+        <android.support.v7.widget.AppCompatButton
+            android:id="@+id/folder_picker_btn_cancel"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="@dimen/standard_half_margin"
+            android:layout_marginRight="@dimen/standard_half_margin"
+            android:layout_weight="1"
+            android:text="@string/common_cancel"
+            android:theme="@style/Button"/>
+    </LinearLayout>
+
+</LinearLayout>

+ 79 - 0
src/main/res/layout/richdocuments_webview.xml

@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Nextcloud Android client application
+
+  @author Tobias Kaminsky
+  Copyright (C) 2018 Tobias Kaminsky
+  Copyright (C) 2018 Nextcloud GmbH.
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program. If not, see <https://www.gnu.org/licenses/>.
+-->
+<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                                        xmlns:app="http://schemas.android.com/apk/res-auto"
+                                        android:id="@+id/drawer_layout"
+                                        android:layout_width="match_parent"
+                                        android:layout_height="match_parent"
+                                        android:clickable="true"
+                                        android:fitsSystemWindows="true"
+                                        android:focusable="true">
+
+    <!-- The main content view -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:orientation="vertical">
+
+        <ImageView
+            android:id="@+id/thumbnail"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:contentDescription="@string/file_icon"
+            app:srcCompat="@drawable/file"/>
+
+        <TextView
+            android:id="@+id/filename"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="middle"
+            android:text="@string/placeholder_filename"
+            android:textColor="@color/black"
+            android:textSize="20sp"
+            android:textStyle="bold"/>
+
+        <ProgressBar
+            android:id="@+id/progressBar2"
+            style="?android:attr/progressBarStyle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/standard_margin"
+            android:indeterminate="true"/>
+
+        <WebView
+            android:id="@+id/webView"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone"/>
+
+
+    </LinearLayout>
+
+    <include
+        layout="@layout/drawer"
+        android:layout_width="@dimen/drawer_width"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"
+        android:visibility="gone"/>
+
+</android.support.v4.widget.DrawerLayout>

+ 48 - 0
src/main/res/layout/template_button.xml

@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Nextcloud Android client application
+
+  @author Tobias Kaminsky
+  Copyright (C) 2018 Tobias Kaminsky
+  Copyright (C) 2018 Nextcloud GmbH.
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program. If not, see <https://www.gnu.org/licenses/>.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              xmlns:tools="http://schemas.android.com/tools"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:orientation="vertical"
+              android:paddingTop="@dimen/standard_padding"
+              tools:ignore="UseCompoundDrawables">
+
+    <ImageView
+        android:id="@+id/thumbnail"
+        android:layout_width="@dimen/share_icon_size"
+        android:layout_height="@dimen/share_icon_size"
+        android:layout_gravity="center_horizontal"
+        android:contentDescription="@string/thumbnail"
+        android:src="@drawable/file_doc"/>
+
+    <TextView
+        android:id="@+id/name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:ellipsize="middle"
+        android:gravity="center_horizontal"
+        android:paddingTop="@dimen/standard_half_padding"
+        android:textColor="@color/black"/>
+</LinearLayout>

+ 6 - 0
src/main/res/menu/file_actions_menu.xml

@@ -84,6 +84,12 @@
         app:showAsAction="never"
         android:showAsAction="never" />
 
+    <item
+        android:id="@+id/action_open_file_as_richdocument"
+        android:title="@string/actionbar_open_as_richdocument"
+        app:showAsAction="never"
+        android:showAsAction="never"/>
+
     <item
         android:id="@+id/action_sync_file"
         android:title="@string/filedetails_sync_file"

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

@@ -811,6 +811,7 @@
     <string name="stream_not_possible_headline">Internal streaming not possible</string>
     <string name="stream_not_possible_message">Please download media instead or use external app.</string>
     <string name="folder_already_exists">Folder already exists</string>
+    <string name="actionbar_open_as_richdocument">Open with Collabora</string>
     <string name="notification_icon">Notification icon</string>
     <string name="folder_confirm_create">Create</string>
     <string name="file_delete">Delete</string>
@@ -818,7 +819,6 @@
     <string name="file_rename">Rename</string>
     <string name="fab_label">Add or upload</string>
     <string name="account_creation_failed">Account creation failed</string>
-
     <string name="single_sign_on_request_token" formatted="true">Allow %1$s to access your Nextcloud account %2$s?</string>
     <string name="permission_deny">Deny</string>
     <string name="permission_allow">Allow</string>
@@ -831,4 +831,14 @@
     <string name="no_pdf_app_available">No App available to handle PDF</string>
     <string name="share_via_link_hide_download">Hide download</string>
 
+    <string name="richdocuments_failed_to_load_document">Failed to load document!</string>
+    <string name="create_new_document">Create new document</string>
+    <string name="create_new_spreadsheet">Create new spreadsheet</string>
+    <string name="create_new_presentation">Create new presentation</string>
+    <string name="select_template">Select template</string>
+    <string name="filename_hint">Filename</string>
+    <string name="thumbnail">Thumbnail</string>
+    <string name="enter_filename">Please enter a filename</string>
+    <string name="create_file_from_template">Creating file from template…</string>
+    <string name="error_retrieving_templates">Error retrieving templates</string>
 </resources>