From d1611f63349ef4f099310df80453b1e7c6c2546c Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Fri, 1 Nov 2019 12:09:08 +0100 Subject: [PATCH 01/14] Direct editing support - abstract EditorWebView - support direct editing endpoint Signed-off-by: tobiasKaminsky --- build.gradle | 2 +- settings.gradle | 2 +- src/main/AndroidManifest.xml | 5 +- .../nextcloud/client/di/ComponentsModule.java | 12 +- .../datamodel/FileDataStorageManager.java | 153 ++++++------ .../owncloud/android/datamodel/Template.java | 5 +- .../com/owncloud/android/db/ProviderMeta.java | 4 +- .../android/files/FetchTemplateOperation.java | 8 +- .../android/files/FileMenuFilter.java | 52 ++-- .../operations/RichDocumentsUrlOperation.java | 9 +- .../providers/FileContentProvider.java | 21 +- .../android/ui/activity/EditorWebView.java | 228 ++++++++++++++++++ ...w.java => RichDocumentsEditorWebView.java} | 204 ++++------------ .../android/ui/activity/TextEditorWebView.kt | 37 +++ .../android/ui/asynctasks/LoadUrlTask.java | 62 ++--- .../android/ui/asynctasks/PrintAsyncTask.java | 8 +- .../dialog/ChooseTemplateDialogFragment.java | 4 +- .../fragment/OCFileListBottomSheetDialog.java | 4 +- .../ui/fragment/OCFileListFragment.java | 33 ++- .../ui/helpers/FileOperationsHelper.java | 15 +- src/main/res/layout/richdocuments_webview.xml | 2 - src/main/res/menu/item_file.xml | 12 +- src/main/res/values/strings.xml | 1 + 23 files changed, 533 insertions(+), 350 deletions(-) create mode 100644 src/main/java/com/owncloud/android/ui/activity/EditorWebView.java rename src/main/java/com/owncloud/android/ui/activity/{RichDocumentsWebView.java => RichDocumentsEditorWebView.java} (66%) create mode 100644 src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt diff --git a/build.gradle b/build.gradle index f51fd32b4cef..c431eab4da81 100644 --- a/build.gradle +++ b/build.gradle @@ -63,7 +63,7 @@ ext { daggerVersion = "2.25.3" markwonVersion = "4.2.0" prismVersion = "2.0.0" - androidLibraryVersion = "master-SNAPSHOT" + androidLibraryVersion = "directEditing-SNAPSHOT" travisBuild = System.getenv("TRAVIS") == "true" diff --git a/settings.gradle b/settings.gradle index 05a2e7e7d7eb..520b64d902c9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ include ':' -//include 'nextcloud-android-library' +//include ':nextcloud-android-library' diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 2410c479a43b..4edba5c5e1c1 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -133,7 +133,10 @@ + diff --git a/src/main/java/com/nextcloud/client/di/ComponentsModule.java b/src/main/java/com/nextcloud/client/di/ComponentsModule.java index 558ef0afa9a5..9f0b9b6e98d4 100644 --- a/src/main/java/com/nextcloud/client/di/ComponentsModule.java +++ b/src/main/java/com/nextcloud/client/di/ComponentsModule.java @@ -54,11 +54,12 @@ import com.owncloud.android.ui.activity.PassCodeActivity; import com.owncloud.android.ui.activity.ReceiveExternalFilesActivity; import com.owncloud.android.ui.activity.RequestCredentialsActivity; -import com.owncloud.android.ui.activity.RichDocumentsWebView; +import com.owncloud.android.ui.activity.RichDocumentsEditorWebView; import com.owncloud.android.ui.activity.SettingsActivity; import com.owncloud.android.ui.activity.ShareActivity; import com.owncloud.android.ui.activity.SsoGrantPermissionActivity; import com.owncloud.android.ui.activity.SyncedFoldersActivity; +import com.owncloud.android.ui.activity.TextEditorWebView; import com.owncloud.android.ui.activity.UploadFilesActivity; import com.owncloud.android.ui.activity.UploadListActivity; import com.owncloud.android.ui.activity.UserInfoActivity; @@ -106,15 +107,12 @@ abstract class ComponentsModule { @ContributesAndroidInjector abstract ManageAccountsActivity manageAccountsActivity(); @ContributesAndroidInjector abstract ManageSpaceActivity manageSpaceActivity(); @ContributesAndroidInjector abstract NotificationsActivity notificationsActivity(); - - @ContributesAndroidInjector - abstract CommunityActivity participateActivity(); + @ContributesAndroidInjector abstract CommunityActivity participateActivity(); @ContributesAndroidInjector abstract PassCodeActivity passCodeActivity(); @ContributesAndroidInjector abstract PreviewImageActivity previewImageActivity(); @ContributesAndroidInjector abstract PreviewVideoActivity previewVideoActivity(); @ContributesAndroidInjector abstract ReceiveExternalFilesActivity receiveExternalFilesActivity(); @ContributesAndroidInjector abstract RequestCredentialsActivity requestCredentialsActivity(); - @ContributesAndroidInjector abstract RichDocumentsWebView richDocumentsWebView(); @ContributesAndroidInjector abstract SettingsActivity settingsActivity(); @ContributesAndroidInjector abstract ShareActivity shareActivity(); @ContributesAndroidInjector abstract SsoGrantPermissionActivity ssoGrantPermissionActivity(); @@ -126,6 +124,9 @@ abstract class ComponentsModule { @ContributesAndroidInjector abstract WhatsNewActivity whatsNewActivity(); @ContributesAndroidInjector abstract EtmActivity etmActivity(); + @ContributesAndroidInjector abstract RichDocumentsEditorWebView richDocumentsWebView(); + @ContributesAndroidInjector abstract TextEditorWebView textEditorWebView(); + @ContributesAndroidInjector abstract ExtendedListFragment extendedListFragment(); @ContributesAndroidInjector abstract FileDetailFragment fileDetailFragment(); @ContributesAndroidInjector abstract LocalFileListFragment localFileListFragment(); @@ -137,7 +138,6 @@ abstract class ComponentsModule { @ContributesAndroidInjector abstract ContactListFragment chooseContactListFragment(); @ContributesAndroidInjector abstract PreviewMediaFragment previewMediaFragment(); @ContributesAndroidInjector abstract PreviewTextFragment previewTextFragment(); - @ContributesAndroidInjector abstract PhotoFragment photoFragment(); @ContributesAndroidInjector abstract MultipleAccountsDialog multipleAccountsDialog(); diff --git a/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java index ad7fbb6e5687..5bd0e448436d 100644 --- a/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -39,6 +39,7 @@ import com.google.gson.JsonSyntaxException; import com.owncloud.android.MainApp; import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; +import com.owncloud.android.lib.common.DirectEditing; import com.owncloud.android.lib.common.network.WebdavEntry; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; @@ -2030,6 +2031,8 @@ private ContentValues createContentValues(String accountName, OCCapability capab cv.put(ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_TEMPLATES, capability.getRichDocumentsTemplatesAvailable() .getValue()); cv.put(ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_PRODUCT_NAME, capability.getRichDocumentsProductName()); + cv.put(ProviderTableMeta.CAPABILITIES_DIRECT_EDITING, new Gson().toJson(capability.getDirectEditing())); + return cv; } @@ -2084,97 +2087,72 @@ private OCCapability createCapabilityInstance(Cursor c) { OCCapability capability = null; if (c != null) { capability = new OCCapability(); - capability.setId(c.getLong(c.getColumnIndex(ProviderTableMeta._ID))); - capability.setAccountName(c.getString(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_ACCOUNT_NAME))); - capability.setVersionMayor(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_VERSION_MAYOR))); - capability.setVersionMinor(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_VERSION_MINOR))); - capability.setVersionMicro(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_VERSION_MICRO))); - capability.setVersionString(c.getString(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_VERSION_STRING))); - capability.setVersionEdition(c.getString(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_VERSION_EDITION))); - capability.setExtendedSupport(CapabilityBooleanType.fromValue(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_EXTENDED_SUPPORT)))); - capability.setCorePollInterval(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_CORE_POLLINTERVAL))); - capability.setFilesSharingApiEnabled(CapabilityBooleanType.fromValue(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_SHARING_API_ENABLED)))); - capability.setFilesSharingPublicEnabled(CapabilityBooleanType.fromValue(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_ENABLED)))); - capability.setFilesSharingPublicPasswordEnforced(CapabilityBooleanType.fromValue(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_PASSWORD_ENFORCED)))); + capability.setId(getLong(c, ProviderTableMeta._ID)); + capability.setAccountName(getString(c, ProviderTableMeta.CAPABILITIES_ACCOUNT_NAME)); + capability.setVersionMayor(getInt(c, ProviderTableMeta.CAPABILITIES_VERSION_MAYOR)); + capability.setVersionMinor(getInt(c, ProviderTableMeta.CAPABILITIES_VERSION_MINOR)); + capability.setVersionMicro(getInt(c, ProviderTableMeta.CAPABILITIES_VERSION_MICRO)); + capability.setVersionString(getString(c, ProviderTableMeta.CAPABILITIES_VERSION_STRING)); + capability.setVersionEdition(getString(c, ProviderTableMeta.CAPABILITIES_VERSION_EDITION)); + capability.setExtendedSupport(getBoolean(c, ProviderTableMeta.CAPABILITIES_EXTENDED_SUPPORT)); + capability.setCorePollInterval(getInt(c, ProviderTableMeta.CAPABILITIES_CORE_POLLINTERVAL)); + capability.setFilesSharingApiEnabled(getBoolean(c, ProviderTableMeta.CAPABILITIES_SHARING_API_ENABLED)); + capability.setFilesSharingPublicEnabled( + getBoolean(c, ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_ENABLED)); + capability.setFilesSharingPublicPasswordEnforced( + getBoolean(c, ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_PASSWORD_ENFORCED)); capability.setFilesSharingPublicAskForOptionalPassword( - CapabilityBooleanType.fromValue( - c.getInt(c.getColumnIndex(ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_ASK_FOR_OPTIONAL_PASSWORD)))); - capability.setFilesSharingPublicExpireDateEnabled(CapabilityBooleanType.fromValue(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_ENABLED)))); - capability.setFilesSharingPublicExpireDateDays(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_DAYS))); - capability.setFilesSharingPublicExpireDateEnforced(CapabilityBooleanType.fromValue(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_ENFORCED)))); - capability.setFilesSharingPublicSendMail(CapabilityBooleanType.fromValue(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_SEND_MAIL)))); - capability.setFilesSharingPublicUpload(CapabilityBooleanType.fromValue(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_UPLOAD)))); - capability.setFilesSharingUserSendMail(CapabilityBooleanType.fromValue(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_SHARING_USER_SEND_MAIL)))); - capability.setFilesSharingResharing(CapabilityBooleanType.fromValue(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_SHARING_RESHARING)))); - capability.setFilesSharingFederationOutgoing(CapabilityBooleanType.fromValue(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_SHARING_FEDERATION_OUTGOING)))); - capability.setFilesSharingFederationIncoming(CapabilityBooleanType.fromValue(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_SHARING_FEDERATION_INCOMING)))); - capability.setFilesBigFileChunking(CapabilityBooleanType.fromValue(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_FILES_BIGFILECHUNKING)))); - capability.setFilesUndelete(CapabilityBooleanType.fromValue(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_FILES_UNDELETE)))); - capability.setFilesVersioning(CapabilityBooleanType.fromValue(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_FILES_VERSIONING)))); - capability.setFilesFileDrop(CapabilityBooleanType.fromValue(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_FILES_DROP)))); - capability.setExternalLinks(CapabilityBooleanType.fromValue(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_EXTERNAL_LINKS)))); - capability.setServerName(c.getString(c.getColumnIndex(ProviderTableMeta.CAPABILITIES_SERVER_NAME))); - capability.setServerColor(c.getString(c.getColumnIndex(ProviderTableMeta.CAPABILITIES_SERVER_COLOR))); - capability.setServerTextColor( - c.getString(c.getColumnIndex(ProviderTableMeta.CAPABILITIES_SERVER_TEXT_COLOR))); - capability.setServerElementColor( - c.getString(c.getColumnIndex(ProviderTableMeta.CAPABILITIES_SERVER_ELEMENT_COLOR))); - capability.setServerBackground(c.getString(c.getColumnIndex( - ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_URL))); - capability.setServerSlogan(c.getString(c.getColumnIndex(ProviderTableMeta.CAPABILITIES_SERVER_SLOGAN))); - capability.setEndToEndEncryption(CapabilityBooleanType.fromValue(c.getInt(c - .getColumnIndex(ProviderTableMeta.CAPABILITIES_END_TO_END_ENCRYPTION)))); - capability.setServerBackgroundDefault(CapabilityBooleanType.fromValue( - c.getInt(c.getColumnIndex(ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_DEFAULT)))); - capability.setServerBackgroundPlain(CapabilityBooleanType.fromValue( - 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)))); - capability.setRichDocumentsDirectEditing(CapabilityBooleanType.fromValue(c.getInt( - c.getColumnIndex(ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_DIRECT_EDITING)))); - capability.setRichDocumentsTemplatesAvailable(CapabilityBooleanType.fromValue(c.getInt( - c.getColumnIndex(ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_TEMPLATES)))); + getBoolean(c, ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_ASK_FOR_OPTIONAL_PASSWORD)); + capability.setFilesSharingPublicExpireDateEnabled( + getBoolean(c, ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_ENABLED)); + capability.setFilesSharingPublicExpireDateDays( + getInt(c, ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_DAYS)); + capability.setFilesSharingPublicExpireDateEnforced( + getBoolean(c, ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_EXPIRE_DATE_ENFORCED)); + capability.setFilesSharingPublicSendMail( + getBoolean(c, ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_SEND_MAIL)); + capability.setFilesSharingPublicUpload(getBoolean(c, ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_UPLOAD)); + capability.setFilesSharingUserSendMail(getBoolean(c, ProviderTableMeta.CAPABILITIES_SHARING_USER_SEND_MAIL)); + capability.setFilesSharingResharing(getBoolean(c, ProviderTableMeta.CAPABILITIES_SHARING_RESHARING)); + capability.setFilesSharingFederationOutgoing( + getBoolean(c, ProviderTableMeta.CAPABILITIES_SHARING_FEDERATION_OUTGOING)); + capability.setFilesSharingFederationIncoming( + getBoolean(c, ProviderTableMeta.CAPABILITIES_SHARING_FEDERATION_INCOMING)); + capability.setFilesBigFileChunking(getBoolean(c, ProviderTableMeta.CAPABILITIES_FILES_BIGFILECHUNKING)); + capability.setFilesUndelete(getBoolean(c, ProviderTableMeta.CAPABILITIES_FILES_UNDELETE)); + capability.setFilesVersioning(getBoolean(c, ProviderTableMeta.CAPABILITIES_FILES_VERSIONING)); + capability.setFilesFileDrop(getBoolean(c, ProviderTableMeta.CAPABILITIES_FILES_DROP)); + capability.setExternalLinks(getBoolean(c, ProviderTableMeta.CAPABILITIES_EXTERNAL_LINKS)); + capability.setServerName(getString(c, ProviderTableMeta.CAPABILITIES_SERVER_NAME)); + capability.setServerColor(getString(c, ProviderTableMeta.CAPABILITIES_SERVER_COLOR)); + capability.setServerTextColor(getString(c, ProviderTableMeta.CAPABILITIES_SERVER_TEXT_COLOR)); + capability.setServerElementColor(getString(c, ProviderTableMeta.CAPABILITIES_SERVER_ELEMENT_COLOR)); + capability.setServerBackground(getString(c, ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_URL)); + capability.setServerSlogan(getString(c, ProviderTableMeta.CAPABILITIES_SERVER_SLOGAN)); + capability.setEndToEndEncryption(getBoolean(c, ProviderTableMeta.CAPABILITIES_END_TO_END_ENCRYPTION)); + capability.setServerBackgroundDefault( + getBoolean(c, ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_DEFAULT)); + capability.setServerBackgroundPlain(getBoolean(c, ProviderTableMeta.CAPABILITIES_SERVER_BACKGROUND_PLAIN)); + capability.setActivity(getBoolean(c, ProviderTableMeta.CAPABILITIES_ACTIVITY)); + capability.setRichDocuments(getBoolean(c, ProviderTableMeta.CAPABILITIES_RICHDOCUMENT)); + capability.setRichDocumentsDirectEditing( + getBoolean(c, ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_DIRECT_EDITING)); + capability.setRichDocumentsTemplatesAvailable( + getBoolean(c, ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_TEMPLATES)); String mimetypes = c.getString(c.getColumnIndex(ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_MIMETYPE_LIST)); if (mimetypes == null) { mimetypes = ""; } capability.setRichDocumentsMimeTypeList(Arrays.asList(mimetypes.split(","))); - String optionalMimetypes = c.getString(c.getColumnIndex( - ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_OPTIONAL_MIMETYPE_LIST)); + String optionalMimetypes = getString(c, ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_OPTIONAL_MIMETYPE_LIST); if (optionalMimetypes == null) { optionalMimetypes = ""; } capability.setRichDocumentsOptionalMimeTypeList(Arrays.asList(optionalMimetypes.split(","))); - capability.setRichDocumentsProductName( - c.getString(c.getColumnIndex(ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_PRODUCT_NAME))); + capability.setRichDocumentsProductName(getString(c, ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_PRODUCT_NAME)); + capability.setDirectEditing(new Gson().fromJson(getString(c, ProviderTableMeta.CAPABILITIES_DIRECT_EDITING), + DirectEditing.class)); } return capability; } @@ -2297,4 +2275,19 @@ public void deleteAllFiles() { } } + private String getString(Cursor cursor, String columnName) { + return cursor.getString(cursor.getColumnIndex(columnName)); + } + + private int getInt(Cursor cursor, String columnName) { + return cursor.getInt(cursor.getColumnIndex(columnName)); + } + + private long getLong(Cursor cursor, String columnName) { + return cursor.getLong(cursor.getColumnIndex(columnName)); + } + + private CapabilityBooleanType getBoolean(Cursor cursor, String columnName) { + return CapabilityBooleanType.fromValue(cursor.getInt(cursor.getColumnIndex(columnName))); + } } diff --git a/src/main/java/com/owncloud/android/datamodel/Template.java b/src/main/java/com/owncloud/android/datamodel/Template.java index dbdd7a6f9d41..bb5e821c55b5 100644 --- a/src/main/java/com/owncloud/android/datamodel/Template.java +++ b/src/main/java/com/owncloud/android/datamodel/Template.java @@ -37,10 +37,13 @@ @AllArgsConstructor @NoArgsConstructor public class Template { + public enum Type { + DOCUMENT, SPREADSHEET, PRESENTATION + } public int id; public String name; public String thumbnailLink; - public String type; + public Type type; public String extension; } diff --git a/src/main/java/com/owncloud/android/db/ProviderMeta.java b/src/main/java/com/owncloud/android/db/ProviderMeta.java index a9dad01cd914..ad42bec538d3 100644 --- a/src/main/java/com/owncloud/android/db/ProviderMeta.java +++ b/src/main/java/com/owncloud/android/db/ProviderMeta.java @@ -31,7 +31,7 @@ */ public class ProviderMeta { public static final String DB_NAME = "filelist"; - public static final int DB_VERSION = 51; + public static final int DB_VERSION = 52; private ProviderMeta() { // No instance @@ -192,9 +192,9 @@ static public class ProviderTableMeta implements BaseColumns { public static final String CAPABILITIES_RICHDOCUMENT_DIRECT_EDITING = "richdocument_direct_editing"; public static final String CAPABILITIES_RICHDOCUMENT_TEMPLATES = "richdocument_direct_templates"; public static final String CAPABILITIES_RICHDOCUMENT_PRODUCT_NAME = "richdocument_product_name"; - public static final String CAPABILITIES_DEFAULT_SORT_ORDER = CAPABILITIES_ACCOUNT_NAME + " collate nocase asc"; + public static final String CAPABILITIES_DIRECT_EDITING = "direct_editing"; //Columns of Uploads table public static final String UPLOADS_LOCAL_PATH = "local_path"; diff --git a/src/main/java/com/owncloud/android/files/FetchTemplateOperation.java b/src/main/java/com/owncloud/android/files/FetchTemplateOperation.java index c164a3c0235c..3c8abad3c315 100644 --- a/src/main/java/com/owncloud/android/files/FetchTemplateOperation.java +++ b/src/main/java/com/owncloud/android/files/FetchTemplateOperation.java @@ -79,10 +79,10 @@ protected RemoteOperationResult run(OwnCloudClient client) { JSONObject templateObject = templates.getJSONObject(i); templateArray.add(new Template(templateObject.getInt("id"), - templateObject.getString("name"), - templateObject.optString("preview"), - templateObject.getString("type"), - templateObject.getString("extension"))); + templateObject.getString("name"), + templateObject.optString("preview"), + Template.Type.valueOf(templateObject.getString("type")), + templateObject.getString("extension"))); } result = new RemoteOperationResult(true, getMethod); diff --git a/src/main/java/com/owncloud/android/files/FileMenuFilter.java b/src/main/java/com/owncloud/android/files/FileMenuFilter.java index 11ce2fc537bf..6cc35259f82f 100644 --- a/src/main/java/com/owncloud/android/files/FileMenuFilter.java +++ b/src/main/java/com/owncloud/android/files/FileMenuFilter.java @@ -30,11 +30,14 @@ import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; +import com.owncloud.android.lib.common.DirectEditing; +import com.owncloud.android.lib.common.Editor; 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.ui.activity.RichDocumentsEditorWebView; import com.owncloud.android.utils.MimeTypeUtil; +import com.owncloud.android.utils.NextcloudServer; import java.util.ArrayList; import java.util.Collection; @@ -172,6 +175,7 @@ private void filter(List toShow, OCCapability capability = mComponentsGetter.getStorageManager().getCapability(mAccount.name); boolean endToEndEncryptionEnabled = capability.getEndToEndEncryption().isTrue(); + filterEdit(toShow, toHide, capability); filterDownload(toShow, toHide, synchronizing); filterRename(toShow, toHide, synchronizing); filterMoveCopy(toShow, toHide, synchronizing); @@ -189,7 +193,6 @@ private void filter(List toShow, filterUnsetEncrypted(toShow, toHide, endToEndEncryptionEnabled); filterSetPictureAs(toShow, toHide); filterStream(toShow, toHide, isMediaSupported); - filterOpenAsRichDocument(toShow, toHide, capability, menu); } private void filterShareFile(List toShow, List toHide, OCCapability capability) { @@ -252,29 +255,40 @@ private void filterSetPictureAs(List toShow, List toHide) { } } - private void filterOpenAsRichDocument(List toShow, - List toHide, - OCCapability capability, - Menu menu) { + private void filterEdit(List toShow, + List toHide, + OCCapability capability + ) { String mimeType = mFiles.iterator().next().getMimeType(); - if (isSingleFile() && android.os.Build.VERSION.SDK_INT >= RichDocumentsWebView.MINIMUM_API && - (capability.getRichDocumentsMimeTypeList().contains(mimeType) || - capability.getRichDocumentsOptionalMimeTypeList().contains(mimeType)) && - capability.getRichDocumentsDirectEditing().isTrue()) { + if (isRichDocumentEditingSupported(capability, mimeType) || isEditorAvailable(capability, mimeType)) { + toShow.add(R.id.action_edit); + } else { + toHide.add(R.id.action_edit); + } + } - String openWith = mContext.getResources().getString(R.string.actionbar_open_as_richdocument_parameter); - String productName = capability.getRichDocumentsProductName(); - MenuItem item = menu.findItem(R.id.action_open_file_as_richdocument); + public static boolean isEditorAvailable(OCCapability capability, String mimeType) { + DirectEditing directEditing = capability.getDirectEditing(); - if (item != null) { - item.setTitle(String.format(openWith, productName)); + for (Editor editor : directEditing.editors.values()) { + if (editor.mimetypes.contains(mimeType) || editor.optionalMimetypes.contains(mimeType)) { + return true; } - - toShow.add(R.id.action_open_file_as_richdocument); - } else { - toHide.add(R.id.action_open_file_as_richdocument); } + + return false; + } + + /** + * This will be replaced by unified editor and can be removed once EOL of corresponding server version. + */ + @NextcloudServer(max = 18) + private boolean isRichDocumentEditingSupported(OCCapability capability, String mimeType) { + return isSingleFile() && android.os.Build.VERSION.SDK_INT >= RichDocumentsEditorWebView.MINIMUM_API && + (capability.getRichDocumentsMimeTypeList().contains(mimeType) || + capability.getRichDocumentsOptionalMimeTypeList().contains(mimeType)) && + capability.getRichDocumentsDirectEditing().isTrue(); } private void filterSync(List toShow, List toHide, boolean synchronizing) { diff --git a/src/main/java/com/owncloud/android/operations/RichDocumentsUrlOperation.java b/src/main/java/com/owncloud/android/operations/RichDocumentsUrlOperation.java index eb67a47d1d0a..9e6967f34f53 100644 --- a/src/main/java/com/owncloud/android/operations/RichDocumentsUrlOperation.java +++ b/src/main/java/com/owncloud/android/operations/RichDocumentsUrlOperation.java @@ -24,6 +24,7 @@ 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.utils.NextcloudServer; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.methods.PostMethod; @@ -33,6 +34,11 @@ * Edit a file with Richdocuments. Returns URL which can be shown in WebView. */ public class RichDocumentsUrlOperation extends RemoteOperation { + + /** + * TODO move to library + */ + private static final String TAG = RichDocumentsUrlOperation.class.getSimpleName(); private static final int SYNC_READ_TIMEOUT = 40000; private static final int SYNC_CONNECTION_TIMEOUT = 5000; @@ -51,6 +57,7 @@ public RichDocumentsUrlOperation(String fileID) { this.fileID = fileID; } + @NextcloudServer(max = 18) protected RemoteOperationResult run(OwnCloudClient client) { RemoteOperationResult result; PostMethod postMethod = null; @@ -80,7 +87,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { } 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()); + result.getException()); } finally { if (postMethod != null) { postMethod.releaseConnection(); diff --git a/src/main/java/com/owncloud/android/providers/FileContentProvider.java b/src/main/java/com/owncloud/android/providers/FileContentProvider.java index e66e5deb4428..7ce23d125327 100644 --- a/src/main/java/com/owncloud/android/providers/FileContentProvider.java +++ b/src/main/java/com/owncloud/android/providers/FileContentProvider.java @@ -789,7 +789,8 @@ private void createCapabilitiesTable(SQLiteDatabase db) { + ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_TEMPLATES + INTEGER + ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_OPTIONAL_MIMETYPE_LIST + TEXT + ProviderTableMeta.CAPABILITIES_SHARING_PUBLIC_ASK_FOR_OPTIONAL_PASSWORD + INTEGER - + ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_PRODUCT_NAME + " TEXT );"); + + ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_PRODUCT_NAME + TEXT + + ProviderTableMeta.CAPABILITIES_DIRECT_EDITING + " TEXT );"); } private void createUploadsTable(SQLiteDatabase db) { @@ -2064,6 +2065,24 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (!upgraded) { Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion)); } + + if (oldVersion < 52 && newVersion >= 52) { + Log_OC.i(SQL, "Entering in the #52 add directEditing to capability"); + db.beginTransaction(); + try { + db.execSQL(ALTER_TABLE + ProviderTableMeta.CAPABILITIES_TABLE_NAME + + ADD_COLUMN + ProviderTableMeta.CAPABILITIES_DIRECT_EDITING + " TEXT "); + + upgraded = true; + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + if (!upgraded) { + Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion)); + } } @Override diff --git a/src/main/java/com/owncloud/android/ui/activity/EditorWebView.java b/src/main/java/com/owncloud/android/ui/activity/EditorWebView.java new file mode 100644 index 000000000000..39fee229c8e9 --- /dev/null +++ b/src/main/java/com/owncloud/android/ui/activity/EditorWebView.java @@ -0,0 +1,228 @@ +/* + * + * Nextcloud Android client application + * + * @author Tobias Kaminsky + * Copyright (C) 2019 Tobias Kaminsky + * Copyright (C) 2019 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.owncloud.android.ui.activity; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.os.Handler; +import android.text.TextUtils; +import android.view.View; +import android.webkit.JavascriptInterface; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.android.material.snackbar.Snackbar; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.datamodel.ThumbnailsCacheManager; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.ui.asynctasks.LoadUrlTask; +import com.owncloud.android.utils.DisplayUtils; +import com.owncloud.android.utils.MimeTypeUtil; +import com.owncloud.android.utils.ThemeUtils; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import lombok.Getter; +import lombok.Setter; + +public abstract class EditorWebView extends ExternalSiteWebView { + @Getter @Setter protected Snackbar loadingSnackbar; + protected OCFile file; + + @BindView(R.id.progressBar2) + ProgressBar progressBar; + + @BindView(R.id.thumbnail) + ImageView thumbnail; + + @BindView(R.id.filename) + TextView fileName; + + private Unbinder unbinder; + + private static final String TAG = EditorWebView.class.getSimpleName(); + + protected void loadUrl(String url, OCFile file) { + if (TextUtils.isEmpty(url)) { + new LoadUrlTask(this, getAccount()).execute(file.getLocalId()); + } else { + webview.loadUrl(url); + } + } + + protected void hideLoading() { + thumbnail.setVisibility(View.GONE); + fileName.setVisibility(View.GONE); + progressBar.setVisibility(View.GONE); + webview.setVisibility(View.VISIBLE); + + if (loadingSnackbar != null) { + loadingSnackbar.dismiss(); + } + } + + public void onUrlLoaded(String loadedUrl) { + this.url = loadedUrl; + + if (!url.isEmpty()) { + getWebview().loadUrl(url); + + new Handler().postDelayed(() -> { + if (getWebview().getVisibility() != View.VISIBLE) { + Snackbar snackbar = DisplayUtils.createSnackbar(findViewById(android.R.id.content), + R.string.timeout_richDocuments, Snackbar.LENGTH_INDEFINITE) + .setAction(R.string.fallback_weblogin_back, v -> hideLoading()); + + ThemeUtils.colorSnackbar(getApplicationContext(), snackbar); + setLoadingSnackbar(snackbar); + snackbar.show(); + } + }, 10 * 1000); + } else { + Toast.makeText(getApplicationContext(), + R.string.richdocuments_failed_to_load_document, Toast.LENGTH_LONG).show(); + finish(); + } + } + + public void closeView() { + webview.destroy(); + finish(); + } + + @SuppressLint("AddJavascriptInterface") // suppress warning as webview is only used >= Lollipop + @Override + protected void onCreate(Bundle savedInstanceState) { + webViewLayout = R.layout.richdocuments_webview; // TODO rename + + showToolbar = false; + + super.onCreate(savedInstanceState); + + unbinder = ButterKnife.bind(this); + + file = getIntent().getParcelableExtra(ExternalSiteWebView.EXTRA_FILE); + + initLoadingScreen(); + } + + protected void initLoadingScreen() { + setThumbnail(file, thumbnail); + fileName.setText(file.getFileName()); + } + + 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 onDestroy() { + unbinder.unbind(); + webview.destroy(); + + super.onDestroy(); + } + + protected 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.bg_default)); + } + } else { + thumbnailView.setImageDrawable(MimeTypeUtil.getFileTypeIcon(file.getMimeType(), file.getFileName(), + getAccount(), this)); + } + } + } + + public class MobileInterface { + @JavascriptInterface + public void close() { + runOnUiThread(EditorWebView.this::closeView); + } + + @JavascriptInterface + public void share() { + openShareDialog(); + } + + @JavascriptInterface + public void loaded() { + runOnUiThread(EditorWebView.this::hideLoading); + } + } + +} diff --git a/src/main/java/com/owncloud/android/ui/activity/RichDocumentsWebView.java b/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.java similarity index 66% rename from src/main/java/com/owncloud/android/ui/activity/RichDocumentsWebView.java rename to src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.java index e72182bcd93b..670b316b6566 100644 --- a/src/main/java/com/owncloud/android/ui/activity/RichDocumentsWebView.java +++ b/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.java @@ -30,41 +30,31 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; -import android.graphics.Bitmap; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.text.TextUtils; import android.view.KeyEvent; -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.google.android.material.snackbar.Snackbar; import com.nextcloud.client.account.CurrentAccountProvider; import com.nextcloud.client.account.User; import com.nextcloud.client.network.ClientFactory; import com.owncloud.android.R; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.Template; -import com.owncloud.android.datamodel.ThumbnailsCacheManager; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.operations.RichDocumentsCreateAssetOperation; -import com.owncloud.android.ui.asynctasks.LoadUrlTask; import com.owncloud.android.ui.asynctasks.PrintAsyncTask; import com.owncloud.android.ui.fragment.OCFileListFragment; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.FileStorageUtils; -import com.owncloud.android.utils.MimeTypeUtil; import com.owncloud.android.utils.glide.CustomGlideStreamLoader; import org.json.JSONException; @@ -77,42 +67,26 @@ import javax.inject.Inject; import androidx.annotation.RequiresApi; -import butterknife.BindView; import butterknife.ButterKnife; import butterknife.Unbinder; -import lombok.Getter; -import lombok.Setter; /** * Opens document for editing via Richdocuments app in a web view */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) -public class RichDocumentsWebView extends ExternalSiteWebView { - +public class RichDocumentsEditorWebView extends EditorWebView { public static final int MINIMUM_API = Build.VERSION_CODES.LOLLIPOP; public static final int REQUEST_LOCAL_FILE = 101; private static final int REQUEST_REMOTE_FILE = 100; - private static final String TAG = RichDocumentsWebView.class.getSimpleName(); private static final String URL = "URL"; private static final String TYPE = "Type"; private static final String PRINT = "print"; private static final String NEW_NAME = "NewName"; private Unbinder unbinder; - private OCFile file; - @Getter @Setter private Snackbar loadingSnackbar; public ValueCallback uploadMessage; - @BindView(R.id.progressBar2) - ProgressBar progressBar; - - @BindView(R.id.thumbnail) - ImageView thumbnail; - - @BindView(R.id.filename) - TextView fileName; - @Inject protected CurrentAccountProvider currentAccountProvider; @@ -122,54 +96,16 @@ public class RichDocumentsWebView extends ExternalSiteWebView { @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(currentAccountProvider, clientFactory)) - .load(template.getThumbnailLink()) - .placeholder(placeholder) - .error(placeholder) - .into(thumbnail); - } else { - setThumbnail(file, thumbnail); - fileName.setText(file.getFileName()); - } + unbinder = ButterKnife.bind(this); + WebView.setWebContentsDebuggingEnabled(true); webview.addJavascriptInterface(new RichDocumentsMobileInterface(), "RichDocumentsMobileInterface"); webview.setWebChromeClient(new WebChromeClient() { - RichDocumentsWebView activity = RichDocumentsWebView.this; + RichDocumentsEditorWebView activity = RichDocumentsEditorWebView.this; @Override public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, @@ -197,70 +133,49 @@ public boolean onShowFileChooser(WebView webView, ValueCallback filePathC }); // load url in background - url = getIntent().getStringExtra(EXTRA_URL); - if (TextUtils.isEmpty(url)) { - new LoadUrlTask(this, getAccount()).execute(file.getLocalId()); - } else { - webview.loadUrl(url); - } + loadUrl(getIntent().getStringExtra(EXTRA_URL), file); } - private void setThumbnail(OCFile file, ImageView thumbnailView) { - // Todo minimize: only icon by mimetype + @Override + protected void initLoadingScreen() { + if (file == null) { + fileName.setText(R.string.create_file_from_template); - 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()); - } - } - } + Template template = Parcels.unwrap(getIntent().getParcelableExtra(EXTRA_TEMPLATE)); - if ("image/png".equalsIgnoreCase(file.getMimeType())) { - thumbnailView.setBackgroundColor(getResources().getColor(R.color.bg_default)); - } - } else { - thumbnailView.setImageDrawable(MimeTypeUtil.getFileTypeIcon(file.getMimeType(), file.getFileName(), - getAccount(), this)); + int placeholder; + + switch (template.getType()) { + case DOCUMENT: + placeholder = R.drawable.file_doc; + break; + + case SPREADSHEET: + placeholder = R.drawable.file_xls; + break; + + case PRESENTATION: + placeholder = R.drawable.file_ppt; + break; + + default: + placeholder = R.drawable.file; + break; } + + Glide.with(this).using(new CustomGlideStreamLoader(currentAccountProvider, clientFactory)) + .load(template.getThumbnailLink()) + .placeholder(placeholder) + .error(placeholder) + .into(thumbnail); + } else { + setThumbnail(file, thumbnail); + fileName.setText(file.getFileName()); } } + + @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); @@ -272,13 +187,6 @@ private void openFileChooser() { 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) { @@ -351,22 +259,6 @@ protected void onDestroy() { super.onDestroy(); } - public void closeView() { - webview.destroy(); - finish(); - } - - private void hideLoading() { - thumbnail.setVisibility(View.GONE); - fileName.setVisibility(View.GONE); - progressBar.setVisibility(View.GONE); - webview.setVisibility(View.VISIBLE); - - if (loadingSnackbar != null) { - loadingSnackbar.dismiss(); - } - } - @Override protected void onResume() { super.onResume(); @@ -403,25 +295,15 @@ private void downloadFile(Uri url) { downloadmanager.enqueue(request); } - private class RichDocumentsMobileInterface { - @JavascriptInterface - public void close() { - runOnUiThread(RichDocumentsWebView.this::closeView); - } - + private class RichDocumentsMobileInterface extends MobileInterface { @JavascriptInterface public void insertGraphic() { openFileChooser(); } - @JavascriptInterface - public void share() { - openShareDialog(); - } - @JavascriptInterface public void documentLoaded() { - runOnUiThread(RichDocumentsWebView.this::hideLoading); + runOnUiThread(RichDocumentsEditorWebView.this::hideLoading); } @JavascriptInterface @@ -440,8 +322,6 @@ public void downloadAs(String json) { Log_OC.e(this, "Failed to parse download json message: " + e); return; } - - } @JavascriptInterface @@ -466,6 +346,4 @@ public void paste() { } } } - - } diff --git a/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt b/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt new file mode 100644 index 000000000000..4a433947d4dd --- /dev/null +++ b/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt @@ -0,0 +1,37 @@ +/* + * Nextcloud Android client application + * + * @author Tobias Kaminsky + * Copyright (C) 2019 Tobias Kaminsky + * Copyright (C) 2019 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.owncloud.android.ui.activity + +import android.os.Build +import android.os.Bundle +import androidx.annotation.RequiresApi + +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +class TextEditorWebView : EditorWebView() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + webview.addJavascriptInterface(MobileInterface(), "DirectEditingMobileInterface") + + loadUrl(intent.getStringExtra(ExternalSiteWebView.EXTRA_URL), file) + } +} diff --git a/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java b/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java index f865d3437357..35a2fdf7d8bc 100644 --- a/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java +++ b/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java @@ -22,42 +22,42 @@ import android.accounts.Account; import android.os.AsyncTask; -import android.os.Build; -import android.os.Handler; -import android.view.View; -import android.widget.Toast; -import com.google.android.material.snackbar.Snackbar; -import com.owncloud.android.R; +import com.nextcloud.android.lib.resources.directediting.DirectEditingOpenFileRemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.operations.RichDocumentsUrlOperation; -import com.owncloud.android.ui.activity.RichDocumentsWebView; -import com.owncloud.android.utils.DisplayUtils; -import com.owncloud.android.utils.ThemeUtils; +import com.owncloud.android.ui.activity.EditorWebView; +import com.owncloud.android.ui.activity.RichDocumentsEditorWebView; +import com.owncloud.android.ui.activity.TextEditorWebView; import java.lang.ref.WeakReference; -import androidx.annotation.RequiresApi; - -@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public class LoadUrlTask extends AsyncTask { private Account account; - private WeakReference richDocumentsWebViewWeakReference; + private WeakReference editorWebViewWeakReference; + private static final String TEXT = "text"; - public LoadUrlTask(RichDocumentsWebView richDocumentsWebView, Account account) { + public LoadUrlTask(EditorWebView editorWebView, Account account) { this.account = account; - this.richDocumentsWebViewWeakReference = new WeakReference<>(richDocumentsWebView); + this.editorWebViewWeakReference = new WeakReference<>(editorWebView); } @Override protected String doInBackground(String... fileId) { - if (richDocumentsWebViewWeakReference.get() == null) { + if (editorWebViewWeakReference.get() == null) { + return ""; + } + + RemoteOperationResult result = null; + if (editorWebViewWeakReference.get() instanceof RichDocumentsEditorWebView) { + result = new RichDocumentsUrlOperation(fileId[0]).execute(account, editorWebViewWeakReference.get()); + } else if (editorWebViewWeakReference.get() instanceof TextEditorWebView) { + result = new DirectEditingOpenFileRemoteOperation(fileId[0], TEXT) + .execute(account, editorWebViewWeakReference.get()); + } else { return ""; } - RichDocumentsUrlOperation richDocumentsUrlOperation = new RichDocumentsUrlOperation(fileId[0]); - RemoteOperationResult result = richDocumentsUrlOperation.execute(account, - richDocumentsWebViewWeakReference.get()); if (!result.isSuccess()) { return ""; @@ -68,30 +68,12 @@ protected String doInBackground(String... fileId) { @Override protected void onPostExecute(String url) { - RichDocumentsWebView richDocumentsWebView = richDocumentsWebViewWeakReference.get(); + EditorWebView editorWebView = editorWebViewWeakReference.get(); - if (richDocumentsWebView == null) { + if (editorWebView == null) { return; } - if (!url.isEmpty()) { - richDocumentsWebView.getWebview().loadUrl(url); - - new Handler().postDelayed(() -> { - if (richDocumentsWebView.getWebview().getVisibility() != View.VISIBLE) { - Snackbar snackbar = DisplayUtils.createSnackbar(richDocumentsWebView.findViewById(android.R.id.content), - R.string.timeout_richDocuments, Snackbar.LENGTH_INDEFINITE) - .setAction(R.string.fallback_weblogin_back, v -> richDocumentsWebView.closeView()); - - ThemeUtils.colorSnackbar(richDocumentsWebView.getApplicationContext(),snackbar); - richDocumentsWebView.setLoadingSnackbar(snackbar); - snackbar.show(); - } - }, 10 * 1000); - } else { - Toast.makeText(richDocumentsWebView.getApplicationContext(), - R.string.richdocuments_failed_to_load_document, Toast.LENGTH_LONG).show(); - richDocumentsWebView.finish(); - } + editorWebView.onUrlLoaded(url); } } diff --git a/src/main/java/com/owncloud/android/ui/asynctasks/PrintAsyncTask.java b/src/main/java/com/owncloud/android/ui/asynctasks/PrintAsyncTask.java index 6fe587f1aa27..9710840a08c4 100644 --- a/src/main/java/com/owncloud/android/ui/asynctasks/PrintAsyncTask.java +++ b/src/main/java/com/owncloud/android/ui/asynctasks/PrintAsyncTask.java @@ -29,7 +29,7 @@ import com.owncloud.android.R; import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.ui.activity.RichDocumentsWebView; +import com.owncloud.android.ui.activity.RichDocumentsEditorWebView; import com.owncloud.android.ui.adapter.PrintAdapter; import com.owncloud.android.utils.DisplayUtils; @@ -55,9 +55,9 @@ public class PrintAsyncTask extends AsyncTask { private File file; private String url; - private WeakReference richDocumentsWebViewWeakReference; + private WeakReference richDocumentsWebViewWeakReference; - public PrintAsyncTask(File file, String url, WeakReference richDocumentsWebViewWeakReference) { + public PrintAsyncTask(File file, String url, WeakReference richDocumentsWebViewWeakReference) { this.file = file; this.url = url; this.richDocumentsWebViewWeakReference = richDocumentsWebViewWeakReference; @@ -129,7 +129,7 @@ protected Boolean doInBackground(Void... voids) { @Override protected void onPostExecute(Boolean result) { - RichDocumentsWebView richDocumentsWebView = richDocumentsWebViewWeakReference.get(); + RichDocumentsEditorWebView richDocumentsWebView = richDocumentsWebViewWeakReference.get(); richDocumentsWebView.dismissLoadingDialog(); PrintManager printManager = (PrintManager) richDocumentsWebView.getSystemService(PRINT_SERVICE); diff --git a/src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.java b/src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.java index 90bffa6dc35f..e06e190b12aa 100644 --- a/src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.java +++ b/src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.java @@ -52,7 +52,7 @@ 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.activity.RichDocumentsEditorWebView; import com.owncloud.android.ui.adapter.TemplateAdapter; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.ThemeUtils; @@ -241,7 +241,7 @@ protected void onPostExecute(String url) { if (url.isEmpty()) { DisplayUtils.showSnackMessage(fragment.listView, "Error creating file from template"); } else { - Intent collaboraWebViewIntent = new Intent(MainApp.getAppContext(), RichDocumentsWebView.class); + Intent collaboraWebViewIntent = new Intent(MainApp.getAppContext(), RichDocumentsEditorWebView.class); collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_TITLE, "Collabora"); collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_URL, url); collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_SHOW_SIDEBAR, false); diff --git a/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java b/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java index 65c97980aa0b..4df2bd158d39 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java +++ b/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java @@ -32,7 +32,7 @@ import com.owncloud.android.R; import com.owncloud.android.lib.resources.status.OCCapability; import com.owncloud.android.ui.activity.FileActivity; -import com.owncloud.android.ui.activity.RichDocumentsWebView; +import com.owncloud.android.ui.activity.RichDocumentsEditorWebView; import com.owncloud.android.utils.ThemeUtils; import butterknife.BindView; @@ -100,7 +100,7 @@ protected void onCreate(Bundle savedInstanceState) { OCCapability capability = fileActivity.getCapabilities(); if (capability.getRichDocuments().isTrue() && capability.getRichDocumentsDirectEditing().isTrue() && - android.os.Build.VERSION.SDK_INT >= RichDocumentsWebView.MINIMUM_API && + android.os.Build.VERSION.SDK_INT >= RichDocumentsEditorWebView.MINIMUM_API && capability.getRichDocumentsTemplatesAvailable().isTrue()) { templates.setVisibility(View.VISIBLE); } diff --git a/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 3d3b85aaa591..debd5981c90a 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -75,7 +75,7 @@ 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.RichDocumentsEditorWebView; import com.owncloud.android.ui.activity.ToolbarActivity; import com.owncloud.android.ui.activity.UploadFilesActivity; import com.owncloud.android.ui.adapter.OCFileListAdapter; @@ -483,7 +483,7 @@ mContainerActivity, getActivity(), popup.setOnMenuItemClickListener(item -> { Set checkedFiles = new HashSet<>(); checkedFiles.add(file); - return onFileActionChosen(item.getItemId(), checkedFiles); + return onFileActionChosen(item, checkedFiles); }); popup.show(); } @@ -635,7 +635,7 @@ public boolean onPrepareActionMode(ActionMode mode, Menu menu) { @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { Set checkedFiles = mAdapter.getCheckedItems(); - return onFileActionChosen(item.getItemId(), checkedFiles); + return onFileActionChosen(item, checkedFiles); } /** @@ -713,7 +713,7 @@ public void onSaveInstanceState(@NonNull Bundle outState) { } @Override - public void onPrepareOptionsMenu(Menu menu) { + public void onPrepareOptionsMenu(@NonNull Menu menu) { Menu mMenu = menu; if (mOriginalMenuItems.size() == 0) { @@ -945,6 +945,7 @@ public void onItemClicked(OCFile file) { mContainerActivity.getFileOperationsHelper().openFile(file); } } else { + // file not downloaded, check for streaming, remote editing User account = accountManager.getUser(); OCCapability capability = mContainerActivity.getStorageManager() .getCapability(account.getAccountName()); @@ -953,8 +954,10 @@ public void onItemClicked(OCFile file) { .isMediaStreamingSupported()) { // stream media preview on >= NC14 ((FileDisplayActivity) mContainerActivity).startMediaPreview(file, 0, true, true, true); + } else if (FileMenuFilter.isEditorAvailable(capability, file.getMimeType())) { + mContainerActivity.getFileOperationsHelper().openFileWithTextEditor(file, getContext()); } else if (capability.getRichDocumentsMimeTypeList().contains(file.getMimeType()) && - android.os.Build.VERSION.SDK_INT >= RichDocumentsWebView.MINIMUM_API && + android.os.Build.VERSION.SDK_INT >= RichDocumentsEditorWebView.MINIMUM_API && capability.getRichDocumentsDirectEditing().isTrue()) { mContainerActivity.getFileOperationsHelper().openFileAsRichDocument(file, getContext()); } else { @@ -993,11 +996,11 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { /** * Start the appropriate action(s) on the currently selected files given menu selected by the user. * - * @param menuId Identifier of the action menu selected by the user + * @param item MenuItem selected by the user * @param checkedFiles List of files selected by the user on which the action should be performed * @return 'true' if the menu selection started any action, 'false' otherwise. */ - public boolean onFileActionChosen(int menuId, Set checkedFiles) { + public boolean onFileActionChosen(MenuItem item, Set checkedFiles) { if (checkedFiles.isEmpty()) { return false; } @@ -1005,7 +1008,7 @@ public boolean onFileActionChosen(int menuId, Set checkedFiles) { if (checkedFiles.size() == SINGLE_SELECTION) { /// action only possible on a single file OCFile singleFile = checkedFiles.iterator().next(); - switch (menuId) { + switch (item.getItemId()) { case R.id.action_send_share_file: { mContainerActivity.getFileOperationsHelper().sendShareFile(singleFile); return true; @@ -1018,8 +1021,16 @@ public boolean onFileActionChosen(int menuId, Set checkedFiles) { mContainerActivity.getFileOperationsHelper().streamMediaFile(singleFile); return true; } - case R.id.action_open_file_as_richdocument: { - mContainerActivity.getFileOperationsHelper().openFileAsRichDocument(singleFile, getContext()); + case R.id.action_edit: { + Account account = ((FileActivity) mContainerActivity).getUserAccountManager() + .getUser().toPlatformAccount(); + OCCapability ocCapability = mContainerActivity.getStorageManager().getCapability(account.name); + + if (FileMenuFilter.isEditorAvailable(ocCapability, singleFile.getMimeType())) { + mContainerActivity.getFileOperationsHelper().openFileWithTextEditor(singleFile, getContext()); + } else { + mContainerActivity.getFileOperationsHelper().openFileAsRichDocument(singleFile, getContext()); + } return true; } case R.id.action_rename_file: { @@ -1050,7 +1061,7 @@ public boolean onFileActionChosen(int menuId, Set checkedFiles) { } /// actions possible on a batch of files - switch (menuId) { + switch (item.getItemId()) { case R.id.action_remove_file: { RemoveFilesDialogFragment dialog = RemoveFilesDialogFragment.newInstance(new ArrayList<>(checkedFiles), mActiveActionMode); dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION); diff --git a/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java b/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java index 387125c67c72..8b462a2b9f0a 100755 --- a/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java +++ b/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java @@ -67,8 +67,9 @@ 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.RichDocumentsEditorWebView; import com.owncloud.android.ui.activity.ShareActivity; +import com.owncloud.android.ui.activity.TextEditorWebView; import com.owncloud.android.ui.dialog.SendShareDialog; import com.owncloud.android.ui.events.EncryptionEvent; import com.owncloud.android.ui.events.FavoriteEvent; @@ -278,7 +279,7 @@ public void openFile(OCFile file) { Account account = fileActivity.getAccount(); OCCapability capability = fileActivity.getStorageManager().getCapability(account.name); if (capability.getRichDocumentsMimeTypeList().contains(file.getMimeType()) && - android.os.Build.VERSION.SDK_INT >= RichDocumentsWebView.MINIMUM_API && + android.os.Build.VERSION.SDK_INT >= RichDocumentsEditorWebView.MINIMUM_API && capability.getRichDocumentsDirectEditing().isTrue()) { openFileAsRichDocument(file, fileActivity); return; @@ -342,13 +343,21 @@ public void run() { } public void openFileAsRichDocument(OCFile file, Context context) { - Intent collaboraWebViewIntent = new Intent(context, RichDocumentsWebView.class); + Intent collaboraWebViewIntent = new Intent(context, RichDocumentsEditorWebView.class); collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_TITLE, "Collabora"); collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_FILE, file); collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_SHOW_SIDEBAR, false); context.startActivity(collaboraWebViewIntent); } + public void openFileWithTextEditor(OCFile file, Context context) { + Intent textEditorIntent = new Intent(context, TextEditorWebView.class); + textEditorIntent.putExtra(ExternalSiteWebView.EXTRA_TITLE, "Text"); + textEditorIntent.putExtra(ExternalSiteWebView.EXTRA_FILE, file); + textEditorIntent.putExtra(ExternalSiteWebView.EXTRA_SHOW_SIDEBAR, false); + context.startActivity(textEditorIntent); + } + @NonNull private Intent createOpenFileIntent(OCFile file) { String storagePath = file.getStoragePath(); diff --git a/src/main/res/layout/richdocuments_webview.xml b/src/main/res/layout/richdocuments_webview.xml index ee4b356dde7e..4bb72dc610d4 100644 --- a/src/main/res/layout/richdocuments_webview.xml +++ b/src/main/res/layout/richdocuments_webview.xml @@ -64,8 +64,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone"/> - - + + - - folder file Share internal link + Edit From b31cee531b1ae56efb6d6ff4fed3e9bddab749b1 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Mon, 2 Dec 2019 13:24:38 +0100 Subject: [PATCH 02/14] prevent NPE Signed-off-by: tobiasKaminsky --- .../owncloud/android/ui/asynctasks/LoadUrlTask.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java b/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java index 35a2fdf7d8bc..eab1dce04bf8 100644 --- a/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java +++ b/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java @@ -45,14 +45,18 @@ public LoadUrlTask(EditorWebView editorWebView, Account account) { @Override protected String doInBackground(String... fileId) { - if (editorWebViewWeakReference.get() == null) { + final EditorWebView editorWebView = editorWebViewWeakReference.get(); + + if (editorWebView == null) { return ""; } - RemoteOperationResult result = null; - if (editorWebViewWeakReference.get() instanceof RichDocumentsEditorWebView) { + RemoteOperationResult result; + + + if (editorWebView instanceof RichDocumentsEditorWebView) { result = new RichDocumentsUrlOperation(fileId[0]).execute(account, editorWebViewWeakReference.get()); - } else if (editorWebViewWeakReference.get() instanceof TextEditorWebView) { + } else if (editorWebView instanceof TextEditorWebView) { result = new DirectEditingOpenFileRemoteOperation(fileId[0], TEXT) .execute(account, editorWebViewWeakReference.get()); } else { From 994817ce9792828e567e43d1e30f8fcd30eb9f47 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Mon, 2 Dec 2019 16:34:01 +0100 Subject: [PATCH 03/14] get direct editing info from endpoint Signed-off-by: tobiasKaminsky --- .../datamodel/ArbitraryDataProvider.java | 3 ++ .../datamodel/FileDataStorageManager.java | 6 ++-- .../com/owncloud/android/db/ProviderMeta.java | 2 +- .../android/files/FileMenuFilter.java | 17 ++++++++-- .../operations/RefreshFolderOperation.java | 32 ++++++++++++++++++- .../providers/DocumentsStorageProvider.java | 15 +++++++-- .../providers/FileContentProvider.java | 6 ++-- .../ui/fragment/OCFileListFragment.java | 14 ++++---- 8 files changed, 72 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/owncloud/android/datamodel/ArbitraryDataProvider.java b/src/main/java/com/owncloud/android/datamodel/ArbitraryDataProvider.java index df40d3f6117f..0ab94bd0015e 100644 --- a/src/main/java/com/owncloud/android/datamodel/ArbitraryDataProvider.java +++ b/src/main/java/com/owncloud/android/datamodel/ArbitraryDataProvider.java @@ -37,6 +37,9 @@ * Database provider for handling the persistence aspects of arbitrary data table. */ public class ArbitraryDataProvider { + public static final String DIRECT_EDITING = "DIRECT_EDITING"; + public static final String DIRECT_EDITING_ETAG = "DIRECT_EDITING_ETAG"; + private static final String TAG = ArbitraryDataProvider.class.getSimpleName(); private static final String TRUE = "true"; diff --git a/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java index 5bd0e448436d..602902eb1101 100644 --- a/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -39,7 +39,6 @@ import com.google.gson.JsonSyntaxException; import com.owncloud.android.MainApp; import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; -import com.owncloud.android.lib.common.DirectEditing; import com.owncloud.android.lib.common.network.WebdavEntry; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; @@ -2031,7 +2030,7 @@ private ContentValues createContentValues(String accountName, OCCapability capab cv.put(ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_TEMPLATES, capability.getRichDocumentsTemplatesAvailable() .getValue()); cv.put(ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_PRODUCT_NAME, capability.getRichDocumentsProductName()); - cv.put(ProviderTableMeta.CAPABILITIES_DIRECT_EDITING, new Gson().toJson(capability.getDirectEditing())); + cv.put(ProviderTableMeta.CAPABILITIES_DIRECT_EDITING_ETAG, capability.getDirectEditingEtag()); return cv; } @@ -2151,8 +2150,7 @@ private OCCapability createCapabilityInstance(Cursor c) { } capability.setRichDocumentsOptionalMimeTypeList(Arrays.asList(optionalMimetypes.split(","))); capability.setRichDocumentsProductName(getString(c, ProviderTableMeta.CAPABILITIES_RICHDOCUMENT_PRODUCT_NAME)); - capability.setDirectEditing(new Gson().fromJson(getString(c, ProviderTableMeta.CAPABILITIES_DIRECT_EDITING), - DirectEditing.class)); + capability.setDirectEditingEtag(getString(c, ProviderTableMeta.CAPABILITIES_DIRECT_EDITING_ETAG)); } return capability; } diff --git a/src/main/java/com/owncloud/android/db/ProviderMeta.java b/src/main/java/com/owncloud/android/db/ProviderMeta.java index ad42bec538d3..76674112d27c 100644 --- a/src/main/java/com/owncloud/android/db/ProviderMeta.java +++ b/src/main/java/com/owncloud/android/db/ProviderMeta.java @@ -194,7 +194,7 @@ static public class ProviderTableMeta implements BaseColumns { public static final String CAPABILITIES_RICHDOCUMENT_PRODUCT_NAME = "richdocument_product_name"; public static final String CAPABILITIES_DEFAULT_SORT_ORDER = CAPABILITIES_ACCOUNT_NAME + " collate nocase asc"; - public static final String CAPABILITIES_DIRECT_EDITING = "direct_editing"; + public static final String CAPABILITIES_DIRECT_EDITING_ETAG = "direct_editing_etag"; //Columns of Uploads table public static final String UPLOADS_LOCAL_PATH = "local_path"; diff --git a/src/main/java/com/owncloud/android/files/FileMenuFilter.java b/src/main/java/com/owncloud/android/files/FileMenuFilter.java index 6cc35259f82f..eb62a53270f9 100644 --- a/src/main/java/com/owncloud/android/files/FileMenuFilter.java +++ b/src/main/java/com/owncloud/android/files/FileMenuFilter.java @@ -22,11 +22,14 @@ package com.owncloud.android.files; import android.accounts.Account; +import android.content.ContentResolver; import android.content.Context; import android.view.Menu; import android.view.MenuItem; +import com.google.gson.Gson; import com.owncloud.android.R; +import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; @@ -261,15 +264,23 @@ private void filterEdit(List toShow, ) { String mimeType = mFiles.iterator().next().getMimeType(); - if (isRichDocumentEditingSupported(capability, mimeType) || isEditorAvailable(capability, mimeType)) { + if (isRichDocumentEditingSupported(capability, mimeType) || isEditorAvailable(mContext.getContentResolver(), + mAccount, + mimeType)) { toShow.add(R.id.action_edit); } else { toHide.add(R.id.action_edit); } } - public static boolean isEditorAvailable(OCCapability capability, String mimeType) { - DirectEditing directEditing = capability.getDirectEditing(); + public static boolean isEditorAvailable(ContentResolver contentResolver, Account account, String mimeType) { + String json = new ArbitraryDataProvider(contentResolver).getValue(account, ArbitraryDataProvider.DIRECT_EDITING); + + if (json.isEmpty()) { + return false; + } + + DirectEditing directEditing = new Gson().fromJson(json, DirectEditing.class); for (Editor editor : directEditing.editors.values()) { if (editor.mimetypes.contains(mimeType) || editor.optionalMimetypes.contains(mimeType)) { diff --git a/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java b/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java index a6d4afea197f..4e3d4387a05e 100644 --- a/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -24,9 +24,13 @@ import android.content.Intent; import android.util.Log; +import com.google.gson.Gson; +import com.nextcloud.android.lib.resources.directediting.DirectEditingObtainRemoteOperation; +import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.DecryptedFolderMetadata; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.lib.common.DirectEditing; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; @@ -275,13 +279,39 @@ private void updateUserProfile() { } private void updateCapabilities() { + ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(mContext.getContentResolver()); + String oldDirectEditingEtag = arbitraryDataProvider.getValue(mAccount, + ArbitraryDataProvider.DIRECT_EDITING_ETAG); + GetCapabilitiesOperation getCapabilities = new GetCapabilitiesOperation(); RemoteOperationResult result = getCapabilities.execute(mStorageManager, mContext); - if (!result.isSuccess()) { + if (result.isSuccess()) { + String newDirectEditingEtag = mStorageManager.getCapability(mAccount.name).getDirectEditingEtag(); + + if (!oldDirectEditingEtag.equalsIgnoreCase(newDirectEditingEtag)) { + updateDirectEditing(arbitraryDataProvider, newDirectEditingEtag); + } + } else { Log_OC.w(TAG, "Update Capabilities unsuccessfully"); } } + private void updateDirectEditing(ArbitraryDataProvider arbitraryDataProvider, String newDirectEditingEtag) { + RemoteOperationResult result = new DirectEditingObtainRemoteOperation().execute(mAccount, mContext); + + if (result.isSuccess()) { + DirectEditing directEditing = (DirectEditing) result.getSingleData(); + String json = new Gson().toJson(directEditing); + arbitraryDataProvider.storeOrUpdateKeyValue(mAccount.name, ArbitraryDataProvider.DIRECT_EDITING, json); + } else { + arbitraryDataProvider.deleteKeyForAccount(mAccount.name, ArbitraryDataProvider.DIRECT_EDITING); + } + + arbitraryDataProvider.storeOrUpdateKeyValue(mAccount.name, + ArbitraryDataProvider.DIRECT_EDITING_ETAG, + newDirectEditingEtag); + } + private RemoteOperationResult checkForChanges(OwnCloudClient client) { mRemoteFolderChanged = true; RemoteOperationResult result; diff --git a/src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java b/src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java index b7f426f6f16a..2cf074c0f062 100644 --- a/src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java +++ b/src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java @@ -53,6 +53,7 @@ import com.nextcloud.client.preferences.AppPreferencesImpl; import com.owncloud.android.MainApp; import com.owncloud.android.R; +import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.ThumbnailsCacheManager; @@ -163,7 +164,11 @@ public Cursor queryChildDocuments(String parentDocumentId, String[] projection, boolean isLoading = false; if (parentFolder.isExpired()) { - final ReloadFolderDocumentTask task = new ReloadFolderDocumentTask(parentFolder, result -> { + ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(getContext().getContentResolver()); + + final ReloadFolderDocumentTask task = new ReloadFolderDocumentTask(arbitraryDataProvider, + parentFolder, + result -> { getContext().getContentResolver().notifyChange(toNotifyUri(parentFolder), null, false); }); task.executeOnExecutor(executor); @@ -309,7 +314,7 @@ public AssetFileDescriptor openDocumentThumbnail(String documentId, } Document document = toDocument(documentId); - + boolean exists = ThumbnailsCacheManager.containsBitmap(ThumbnailsCacheManager.PREFIX_THUMBNAIL + document.getFile().getRemoteId()); @@ -692,10 +697,14 @@ static class ReloadFolderDocumentTask extends AsyncTask= 52) { - Log_OC.i(SQL, "Entering in the #52 add directEditing to capability"); + Log_OC.i(SQL, "Entering in the #52 add etag for directEditing to capability"); db.beginTransaction(); try { db.execSQL(ALTER_TABLE + ProviderTableMeta.CAPABILITIES_TABLE_NAME + - ADD_COLUMN + ProviderTableMeta.CAPABILITIES_DIRECT_EDITING + " TEXT "); + ADD_COLUMN + ProviderTableMeta.CAPABILITIES_DIRECT_EDITING_ETAG + " TEXT "); upgraded = true; db.setTransactionSuccessful(); diff --git a/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index debd5981c90a..d085c176b9e2 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -25,8 +25,6 @@ package com.owncloud.android.ui.fragment; import android.accounts.Account; -import android.accounts.AuthenticatorException; -import android.accounts.OperationCanceledException; import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -60,9 +58,7 @@ import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.VirtualFolderType; import com.owncloud.android.files.FileMenuFilter; -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.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; @@ -108,7 +104,6 @@ import org.parceler.Parcels; import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -954,7 +949,9 @@ public void onItemClicked(OCFile file) { .isMediaStreamingSupported()) { // stream media preview on >= NC14 ((FileDisplayActivity) mContainerActivity).startMediaPreview(file, 0, true, true, true); - } else if (FileMenuFilter.isEditorAvailable(capability, file.getMimeType())) { + } else if (FileMenuFilter.isEditorAvailable(requireContext().getContentResolver(), + account.toPlatformAccount(), + file.getMimeType())) { mContainerActivity.getFileOperationsHelper().openFileWithTextEditor(file, getContext()); } else if (capability.getRichDocumentsMimeTypeList().contains(file.getMimeType()) && android.os.Build.VERSION.SDK_INT >= RichDocumentsEditorWebView.MINIMUM_API && @@ -1024,9 +1021,10 @@ public boolean onFileActionChosen(MenuItem item, Set checkedFiles) { case R.id.action_edit: { Account account = ((FileActivity) mContainerActivity).getUserAccountManager() .getUser().toPlatformAccount(); - OCCapability ocCapability = mContainerActivity.getStorageManager().getCapability(account.name); - if (FileMenuFilter.isEditorAvailable(ocCapability, singleFile.getMimeType())) { + if (FileMenuFilter.isEditorAvailable(requireContext().getContentResolver(), + account, + singleFile.getMimeType())) { mContainerActivity.getFileOperationsHelper().openFileWithTextEditor(singleFile, getContext()); } else { mContainerActivity.getFileOperationsHelper().openFileAsRichDocument(singleFile, getContext()); From 300abba48ba6f3447b426b12d44d6550a0e4d1c1 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Tue, 3 Dec 2019 14:04:13 +0100 Subject: [PATCH 04/14] make sure TextEditor is also only used ond >= Android 5, due to security reason of javascript interface Signed-off-by: tobiasKaminsky --- .../android/files/FileMenuFilter.java | 9 +++++-- .../activity/RichDocumentsEditorWebView.java | 2 -- .../android/ui/activity/TextEditorWebView.kt | 4 +++ .../fragment/OCFileListBottomSheetDialog.java | 4 +-- .../ui/fragment/OCFileListFragment.java | 27 ++++++++++++------- .../ui/helpers/FileOperationsHelper.java | 5 +++- src/main/res/values/strings.xml | 1 - 7 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/owncloud/android/files/FileMenuFilter.java b/src/main/java/com/owncloud/android/files/FileMenuFilter.java index eb62a53270f9..894604957ca2 100644 --- a/src/main/java/com/owncloud/android/files/FileMenuFilter.java +++ b/src/main/java/com/owncloud/android/files/FileMenuFilter.java @@ -24,6 +24,7 @@ import android.accounts.Account; import android.content.ContentResolver; import android.content.Context; +import android.os.Build; import android.view.Menu; import android.view.MenuItem; @@ -38,7 +39,6 @@ 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.RichDocumentsEditorWebView; import com.owncloud.android.utils.MimeTypeUtil; import com.owncloud.android.utils.NextcloudServer; @@ -262,6 +262,11 @@ private void filterEdit(List toShow, List toHide, OCCapability capability ) { + if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + toHide.add(R.id.action_edit); + return; + } + String mimeType = mFiles.iterator().next().getMimeType(); if (isRichDocumentEditingSupported(capability, mimeType) || isEditorAvailable(mContext.getContentResolver(), @@ -296,7 +301,7 @@ public static boolean isEditorAvailable(ContentResolver contentResolver, Account */ @NextcloudServer(max = 18) private boolean isRichDocumentEditingSupported(OCCapability capability, String mimeType) { - return isSingleFile() && android.os.Build.VERSION.SDK_INT >= RichDocumentsEditorWebView.MINIMUM_API && + return isSingleFile() && android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && (capability.getRichDocumentsMimeTypeList().contains(mimeType) || capability.getRichDocumentsOptionalMimeTypeList().contains(mimeType)) && capability.getRichDocumentsDirectEditing().isTrue(); diff --git a/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.java b/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.java index 670b316b6566..76dac4d8f0e0 100644 --- a/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.java +++ b/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.java @@ -24,7 +24,6 @@ package com.owncloud.android.ui.activity; -import android.accounts.Account; import android.annotation.SuppressLint; import android.app.DownloadManager; import android.content.ActivityNotFoundException; @@ -75,7 +74,6 @@ */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public class RichDocumentsEditorWebView extends EditorWebView { - public static final int MINIMUM_API = Build.VERSION_CODES.LOLLIPOP; public static final int REQUEST_LOCAL_FILE = 101; private static final int REQUEST_REMOTE_FILE = 100; private static final String URL = "URL"; diff --git a/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt b/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt index 4a433947d4dd..2bdbcb051fe0 100644 --- a/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt +++ b/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt @@ -21,12 +21,16 @@ package com.owncloud.android.ui.activity +import android.annotation.SuppressLint import android.os.Build import android.os.Bundle import androidx.annotation.RequiresApi @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) class TextEditorWebView : EditorWebView() { + + @SuppressLint("AddJavascriptInterface") // suppress warning as webview is only used >= Lollipop + // suppress warning as webview is only used >= Lollipop override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java b/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java index 4df2bd158d39..b47d607d6dd9 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java +++ b/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java @@ -20,6 +20,7 @@ package com.owncloud.android.ui.fragment; +import android.os.Build; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; @@ -32,7 +33,6 @@ import com.owncloud.android.R; import com.owncloud.android.lib.resources.status.OCCapability; import com.owncloud.android.ui.activity.FileActivity; -import com.owncloud.android.ui.activity.RichDocumentsEditorWebView; import com.owncloud.android.utils.ThemeUtils; import butterknife.BindView; @@ -100,7 +100,7 @@ protected void onCreate(Bundle savedInstanceState) { OCCapability capability = fileActivity.getCapabilities(); if (capability.getRichDocuments().isTrue() && capability.getRichDocumentsDirectEditing().isTrue() && - android.os.Build.VERSION.SDK_INT >= RichDocumentsEditorWebView.MINIMUM_API && + android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && capability.getRichDocumentsTemplatesAvailable().isTrue()) { templates.setVisibility(View.VISIBLE); } diff --git a/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index d085c176b9e2..5de8a66ab672 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -71,7 +71,6 @@ 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.RichDocumentsEditorWebView; import com.owncloud.android.ui.activity.ToolbarActivity; import com.owncloud.android.ui.activity.UploadFilesActivity; import com.owncloud.android.ui.adapter.OCFileListAdapter; @@ -951,10 +950,11 @@ public void onItemClicked(OCFile file) { ((FileDisplayActivity) mContainerActivity).startMediaPreview(file, 0, true, true, true); } else if (FileMenuFilter.isEditorAvailable(requireContext().getContentResolver(), account.toPlatformAccount(), - file.getMimeType())) { + file.getMimeType()) && + android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mContainerActivity.getFileOperationsHelper().openFileWithTextEditor(file, getContext()); } else if (capability.getRichDocumentsMimeTypeList().contains(file.getMimeType()) && - android.os.Build.VERSION.SDK_INT >= RichDocumentsEditorWebView.MINIMUM_API && + android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && capability.getRichDocumentsDirectEditing().isTrue()) { mContainerActivity.getFileOperationsHelper().openFileAsRichDocument(file, getContext()); } else { @@ -1022,14 +1022,23 @@ public boolean onFileActionChosen(MenuItem item, Set checkedFiles) { Account account = ((FileActivity) mContainerActivity).getUserAccountManager() .getUser().toPlatformAccount(); - if (FileMenuFilter.isEditorAvailable(requireContext().getContentResolver(), - account, - singleFile.getMimeType())) { - mContainerActivity.getFileOperationsHelper().openFileWithTextEditor(singleFile, getContext()); + // should not be necessary, as menu item is filtered, but better play safe + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (FileMenuFilter.isEditorAvailable(requireContext().getContentResolver(), + account, + singleFile.getMimeType())) { + mContainerActivity.getFileOperationsHelper().openFileWithTextEditor(singleFile, + getContext()); + } else { + mContainerActivity.getFileOperationsHelper().openFileAsRichDocument(singleFile, + getContext()); + } + + return true; } else { - mContainerActivity.getFileOperationsHelper().openFileAsRichDocument(singleFile, getContext()); + DisplayUtils.showSnackMessage(getView(), "Not supported on older than Android 5"); + return false; } - return true; } case R.id.action_rename_file: { RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(singleFile); diff --git a/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java b/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java index 8b462a2b9f0a..3c00ae3074fa 100755 --- a/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java +++ b/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java @@ -99,6 +99,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.core.content.FileProvider; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; @@ -279,7 +280,7 @@ public void openFile(OCFile file) { Account account = fileActivity.getAccount(); OCCapability capability = fileActivity.getStorageManager().getCapability(account.name); if (capability.getRichDocumentsMimeTypeList().contains(file.getMimeType()) && - android.os.Build.VERSION.SDK_INT >= RichDocumentsEditorWebView.MINIMUM_API && + android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && capability.getRichDocumentsDirectEditing().isTrue()) { openFileAsRichDocument(file, fileActivity); return; @@ -342,6 +343,7 @@ public void run() { } } + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public void openFileAsRichDocument(OCFile file, Context context) { Intent collaboraWebViewIntent = new Intent(context, RichDocumentsEditorWebView.class); collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_TITLE, "Collabora"); @@ -350,6 +352,7 @@ public void openFileAsRichDocument(OCFile file, Context context) { context.startActivity(collaboraWebViewIntent); } + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public void openFileWithTextEditor(OCFile file, Context context) { Intent textEditorIntent = new Intent(context, TextEditorWebView.class); textEditorIntent.putExtra(ExternalSiteWebView.EXTRA_TITLE, "Text"); diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 98deebe8e5bf..ce9b735178a2 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -810,7 +810,6 @@ Internal streaming not possible Please download media instead or use external app. Folder already exists - Open with %1$s Notification icon Create Delete From d05de9855a98697128237c8e82be5d4fca3c2a04 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Thu, 5 Dec 2019 07:18:53 +0100 Subject: [PATCH 05/14] use file path as parameter for open direct editing file Signed-off-by: tobiasKaminsky --- .../android/ui/activity/EditorWebView.java | 2 +- .../android/ui/asynctasks/LoadUrlTask.java | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/owncloud/android/ui/activity/EditorWebView.java b/src/main/java/com/owncloud/android/ui/activity/EditorWebView.java index 39fee229c8e9..dbbaf9c5a369 100644 --- a/src/main/java/com/owncloud/android/ui/activity/EditorWebView.java +++ b/src/main/java/com/owncloud/android/ui/activity/EditorWebView.java @@ -70,7 +70,7 @@ public abstract class EditorWebView extends ExternalSiteWebView { protected void loadUrl(String url, OCFile file) { if (TextUtils.isEmpty(url)) { - new LoadUrlTask(this, getAccount()).execute(file.getLocalId()); + new LoadUrlTask(this, getAccount(), file).execute(); } else { webview.loadUrl(url); } diff --git a/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java b/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java index eab1dce04bf8..fe983648efeb 100644 --- a/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java +++ b/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java @@ -24,6 +24,7 @@ import android.os.AsyncTask; import com.nextcloud.android.lib.resources.directediting.DirectEditingOpenFileRemoteOperation; +import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.operations.RichDocumentsUrlOperation; import com.owncloud.android.ui.activity.EditorWebView; @@ -32,19 +33,22 @@ import java.lang.ref.WeakReference; -public class LoadUrlTask extends AsyncTask { +public class LoadUrlTask extends AsyncTask { + + private static final String TEXT = "text"; private Account account; private WeakReference editorWebViewWeakReference; - private static final String TEXT = "text"; + private OCFile file; - public LoadUrlTask(EditorWebView editorWebView, Account account) { + public LoadUrlTask(EditorWebView editorWebView, Account account, OCFile file) { this.account = account; this.editorWebViewWeakReference = new WeakReference<>(editorWebView); + this.file = file; } @Override - protected String doInBackground(String... fileId) { + protected String doInBackground(Void... voids) { final EditorWebView editorWebView = editorWebViewWeakReference.get(); if (editorWebView == null) { @@ -55,9 +59,10 @@ protected String doInBackground(String... fileId) { if (editorWebView instanceof RichDocumentsEditorWebView) { - result = new RichDocumentsUrlOperation(fileId[0]).execute(account, editorWebViewWeakReference.get()); + result = new RichDocumentsUrlOperation(file.getLocalId()).execute(account, + editorWebViewWeakReference.get()); } else if (editorWebView instanceof TextEditorWebView) { - result = new DirectEditingOpenFileRemoteOperation(fileId[0], TEXT) + result = new DirectEditingOpenFileRemoteOperation(file.getRemotePath(), TEXT) .execute(account, editorWebViewWeakReference.get()); } else { return ""; From 95afe8c82a44d9767eeccfe333a350a7130a65ca Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Thu, 5 Dec 2019 09:58:58 +0100 Subject: [PATCH 06/14] use editor id to use any editor Signed-off-by: tobiasKaminsky --- .../com/owncloud/android/files/FileMenuFilter.java | 13 ++++++++++--- .../owncloud/android/ui/asynctasks/LoadUrlTask.java | 13 ++++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/owncloud/android/files/FileMenuFilter.java b/src/main/java/com/owncloud/android/files/FileMenuFilter.java index 894604957ca2..d14788df03fb 100644 --- a/src/main/java/com/owncloud/android/files/FileMenuFilter.java +++ b/src/main/java/com/owncloud/android/files/FileMenuFilter.java @@ -48,6 +48,8 @@ import java.util.Iterator; import java.util.List; +import androidx.annotation.Nullable; + /** * Filters out the file actions available in a given {@link Menu} for a given {@link OCFile} * according to the current state of the latest. @@ -279,21 +281,26 @@ private void filterEdit(List toShow, } public static boolean isEditorAvailable(ContentResolver contentResolver, Account account, String mimeType) { + return getEditor(contentResolver, account, mimeType) != null; + } + + @Nullable + public static Editor getEditor(ContentResolver contentResolver, Account account, String mimeType) { String json = new ArbitraryDataProvider(contentResolver).getValue(account, ArbitraryDataProvider.DIRECT_EDITING); if (json.isEmpty()) { - return false; + return null; } DirectEditing directEditing = new Gson().fromJson(json, DirectEditing.class); for (Editor editor : directEditing.editors.values()) { if (editor.mimetypes.contains(mimeType) || editor.optionalMimetypes.contains(mimeType)) { - return true; + return editor; } } - return false; + return null; } /** diff --git a/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java b/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java index fe983648efeb..6d0122fcdd81 100644 --- a/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java +++ b/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java @@ -25,6 +25,8 @@ import com.nextcloud.android.lib.resources.directediting.DirectEditingOpenFileRemoteOperation; import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.files.FileMenuFilter; +import com.owncloud.android.lib.common.Editor; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.operations.RichDocumentsUrlOperation; import com.owncloud.android.ui.activity.EditorWebView; @@ -59,10 +61,15 @@ protected String doInBackground(Void... voids) { if (editorWebView instanceof RichDocumentsEditorWebView) { - result = new RichDocumentsUrlOperation(file.getLocalId()).execute(account, - editorWebViewWeakReference.get()); + result = new RichDocumentsUrlOperation(file.getLocalId()).execute(account, editorWebView); } else if (editorWebView instanceof TextEditorWebView) { - result = new DirectEditingOpenFileRemoteOperation(file.getRemotePath(), TEXT) + Editor editor = FileMenuFilter.getEditor(editorWebView.getContentResolver(), account, file.getMimeType()); + + if (editor == null) { + return ""; + } + + result = new DirectEditingOpenFileRemoteOperation(file.getRemotePath(), editor.id) .execute(account, editorWebViewWeakReference.get()); } else { return ""; From f25127d6e30161ddf5a83664588ad6a387da534c Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Tue, 17 Dec 2019 12:38:48 +0100 Subject: [PATCH 07/14] revert to master snapshot Signed-off-by: tobiasKaminsky --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c431eab4da81..f51fd32b4cef 100644 --- a/build.gradle +++ b/build.gradle @@ -63,7 +63,7 @@ ext { daggerVersion = "2.25.3" markwonVersion = "4.2.0" prismVersion = "2.0.0" - androidLibraryVersion = "directEditing-SNAPSHOT" + androidLibraryVersion = "master-SNAPSHOT" travisBuild = System.getenv("TRAVIS") == "true" From 0178499596411a302920f252b966d19883935fd6 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Tue, 17 Dec 2019 14:21:23 +0100 Subject: [PATCH 08/14] remove unneeded static string Signed-off-by: tobiasKaminsky --- .../java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java b/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java index 6d0122fcdd81..d98f122ed159 100644 --- a/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java +++ b/src/main/java/com/owncloud/android/ui/asynctasks/LoadUrlTask.java @@ -37,8 +37,6 @@ public class LoadUrlTask extends AsyncTask { - private static final String TEXT = "text"; - private Account account; private WeakReference editorWebViewWeakReference; private OCFile file; From ddffaf7b496c44a3eeb1252a44ccd8ee845a746d Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 18 Dec 2019 08:23:23 +0100 Subject: [PATCH 09/14] use custom user agent for onlyOffice Signed-off-by: tobiasKaminsky --- .../android/ui/activity/TextEditorWebView.kt | 26 +++++++++++++++++++ src/main/res/values/setup.xml | 1 + 2 files changed, 27 insertions(+) diff --git a/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt b/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt index 2bdbcb051fe0..6bcac9dab52f 100644 --- a/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt +++ b/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt @@ -22,9 +22,13 @@ package com.owncloud.android.ui.activity import android.annotation.SuppressLint +import android.content.pm.PackageManager.NameNotFoundException import android.os.Build import android.os.Bundle import androidx.annotation.RequiresApi +import com.owncloud.android.R +import com.owncloud.android.files.FileMenuFilter +import com.owncloud.android.lib.common.utils.Log_OC @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) class TextEditorWebView : EditorWebView() { @@ -34,8 +38,30 @@ class TextEditorWebView : EditorWebView() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val editor = FileMenuFilter.getEditor(contentResolver, account, file.mimeType) + + if (editor != null && editor.id == "onlyoffice") { + webview.settings.userAgentString = generateOnlyOfficeUserAgent() + } + webview.addJavascriptInterface(MobileInterface(), "DirectEditingMobileInterface") loadUrl(intent.getStringExtra(ExternalSiteWebView.EXTRA_URL), file) } + + private fun generateOnlyOfficeUserAgent(): String { + val appString = applicationContext.resources.getString(R.string.only_office_user_agent) + val packageName = applicationContext.packageName + val androidVersion = Build.VERSION.RELEASE + var appVersion = "" + try { + val pInfo = applicationContext.packageManager.getPackageInfo(packageName, 0) + if (pInfo != null) { + appVersion = pInfo.versionName + } + } catch (e: NameNotFoundException) { + Log_OC.e(this, "Trying to get packageName", e.cause) + } + return String.format(appString, androidVersion, appVersion) + } } diff --git a/src/main/res/values/setup.xml b/src/main/res/values/setup.xml index 30a6dcbeabb0..896bcbbcc1db 100644 --- a/src/main/res/values/setup.xml +++ b/src/main/res/values/setup.xml @@ -18,6 +18,7 @@ nextcloud Nextcloud Mozilla/5.0 (Android) Nextcloud-android/%1$s + Mozilla/5.0 (Android %1$s) Mobile Nextcloud-android/%2$s From f2627a29a8d0a983dd9d48039084dd533700bc86 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 18 Dec 2019 09:51:58 +0100 Subject: [PATCH 10/14] enable caching Signed-off-by: tobiasKaminsky --- scripts/analysis/findbugs-results.txt | 2 +- .../owncloud/android/ui/activity/ExternalSiteWebView.java | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/analysis/findbugs-results.txt b/scripts/analysis/findbugs-results.txt index 102c15d5377b..ddabef86a0a1 100644 --- a/scripts/analysis/findbugs-results.txt +++ b/scripts/analysis/findbugs-results.txt @@ -1 +1 @@ -409 +412 diff --git a/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java b/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java index 3015c610dd74..c9d9c24fcca6 100644 --- a/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java +++ b/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java @@ -163,6 +163,12 @@ private void setupWebSettings(WebSettings webSettings) { // enable javascript webSettings.setJavaScriptEnabled(true); webSettings.setDomStorageEnabled(true); + + // caching disabled in debug mode + if ((getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) { + webSettings.setAppCacheEnabled(true); + webSettings.setAppCachePath(getCacheDir().getPath()); + } } private void setupActionBar(String title) { From ec2cfef13867a31d0f5e2336451520fe0a898fe1 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Fri, 1 Nov 2019 12:09:08 +0100 Subject: [PATCH 11/14] Direct editing support - abstract EditorWebView - support direct editing endpoint Signed-off-by: tobiasKaminsky --- build.gradle | 2 +- settings.gradle | 2 +- .../nextcloud/client/di/ComponentsModule.java | 2 + .../datamodel/FileDataStorageManager.java | 5 + .../owncloud/android/datamodel/OCFile.java | 4 + .../com/owncloud/android/db/ProviderMeta.java | 6 +- .../operations/RefreshFolderOperation.java | 3 + .../providers/FileContentProvider.java | 21 +- .../ui/activity/ExternalSiteWebView.java | 1 + .../ui/activity/FileDisplayActivity.java | 33 +- .../activity/RichDocumentsEditorWebView.java | 1 - .../android/ui/activity/TextEditorWebView.kt | 2 +- .../android/ui/adapter/OCFileListAdapter.java | 77 +++- .../ui/fragment/OCFileListFragment.java | 10 +- .../ui/helpers/FileOperationsHelper.java | 9 + .../OCFileListFragmentInterface.java | 2 + .../ui/preview/PreviewTextFileFragment.java | 390 ++++++++++++++++++ .../ui/preview/PreviewTextFragment.java | 381 +---------------- .../ui/preview/PreviewTextStringFragment.java | 177 ++++++++ .../android/utils/FileStorageUtils.java | 1 + src/main/res/drawable/ic_edit.xml | 32 ++ src/main/res/layout/list_header.xml | 36 ++ src/main/res/layout/text_file_preview.xml | 20 +- 23 files changed, 822 insertions(+), 395 deletions(-) create mode 100644 src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java create mode 100644 src/main/java/com/owncloud/android/ui/preview/PreviewTextStringFragment.java create mode 100644 src/main/res/drawable/ic_edit.xml create mode 100644 src/main/res/layout/list_header.xml diff --git a/build.gradle b/build.gradle index f51fd32b4cef..86d9e9d5749e 100644 --- a/build.gradle +++ b/build.gradle @@ -63,7 +63,7 @@ ext { daggerVersion = "2.25.3" markwonVersion = "4.2.0" prismVersion = "2.0.0" - androidLibraryVersion = "master-SNAPSHOT" + androidLibraryVersion = "richWorkspace-SNAPSHOT" travisBuild = System.getenv("TRAVIS") == "true" diff --git a/settings.gradle b/settings.gradle index 520b64d902c9..05a2e7e7d7eb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ include ':' -//include ':nextcloud-android-library' +//include 'nextcloud-android-library' diff --git a/src/main/java/com/nextcloud/client/di/ComponentsModule.java b/src/main/java/com/nextcloud/client/di/ComponentsModule.java index 9f0b9b6e98d4..efcbe05ed1d9 100644 --- a/src/main/java/com/nextcloud/client/di/ComponentsModule.java +++ b/src/main/java/com/nextcloud/client/di/ComponentsModule.java @@ -77,6 +77,7 @@ import com.owncloud.android.ui.preview.PreviewImageFragment; import com.owncloud.android.ui.preview.PreviewMediaFragment; import com.owncloud.android.ui.preview.PreviewTextFragment; +import com.owncloud.android.ui.preview.PreviewTextStringFragment; import com.owncloud.android.ui.preview.PreviewVideoActivity; import com.owncloud.android.ui.trashbin.TrashbinActivity; @@ -138,6 +139,7 @@ abstract class ComponentsModule { @ContributesAndroidInjector abstract ContactListFragment chooseContactListFragment(); @ContributesAndroidInjector abstract PreviewMediaFragment previewMediaFragment(); @ContributesAndroidInjector abstract PreviewTextFragment previewTextFragment(); + @ContributesAndroidInjector abstract PreviewTextStringFragment previewTextStringFragment(); @ContributesAndroidInjector abstract PhotoFragment photoFragment(); @ContributesAndroidInjector abstract MultipleAccountsDialog multipleAccountsDialog(); diff --git a/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java index 602902eb1101..39389a7d03a2 100644 --- a/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -39,6 +39,7 @@ import com.google.gson.JsonSyntaxException; import com.owncloud.android.MainApp; import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; +import com.owncloud.android.lib.common.DirectEditing; import com.owncloud.android.lib.common.network.WebdavEntry; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; @@ -215,6 +216,7 @@ public boolean saveFile(OCFile file) { cv.put(ProviderTableMeta.FILE_OWNER_DISPLAY_NAME, file.getOwnerDisplayName()); cv.put(ProviderTableMeta.FILE_NOTE, file.getNote()); cv.put(ProviderTableMeta.FILE_SHAREES, new Gson().toJson(file.getSharees())); + cv.put(ProviderTableMeta.FILE_RICH_WORKSPACE, file.getRichWorkspace()); boolean sameRemotePath = fileExists(file.getRemotePath()); if (sameRemotePath || @@ -465,6 +467,7 @@ private ContentValues createContentValueForFile(OCFile folder) { cv.put(ProviderTableMeta.FILE_OWNER_DISPLAY_NAME, folder.getOwnerDisplayName()); cv.put(ProviderTableMeta.FILE_NOTE, folder.getNote()); cv.put(ProviderTableMeta.FILE_SHAREES, new Gson().toJson(folder.getSharees())); + cv.put(ProviderTableMeta.FILE_RICH_WORKSPACE, folder.getRichWorkspace()); return cv; } @@ -505,6 +508,7 @@ private ContentValues createContentValueForFile(OCFile file, OCFile folder) { cv.put(ProviderTableMeta.FILE_OWNER_DISPLAY_NAME, file.getOwnerDisplayName()); cv.put(ProviderTableMeta.FILE_NOTE, file.getNote()); cv.put(ProviderTableMeta.FILE_SHAREES, new Gson().toJson(file.getSharees())); + cv.put(ProviderTableMeta.FILE_RICH_WORKSPACE, file.getRichWorkspace()); return cv; } @@ -1003,6 +1007,7 @@ private OCFile createFileInstance(Cursor c) { file.setOwnerId(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_OWNER_ID))); file.setOwnerDisplayName(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_OWNER_DISPLAY_NAME))); file.setNote(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_NOTE))); + file.setRichWorkspace(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_RICH_WORKSPACE))); String sharees = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_SHAREES)); diff --git a/src/main/java/com/owncloud/android/datamodel/OCFile.java b/src/main/java/com/owncloud/android/datamodel/OCFile.java index fd119c5890e1..e383de974255 100644 --- a/src/main/java/com/owncloud/android/datamodel/OCFile.java +++ b/src/main/java/com/owncloud/android/datamodel/OCFile.java @@ -91,6 +91,7 @@ public class OCFile implements Parcelable, Comparable, ServerFileInterfa @Getter @Setter private String ownerDisplayName; @Getter @Setter String note; @Getter @Setter private List sharees; + @Getter @Setter private String richWorkspace; /** * URI to the local path of the file contents, if stored in the device; cached after first call @@ -158,6 +159,7 @@ private OCFile(Parcel source) { ownerId = source.readString(); ownerDisplayName = source.readString(); mountType = (WebdavEntry.MountType) source.readSerializable(); + richWorkspace = source.readString(); } @Override @@ -190,6 +192,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeString(ownerId); dest.writeString(ownerDisplayName); dest.writeSerializable(mountType); + dest.writeString(richWorkspace); } public String getDecryptedRemotePath() { @@ -408,6 +411,7 @@ private void resetData() { encrypted = false; encryptedFileName = null; mountType = WebdavEntry.MountType.INTERNAL; + richWorkspace = ""; } /** diff --git a/src/main/java/com/owncloud/android/db/ProviderMeta.java b/src/main/java/com/owncloud/android/db/ProviderMeta.java index 76674112d27c..fb488528fbf4 100644 --- a/src/main/java/com/owncloud/android/db/ProviderMeta.java +++ b/src/main/java/com/owncloud/android/db/ProviderMeta.java @@ -47,6 +47,8 @@ static public class ProviderTableMeta implements BaseColumns { public static final String ARBITRARY_DATA_TABLE_NAME = "arbitrary_data"; public static final String VIRTUAL_TABLE_NAME = "virtual"; public static final String FILESYSTEM_TABLE_NAME = "filesystem"; + public static final String EDITORS_TABLE_NAME = "editors"; + public static final String CREATORS_TABLE_NAME = "creators"; private static final String CONTENT_PREFIX = "content://"; @@ -110,6 +112,7 @@ static public class ProviderTableMeta implements BaseColumns { public static final String FILE_OWNER_DISPLAY_NAME = "owner_display_name"; public static final String FILE_NOTE = "note"; public static final String FILE_SHAREES = "sharees"; + public static final String FILE_RICH_WORKSPACE = "rich_workspace"; public static final String[] FILE_ALL_COLUMNS = { _ID, FILE_PARENT, FILE_NAME, FILE_CREATION, FILE_MODIFIED, @@ -117,7 +120,8 @@ static public class ProviderTableMeta implements BaseColumns { FILE_PATH, FILE_ACCOUNT_OWNER, FILE_LAST_SYNC_DATE, FILE_LAST_SYNC_DATE_FOR_DATA, FILE_ETAG, FILE_ETAG_ON_SERVER, FILE_SHARED_VIA_LINK, FILE_SHARED_WITH_SHAREE, FILE_PUBLIC_LINK, FILE_PERMISSIONS, FILE_REMOTE_ID, FILE_UPDATE_THUMBNAIL, FILE_IS_DOWNLOADING, FILE_ETAG_IN_CONFLICT, FILE_FAVORITE, - FILE_IS_ENCRYPTED, FILE_MOUNT_TYPE, FILE_HAS_PREVIEW, FILE_UNREAD_COMMENTS_COUNT, FILE_SHAREES + FILE_IS_ENCRYPTED, FILE_MOUNT_TYPE, FILE_HAS_PREVIEW, FILE_UNREAD_COMMENTS_COUNT, FILE_SHAREES, + FILE_RICH_WORKSPACE }; public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME + " collate nocase asc"; diff --git a/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java b/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java index 4e3d4387a05e..9026bcf65d88 100644 --- a/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -422,6 +422,9 @@ private void synchronizeData(List folderAndFiles) { // update permission mLocalFolder.setPermissions(remoteFolder.getPermissions()); + // update richWorkpace + mLocalFolder.setRichWorkspace(remoteFolder.getRichWorkspace()); + DecryptedFolderMetadata metadata = getDecryptedFolderMetadata(encryptedAncestor); // get current data about local contents of the folder to synchronize diff --git a/src/main/java/com/owncloud/android/providers/FileContentProvider.java b/src/main/java/com/owncloud/android/providers/FileContentProvider.java index 7bf240213685..e9292b62dff8 100644 --- a/src/main/java/com/owncloud/android/providers/FileContentProvider.java +++ b/src/main/java/com/owncloud/android/providers/FileContentProvider.java @@ -719,7 +719,8 @@ private void createFilesTable(SQLiteDatabase db) { + ProviderTableMeta.FILE_OWNER_ID + TEXT + ProviderTableMeta.FILE_OWNER_DISPLAY_NAME + TEXT + ProviderTableMeta.FILE_NOTE + TEXT - + ProviderTableMeta.FILE_SHAREES + " TEXT);" + + ProviderTableMeta.FILE_SHAREES + TEXT + + ProviderTableMeta.FILE_RICH_WORKSPACE + " TEXT);" ); } @@ -2083,6 +2084,24 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (!upgraded) { Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion)); } + + if (oldVersion < 52 && newVersion >= 52) { + Log_OC.i(SQL, "Entering in the #52 add rich workspace to file table"); + db.beginTransaction(); + try { + db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + + ADD_COLUMN + ProviderTableMeta.FILE_RICH_WORKSPACE + " TEXT "); + + upgraded = true; + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + if (!upgraded) { + Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion)); + } } @Override diff --git a/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java b/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java index c9d9c24fcca6..feee3a6f1463 100644 --- a/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java +++ b/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java @@ -91,6 +91,7 @@ protected void onCreate(Bundle savedInstanceState) { webview.setFocusable(true); webview.setFocusableInTouchMode(true); webview.setClickable(true); +// webview.addJavascriptInterface(new TestMobileInterface(), "RichDocumentsMobileInterface"); // allow debugging (when building the debug version); see details in // https://developers.google.com/web/tools/chrome-devtools/remote-debugging/webviews diff --git a/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java index b9bf59894d68..1362d567e5a3 100644 --- a/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -113,7 +113,9 @@ import com.owncloud.android.ui.preview.PreviewImageActivity; import com.owncloud.android.ui.preview.PreviewImageFragment; import com.owncloud.android.ui.preview.PreviewMediaFragment; +import com.owncloud.android.ui.preview.PreviewTextFileFragment; import com.owncloud.android.ui.preview.PreviewTextFragment; +import com.owncloud.android.ui.preview.PreviewTextStringFragment; import com.owncloud.android.ui.preview.PreviewVideoActivity; import com.owncloud.android.utils.DataHolderUtil; import com.owncloud.android.utils.DisplayUtils; @@ -478,7 +480,7 @@ private void initFragmentsWithFile() { cleanSecondFragment(); if (file.isDown() && MimeTypeUtil.isVCard(file.getMimeType())) { startContactListFragment(file); - } else if (file.isDown() && PreviewTextFragment.canBePreviewed(file)) { + } else if (file.isDown() && PreviewTextFileFragment.canBePreviewed(file)) { startTextPreview(file, false); } } @@ -597,7 +599,7 @@ private Fragment chooseInitialSecondFragment(OCFile file) { int startPlaybackPosition = getIntent().getIntExtra(PreviewVideoActivity.EXTRA_START_POSITION, 0); boolean autoplay = getIntent().getBooleanExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, true); secondFragment = PreviewMediaFragment.newInstance(file, getAccount(), startPlaybackPosition, autoplay); - } else if (file.isDown() && PreviewTextFragment.canBePreviewed(file)) { + } else if (file.isDown() && PreviewTextFileFragment.canBePreviewed(file)) { secondFragment = null; } else { secondFragment = FileDetailFragment.newInstance(file, getAccount()); @@ -739,7 +741,7 @@ protected void refreshSecondFragment(String downloadEvent, String downloadedRemo } else if (MimeTypeUtil.isVCard(mWaitingToPreview.getMimeType())) { startContactListFragment(mWaitingToPreview); detailsFragmentChanged = true; - } else if (PreviewTextFragment.canBePreviewed(mWaitingToPreview)) { + } else if (PreviewTextFileFragment.canBePreviewed(mWaitingToPreview)) { startTextPreview(mWaitingToPreview, true); detailsFragmentChanged = true; } else { @@ -1504,7 +1506,7 @@ public void onReceive(Context context, Intent intent) { OCFile ocFile = getFile(); if (PreviewImageFragment.canBePreviewed(ocFile)) { startImagePreview(getFile(), true); - } else if (PreviewTextFragment.canBePreviewed(ocFile)) { + } else if (PreviewTextFileFragment.canBePreviewed(ocFile)) { startTextPreview(ocFile, true); } // TODO what about other kind of previews? @@ -1778,7 +1780,7 @@ private void refreshShowDetails() { ((PreviewMediaFragment) details).updateFile(file); } else if (details instanceof PreviewTextFragment) { // Refresh OCFile of the fragment - ((PreviewTextFragment) details).updateFile(file); + ((PreviewTextFileFragment) details).updateFile(file); } else { showDetails(file); } @@ -2060,8 +2062,8 @@ private void onRenameFileOperationFinish(RenameFileOperation operation, } } else if (details instanceof PreviewTextFragment && renamedFile.equals(details.getFile())) { - ((PreviewTextFragment) details).updateFile(renamedFile); - if (PreviewTextFragment.canBePreviewed(renamedFile)) { + ((PreviewTextFileFragment) details).updateFile(renamedFile); + if (PreviewTextFileFragment.canBePreviewed(renamedFile)) { startTextPreview(renamedFile, true); } else { getFileOperationsHelper().openFile(renamedFile); @@ -2385,6 +2387,23 @@ public void startTextPreview(OCFile file, boolean showPreview) { } } + /** + * Stars rich workspace preview for a folder. + * + * @param folder {@link OCFile} to preview its rich workspace. + */ + public void startRichWorkspacePreview(OCFile folder) { + Bundle args = new Bundle(); + args.putParcelable(EXTRA_FILE, folder); + Fragment textPreviewFragment = Fragment.instantiate(getApplicationContext(), + PreviewTextStringFragment.class.getName(), + args); + setSecondFragment(textPreviewFragment); + updateFragmentsVisibility(true); + updateActionBarTitleAndHomeButton(folder); + setFile(folder); + } + public void startContactListFragment(OCFile file) { Intent intent = new Intent(this, ContactsPreferenceActivity.class); intent.putExtra(ContactListFragment.FILE_NAME, Parcels.wrap(file)); diff --git a/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.java b/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.java index 76dac4d8f0e0..db5e40c7667f 100644 --- a/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.java +++ b/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.java @@ -99,7 +99,6 @@ protected void onCreate(Bundle savedInstanceState) { unbinder = ButterKnife.bind(this); - WebView.setWebContentsDebuggingEnabled(true); webview.addJavascriptInterface(new RichDocumentsMobileInterface(), "RichDocumentsMobileInterface"); webview.setWebChromeClient(new WebChromeClient() { diff --git a/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt b/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt index 6bcac9dab52f..a7d707f081b1 100644 --- a/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt +++ b/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt @@ -33,7 +33,7 @@ import com.owncloud.android.lib.common.utils.Log_OC @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) class TextEditorWebView : EditorWebView() { - @SuppressLint("AddJavascriptInterface") // suppress warning as webview is only used >= Lollipop + @SuppressLint("AddJavascriptInterface") // suppress warning as webview is only used >= Lollipop override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java index ec947a8fb25b..293bbf17a570 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java @@ -24,7 +24,6 @@ package com.owncloud.android.ui.adapter; -import android.accounts.Account; import android.accounts.AccountManager; import android.content.ContentValues; import android.content.Context; @@ -73,6 +72,7 @@ import com.owncloud.android.ui.activity.ComponentsGetter; import com.owncloud.android.ui.fragment.ExtendedListFragment; import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface; +import com.owncloud.android.ui.preview.PreviewTextFragment; import com.owncloud.android.utils.BitmapUtils; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.FileSortOrder; @@ -128,6 +128,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter asyncTasks = new ArrayList<>(); private boolean onlyOnDevice; @@ -270,7 +271,11 @@ public long getItemId(int position) { @Override public int getItemCount() { - return mFiles.size() + 1; + if (shouldShowHeader()) { + return mFiles.size() + 2; // for header and footer + } else { + return mFiles.size() + 1; // for footer + } } @NonNull @@ -299,6 +304,15 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int case VIEWTYPE_FOOTER: View itemView = LayoutInflater.from(mContext).inflate(R.layout.list_footer, parent, false); return new OCFileListFooterViewHolder(itemView); + + case VIEWTYPE_HEADER: + View headerView = LayoutInflater.from(mContext).inflate(R.layout.list_header, parent, false); + + ViewGroup.LayoutParams layoutParams = headerView.getLayoutParams(); + layoutParams.height = (int) (parent.getHeight() * 0.3); + headerView.setLayoutParams(layoutParams); + + return new OCFileListHeaderViewHolder(headerView); } } @@ -311,10 +325,16 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi PorterDuff.Mode.SRC_IN); footerViewHolder.progressBar.setVisibility( ocFileListFragmentInterface.isLoading() ? View.VISIBLE : View.GONE); + } else if (holder instanceof OCFileListHeaderViewHolder) { + OCFileListHeaderViewHolder headerViewHolder = (OCFileListHeaderViewHolder) holder; + String text = currentDirectory.getRichWorkspace(); + + PreviewTextFragment.setText(headerViewHolder.headerText, text, mContext); + headerViewHolder.headerView.setOnClickListener(v -> ocFileListFragmentInterface.onHeaderClicked()); } else { OCFileListGridImageViewHolder gridViewHolder = (OCFileListGridImageViewHolder) holder; - OCFile file = mFiles.get(position); + OCFile file = getItem(position); boolean gridImage = MimeTypeUtil.isImage(file) || MimeTypeUtil.isVideo(file); @@ -620,7 +640,7 @@ private String getFooterText() { OCFile file; final boolean showHiddenFiles = preferences.isShowHiddenFilesEnabled(); for (int i = 0; i < count; i++) { - file = getItem(i); + file = mFiles.get(i); if (file.isFolder()) { foldersCount++; } else { @@ -653,20 +673,42 @@ private String generateFooterText(int filesCount, int foldersCount) { } public OCFile getItem(int position) { - return mFiles.get(position); + if (shouldShowHeader()) { + return mFiles.get(position - 1); + } else { + return mFiles.get(position); + } + } + + private boolean shouldShowHeader() { + if (currentDirectory == null) { + return false; + } + + return !TextUtils.isEmpty(currentDirectory.getRichWorkspace()); } @Override public int getItemViewType(int position) { - if (position == mFiles.size()) { - return VIEWTYPE_FOOTER; - } else { - if (MimeTypeUtil.isImageOrVideo(getItem(position))) { - return VIEWTYPE_IMAGE; + if (shouldShowHeader()) { + if (position == 0) { + return VIEWTYPE_HEADER; } else { - return VIEWTYPE_ITEM; + if (position == mFiles.size() + 1) { + return VIEWTYPE_FOOTER; + } + } + } else { + if (position == mFiles.size()) { + return VIEWTYPE_FOOTER; } } + + if (MimeTypeUtil.isImageOrVideo(getItem(position))) { + return VIEWTYPE_IMAGE; + } else { + return VIEWTYPE_ITEM; + } } private void showShareIcon(OCFileListGridImageViewHolder gridViewHolder, OCFile file) { @@ -1135,4 +1177,17 @@ private OCFileListFooterViewHolder(View itemView) { ButterKnife.bind(this, itemView); } } + + static class OCFileListHeaderViewHolder extends RecyclerView.ViewHolder { + @BindView(R.id.headerView) + public View headerView; + + @BindView(R.id.headerText) + public TextView headerText; + + private OCFileListHeaderViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } } diff --git a/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 5de8a66ab672..ae41b0fbb32c 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -89,7 +89,7 @@ import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface; import com.owncloud.android.ui.preview.PreviewImageFragment; import com.owncloud.android.ui.preview.PreviewMediaFragment; -import com.owncloud.android.ui.preview.PreviewTextFragment; +import com.owncloud.android.ui.preview.PreviewTextFileFragment; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.EncryptionUtils; import com.owncloud.android.utils.FileSortOrder; @@ -100,6 +100,7 @@ import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; +import org.jetbrains.annotations.NotNull; import org.parceler.Parcels; import java.io.File; @@ -500,6 +501,11 @@ public void newPresentation() { .show(requireActivity().getSupportFragmentManager(), DIALOG_CREATE_DOCUMENT); } + @Override + public void onHeaderClicked() { + ((FileDisplayActivity) mContainerActivity).startRichWorkspacePreview(getCurrentFile()); + } + /** * Handler for multiple selection mode. *

@@ -929,7 +935,7 @@ public void onItemClicked(OCFile file) { } } else if (file.isDown() && MimeTypeUtil.isVCard(file)) { ((FileDisplayActivity) mContainerActivity).startContactListFragment(file); - } else if (PreviewTextFragment.canBePreviewed(file)) { + } else if (PreviewTextFileFragment.canBePreviewed(file)) { ((FileDisplayActivity) mContainerActivity).startTextPreview(file, false); } else if (file.isDown()) { if (PreviewMediaFragment.canBePreviewed(file)) { diff --git a/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java b/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java index 3c00ae3074fa..ef720682c05d 100755 --- a/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java +++ b/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java @@ -361,6 +361,15 @@ public void openFileWithTextEditor(OCFile file, Context context) { context.startActivity(textEditorIntent); } + public void openRichWorkspaceWithTextEditor(OCFile file, String url, Context context) { + Intent textEditorIntent = new Intent(context, TextEditorWebView.class); + textEditorIntent.putExtra(ExternalSiteWebView.EXTRA_TITLE, "Text"); + textEditorIntent.putExtra(ExternalSiteWebView.EXTRA_URL, url); + textEditorIntent.putExtra(ExternalSiteWebView.EXTRA_FILE, file); + textEditorIntent.putExtra(ExternalSiteWebView.EXTRA_SHOW_SIDEBAR, false); + context.startActivity(textEditorIntent); + } + @NonNull private Intent createOpenFileIntent(OCFile file) { String storagePath = file.getStoragePath(); diff --git a/src/main/java/com/owncloud/android/ui/interfaces/OCFileListFragmentInterface.java b/src/main/java/com/owncloud/android/ui/interfaces/OCFileListFragmentInterface.java index e4fe44d4875d..5b2fd546d57f 100644 --- a/src/main/java/com/owncloud/android/ui/interfaces/OCFileListFragmentInterface.java +++ b/src/main/java/com/owncloud/android/ui/interfaces/OCFileListFragmentInterface.java @@ -46,4 +46,6 @@ public interface OCFileListFragmentInterface { boolean onLongItemClicked(OCFile file); boolean isLoading(); + + void onHeaderClicked(); } diff --git a/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java b/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java new file mode 100644 index 000000000000..cbed5ebef449 --- /dev/null +++ b/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java @@ -0,0 +1,390 @@ +/* + * + * Nextcloud Android client application + * + * @author Tobias Kaminsky + * Copyright (C) 2019 Tobias Kaminsky + * Copyright (C) 2019 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.owncloud.android.ui.preview; + +import android.accounts.Account; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; + +import com.nextcloud.client.account.UserAccountManager; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.files.FileMenuFilter; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.ui.activity.FileDisplayActivity; +import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; +import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment; +import com.owncloud.android.utils.DisplayUtils; +import com.owncloud.android.utils.MimeTypeUtil; + +import org.jetbrains.annotations.NotNull; +import org.mozilla.universalchardet.ReaderFactory; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.io.StringWriter; +import java.lang.ref.WeakReference; +import java.util.LinkedList; +import java.util.List; +import java.util.Scanner; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.appcompat.widget.SearchView; +import androidx.core.view.MenuItemCompat; + +public class PreviewTextFileFragment extends PreviewTextFragment { + private static final String EXTRA_FILE = "FILE"; + private static final String EXTRA_ACCOUNT = "ACCOUNT"; + private static final String TAG = PreviewTextFileFragment.class.getSimpleName(); + + private TextLoadAsyncTask textLoadAsyncTask; + private Account account; + + @Inject UserAccountManager accountManager; + + /** + * Creates an empty fragment for previews. + *

+ * MUST BE KEPT: the system uses it when tries to re-instantiate a fragment automatically (for instance, when the + * device is turned a aside). + *

+ * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful construction + */ + public PreviewTextFileFragment() { + super(); + account = null; + } + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + + OCFile file = getFile(); + + Bundle args = getArguments(); + + if (file == null) { + file = args.getParcelable(FileDisplayActivity.EXTRA_FILE); + } + + if (account == null) { + account = args.getParcelable(FileDisplayActivity.EXTRA_ACCOUNT); + } + + if (args.containsKey(FileDisplayActivity.EXTRA_SEARCH_QUERY)) { + mSearchQuery = args.getString(FileDisplayActivity.EXTRA_SEARCH_QUERY); + } + mSearchOpen = args.getBoolean(FileDisplayActivity.EXTRA_SEARCH, false); + + if (savedInstanceState == null) { + if (file == null) { + throw new IllegalStateException("Instanced with a NULL OCFile"); + } + if (account == null) { + throw new IllegalStateException("Instanced with a NULL ownCloud Account"); + } + } else { + file = savedInstanceState.getParcelable(EXTRA_FILE); + account = savedInstanceState.getParcelable(EXTRA_ACCOUNT); + } + + mHandler = new Handler(); + setFile(file); + } + + /** + * {@inheritDoc} + */ + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putParcelable(PreviewTextFileFragment.EXTRA_FILE, getFile()); + outState.putParcelable(PreviewTextFileFragment.EXTRA_ACCOUNT, account); + + super.onSaveInstanceState(outState); + } + + @Override + void loadAndShowTextPreview() { + textLoadAsyncTask = new TextLoadAsyncTask(new WeakReference<>(mTextPreview)); + textLoadAsyncTask.execute(getFile().getStoragePath()); + } + + /** + * Reads the file to preview and shows its contents. Too critical to be anonymous. + */ + private class TextLoadAsyncTask extends AsyncTask { + private static final int PARAMS_LENGTH = 1; + private final WeakReference mTextViewReference; + + private TextLoadAsyncTask(WeakReference textView) { + mTextViewReference = textView; + } + + @Override + protected void onPreExecute() { + // not used at the moment + } + + @Override + protected StringWriter doInBackground(Object... params) { + if (params.length != PARAMS_LENGTH) { + throw new IllegalArgumentException("The parameter to " + TextLoadAsyncTask.class.getName() + + " must be (1) the file location"); + } + String location = (String) params[0]; + + Scanner scanner = null; + StringWriter source = new StringWriter(); + BufferedWriter bufferedWriter = new BufferedWriter(source); + Reader reader = null; + + try { + File file = new File(location); + reader = ReaderFactory.createReaderFromFile(file); + scanner = new Scanner(reader); + + while (scanner.hasNextLine()) { + bufferedWriter.append(scanner.nextLine()); + if (scanner.hasNextLine()) { + bufferedWriter.append("\n"); + } + } + bufferedWriter.close(); + IOException exc = scanner.ioException(); + if (exc != null) { + throw exc; + } + } catch (IOException e) { + Log_OC.e(TAG, e.getMessage(), e); + finish(); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + Log_OC.e(TAG, e.getMessage(), e); + finish(); + } + } + if (scanner != null) { + scanner.close(); + } + } + return source; + } + + @Override + protected void onPostExecute(final StringWriter stringWriter) { + final TextView textView = mTextViewReference.get(); + + if (textView != null) { + mOriginalText = stringWriter.toString(); + mSearchView.setOnQueryTextListener(PreviewTextFileFragment.this); + + setText(textView, mOriginalText, getContext()); + + if (mSearchOpen) { + mSearchView.setQuery(mSearchQuery, true); + } + textView.setVisibility(View.VISIBLE); + } + + if (mMultiView != null) { + mMultiView.setVisibility(View.GONE); + } + + } + + } + + /** + * {@inheritDoc} + */ + @Override + public void onCreateOptionsMenu(@NotNull Menu menu, @NotNull MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.item_file, menu); + + MenuItem menuItem = menu.findItem(R.id.action_search); + menuItem.setVisible(true); + mSearchView = (SearchView) MenuItemCompat.getActionView(menuItem); + mSearchView.setMaxWidth(Integer.MAX_VALUE); + + if (mSearchOpen) { + mSearchView.setIconified(false); + mSearchView.setQuery(mSearchQuery, false); + mSearchView.clearFocus(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onPrepareOptionsMenu(@NotNull Menu menu) { + super.onPrepareOptionsMenu(menu); + + if (containerActivity.getStorageManager() != null) { + Account currentAccount = containerActivity.getStorageManager().getAccount(); + FileMenuFilter mf = new FileMenuFilter( + getFile(), + currentAccount, + containerActivity, + getActivity(), + false + ); + mf.filter(menu, + true, + accountManager.isMediaStreamingSupported(currentAccount)); + } + + // additional restriction for this fragment + FileMenuFilter.hideMenuItems( + menu.findItem(R.id.action_rename_file), + menu.findItem(R.id.action_select_all), + menu.findItem(R.id.action_move), + menu.findItem(R.id.action_download_file), + menu.findItem(R.id.action_sync_file), + menu.findItem(R.id.action_sync_account), + menu.findItem(R.id.action_favorite), + menu.findItem(R.id.action_unset_favorite) + ); + + Boolean dualPane = getResources().getBoolean(R.bool.large_land_layout); + + if (!dualPane) { + FileMenuFilter.hideMenuItems(menu.findItem(R.id.action_switch_view), + menu.findItem(R.id.action_sort) + ); + } + + if (getFile().isSharedWithMe() && !getFile().canReshare()) { + FileMenuFilter.hideMenuItem(menu.findItem(R.id.action_send_share_file)); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_send_share_file: { + if (getFile().isSharedWithMe() && !getFile().canReshare()) { + DisplayUtils.showSnackMessage(getView(), R.string.resharing_is_not_allowed); + } else { + containerActivity.getFileOperationsHelper().sendShareFile(getFile()); + } + return true; + } + case R.id.action_open_file_with: { + openFile(); + return true; + } + case R.id.action_remove_file: { + RemoveFilesDialogFragment dialog = RemoveFilesDialogFragment.newInstance(getFile()); + dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION); + return true; + } + case R.id.action_see_details: { + seeDetails(); + return true; + } + case R.id.action_sync_file: { + containerActivity.getFileOperationsHelper().syncFile(getFile()); + return true; + } + + default: + return super.onOptionsItemSelected(item); + } + } + + /** + * Update the file of the fragment with file value + * + * @param file The new file to set + */ + public void updateFile(OCFile file) { + setFile(file); + } + + private void seeDetails() { + containerActivity.showDetails(getFile()); + } + + /** + * Opens the previewed file with an external application. + */ + private void openFile() { + containerActivity.getFileOperationsHelper().openFile(getFile()); + finish(); + } + + /** + * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewTextFileFragment} to be previewed. + * + * @param file File to test if can be previewed. + * @return 'True' if the file can be handled by the fragment. + */ + public static boolean canBePreviewed(OCFile file) { + final List unsupportedTypes = new LinkedList<>(); + unsupportedTypes.add("text/richtext"); + unsupportedTypes.add("text/rtf"); + unsupportedTypes.add("text/calendar"); + unsupportedTypes.add("text/vnd.abc"); + unsupportedTypes.add("text/vnd.fmi.flexstor"); + unsupportedTypes.add("text/vnd.rn-realtext"); + unsupportedTypes.add("text/vnd.wap.wml"); + unsupportedTypes.add("text/vnd.wap.wmlscript"); + return file != null && file.isDown() && MimeTypeUtil.isText(file) && + !unsupportedTypes.contains(file.getMimeType()) && + !unsupportedTypes.contains(MimeTypeUtil.getMimeTypeFromPath(file.getRemotePath())); + } + + + @Override + public void onStop() { + super.onStop(); + Log_OC.e(TAG, "onStop"); + + if (textLoadAsyncTask != null) { + textLoadAsyncTask.cancel(Boolean.TRUE); + } + } + +} diff --git a/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java b/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java index 25f845c1e6ab..7937e296d9af 100644 --- a/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java +++ b/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java @@ -19,21 +19,16 @@ package com.owncloud.android.ui.preview; -import android.accounts.Account; import android.content.Context; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; -import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.text.Html; import android.text.Spanned; import android.text.TextPaint; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; @@ -44,35 +39,16 @@ import com.nextcloud.client.account.UserAccountManager; import com.nextcloud.client.di.Injectable; import com.owncloud.android.R; -import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.files.FileMenuFilter; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.ui.activity.FileDisplayActivity; -import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; -import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment; import com.owncloud.android.ui.fragment.FileFragment; -import com.owncloud.android.utils.DisplayUtils; -import com.owncloud.android.utils.MimeTypeUtil; import com.owncloud.android.utils.StringUtils; import com.owncloud.android.utils.ThemeUtils; -import org.mozilla.universalchardet.ReaderFactory; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.IOException; -import java.io.Reader; -import java.io.StringWriter; -import java.lang.ref.WeakReference; -import java.util.LinkedList; -import java.util.List; -import java.util.Scanner; - import javax.inject.Inject; import androidx.annotation.NonNull; import androidx.appcompat.widget.SearchView; -import androidx.core.view.MenuItemCompat; import io.noties.markwon.AbstractMarkwonPlugin; import io.noties.markwon.Markwon; import io.noties.markwon.core.MarkwonTheme; @@ -94,46 +70,25 @@ }, grammarLocatorClassName = ".MarkwonGrammarLocator" ) -public class PreviewTextFragment extends FileFragment implements SearchView.OnQueryTextListener, Injectable { - private static final String EXTRA_FILE = "FILE"; - private static final String EXTRA_ACCOUNT = "ACCOUNT"; +public abstract class PreviewTextFragment extends FileFragment implements SearchView.OnQueryTextListener, Injectable { private static final String TAG = PreviewTextFragment.class.getSimpleName(); - private Account mAccount; - private TextView mTextPreview; - private TextLoadAsyncTask mTextLoadTask; - private String mOriginalText; - - private Handler mHandler; - private SearchView mSearchView; - private RelativeLayout mMultiView; + protected SearchView mSearchView; + protected String mSearchQuery = ""; + protected boolean mSearchOpen; + protected TextView mTextPreview; + protected Handler mHandler; + protected RelativeLayout mMultiView; + protected String mOriginalText; private TextView mMultiListMessage; private TextView mMultiListHeadline; private ImageView mMultiListIcon; private ProgressBar mMultiListProgress; - - private String mSearchQuery = ""; - private boolean mSearchOpen; - @Inject UserAccountManager accountManager; - /** - * Creates an empty fragment for previews. - * - * MUST BE KEPT: the system uses it when tries to re-instantiate a fragment automatically - * (for instance, when the device is turned a aside). - * - * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful - * construction - */ - public PreviewTextFragment() { - super(); - mAccount = null; - } - /** * {@inheritDoc} */ @@ -171,59 +126,6 @@ private void setMultiListLoadingMessage() { } } - - /** - * {@inheritDoc} - */ - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - - OCFile file = getFile(); - - Bundle args = getArguments(); - - if (file == null) { - file = args.getParcelable(FileDisplayActivity.EXTRA_FILE); - } - - if (mAccount == null) { - mAccount = args.getParcelable(FileDisplayActivity.EXTRA_ACCOUNT); - } - - if (args.containsKey(FileDisplayActivity.EXTRA_SEARCH_QUERY)) { - mSearchQuery = args.getString(FileDisplayActivity.EXTRA_SEARCH_QUERY); - } - mSearchOpen = args.getBoolean(FileDisplayActivity.EXTRA_SEARCH, false); - - if (savedInstanceState == null) { - if (file == null) { - throw new IllegalStateException("Instanced with a NULL OCFile"); - } - if (mAccount == null) { - throw new IllegalStateException("Instanced with a NULL ownCloud Account"); - } - } else { - file = savedInstanceState.getParcelable(EXTRA_FILE); - mAccount = savedInstanceState.getParcelable(EXTRA_ACCOUNT); - } - - mHandler = new Handler(); - setFile(file); - } - - /** - * {@inheritDoc} - */ - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - outState.putParcelable(PreviewTextFragment.EXTRA_FILE, getFile()); - outState.putParcelable(PreviewTextFragment.EXTRA_ACCOUNT, mAccount); - - super.onSaveInstanceState(outState); - } - @Override public void onStart() { super.onStart(); @@ -232,11 +134,7 @@ public void onStart() { loadAndShowTextPreview(); } - private void loadAndShowTextPreview() { - mTextLoadTask = new TextLoadAsyncTask(new WeakReference<>(mTextPreview)); - mTextLoadTask.execute(getFile().getStoragePath()); - } - + abstract void loadAndShowTextPreview(); @Override public boolean onQueryTextSubmit(String query) { @@ -267,7 +165,7 @@ private void performSearch(final String query, int delay) { mTextPreview.setText(Html.fromHtml(coloredText.replace("\n", "
"))); } } else { - setText(mTextPreview, mOriginalText, getFile()); + setText(mTextPreview, mOriginalText, getContext()); } }, delay); } @@ -277,7 +175,7 @@ private void performSearch(final String query, int delay) { } } - private Spanned getRenderedMarkdownText(Context context, String markdown) { + protected static Spanned getRenderedMarkdownText(Context context, String markdown) { Prism4j prism4j = new Prism4j(new MarkwonGrammarLocator()); Prism4jTheme prism4jTheme = Prism4jThemeDefault.create(); TaskListDrawable drawable = new TaskListDrawable(Color.GRAY, Color.GRAY, Color.WHITE); @@ -302,263 +200,16 @@ public void configureTheme(@NonNull MarkwonTheme.Builder builder) { return markwon.toMarkdown(markdown); } - /** - * Reads the file to preview and shows its contents. Too critical to be anonymous. - */ - private class TextLoadAsyncTask extends AsyncTask { - private static final int PARAMS_LENGTH = 1; - private final WeakReference mTextViewReference; - - private TextLoadAsyncTask(WeakReference textView) { - mTextViewReference = textView; - } - - @Override - protected void onPreExecute() { - // not used at the moment - } - - @Override - protected StringWriter doInBackground(Object... params) { - if (params.length != PARAMS_LENGTH) { - throw new IllegalArgumentException("The parameter to " + TextLoadAsyncTask.class.getName() - + " must be (1) the file location"); - } - String location = (String) params[0]; - - Scanner scanner = null; - StringWriter source = new StringWriter(); - BufferedWriter bufferedWriter = new BufferedWriter(source); - Reader reader = null; - - try { - File file = new File(location); - reader = ReaderFactory.createReaderFromFile(file); - scanner = new Scanner(reader); - - while (scanner.hasNextLine()) { - bufferedWriter.append(scanner.nextLine()); - if (scanner.hasNextLine()) { - bufferedWriter.append("\n"); - } - } - bufferedWriter.close(); - IOException exc = scanner.ioException(); - if (exc != null) { - throw exc; - } - } catch (IOException e) { - Log_OC.e(TAG, e.getMessage(), e); - finish(); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - Log_OC.e(TAG, e.getMessage(), e); - finish(); - } - } - if (scanner != null) { - scanner.close(); - } - } - return source; - } - - @Override - protected void onPostExecute(final StringWriter stringWriter) { - final TextView textView = mTextViewReference.get(); - - if (textView != null) { - mOriginalText = stringWriter.toString(); - mSearchView.setOnQueryTextListener(PreviewTextFragment.this); - - setText(textView, mOriginalText, getFile()); - - if (mSearchOpen) { - mSearchView.setQuery(mSearchQuery, true); - } - textView.setVisibility(View.VISIBLE); - } - - if (mMultiView != null) { - mMultiView.setVisibility(View.GONE); - } - - } - - } - - /** - * {@inheritDoc} - */ - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.item_file, menu); - - MenuItem menuItem = menu.findItem(R.id.action_search); - menuItem.setVisible(true); - mSearchView = (SearchView) MenuItemCompat.getActionView(menuItem); - mSearchView.setMaxWidth(Integer.MAX_VALUE); - - if (mSearchOpen) { - mSearchView.setIconified(false); - mSearchView.setQuery(mSearchQuery, false); - mSearchView.clearFocus(); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - - if (containerActivity.getStorageManager() != null) { - Account currentAccount = containerActivity.getStorageManager().getAccount(); - FileMenuFilter mf = new FileMenuFilter( - getFile(), - currentAccount, - containerActivity, - getActivity(), - false - ); - mf.filter(menu, - true, - accountManager.isMediaStreamingSupported(currentAccount)); - } - - // additional restriction for this fragment - FileMenuFilter.hideMenuItems( - menu.findItem(R.id.action_rename_file), - menu.findItem(R.id.action_select_all), - menu.findItem(R.id.action_move), - menu.findItem(R.id.action_download_file), - menu.findItem(R.id.action_sync_file), - menu.findItem(R.id.action_sync_account), - menu.findItem(R.id.action_favorite), - menu.findItem(R.id.action_unset_favorite) - ); - - Boolean dualPane = getResources().getBoolean(R.bool.large_land_layout); - - if (!dualPane) { - FileMenuFilter.hideMenuItems(menu.findItem(R.id.action_switch_view), - menu.findItem(R.id.action_sort) - ); - } - - if(getFile().isSharedWithMe() && !getFile().canReshare()){ - FileMenuFilter.hideMenuItem(menu.findItem(R.id.action_send_share_file)); - } - } - - /** - * {@inheritDoc} - */ - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_send_share_file: { - if(getFile().isSharedWithMe() && !getFile().canReshare()){ - DisplayUtils.showSnackMessage(getView(), R.string.resharing_is_not_allowed); - } else { - containerActivity.getFileOperationsHelper().sendShareFile(getFile()); - } - return true; - } - case R.id.action_open_file_with: { - openFile(); - return true; - } - case R.id.action_remove_file: { - RemoveFilesDialogFragment dialog = RemoveFilesDialogFragment.newInstance(getFile()); - dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION); - return true; - } - case R.id.action_see_details: { - seeDetails(); - return true; - } - case R.id.action_sync_file: { - containerActivity.getFileOperationsHelper().syncFile(getFile()); - return true; - } - - default: - return super.onOptionsItemSelected(item); - } - } - - /** - * Update the file of the fragment with file value - * - * @param file The new file to set - */ - public void updateFile(OCFile file) { - setFile(file); - } - - private void seeDetails() { - containerActivity.showDetails(getFile()); - } - - @Override - public void onStop() { - super.onStop(); - Log_OC.e(TAG, "onStop"); - if (mTextLoadTask != null) { - mTextLoadTask.cancel(Boolean.TRUE); - } - } - - /** - * Opens the previewed file with an external application. - */ - private void openFile() { - containerActivity.getFileOperationsHelper().openFile(getFile()); - finish(); - } - - /** - * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewTextFragment} to be previewed. - * - * @param file File to test if can be previewed. - * @return 'True' if the file can be handled by the fragment. - */ - public static boolean canBePreviewed(OCFile file) { - final List unsupportedTypes = new LinkedList<>(); - unsupportedTypes.add("text/richtext"); - unsupportedTypes.add("text/rtf"); - unsupportedTypes.add("text/calendar"); - unsupportedTypes.add("text/vnd.abc"); - unsupportedTypes.add("text/vnd.fmi.flexstor"); - unsupportedTypes.add("text/vnd.rn-realtext"); - unsupportedTypes.add("text/vnd.wap.wml"); - unsupportedTypes.add("text/vnd.wap.wmlscript"); - return file != null && file.isDown() && MimeTypeUtil.isText(file) && - !unsupportedTypes.contains(file.getMimeType()) && - !unsupportedTypes.contains(MimeTypeUtil.getMimeTypeFromPath(file.getRemotePath())); - } - /** * Finishes the preview */ - private void finish() { - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - getActivity().onBackPressed(); - } - }); + protected void finish() { + getActivity().runOnUiThread(() -> getActivity().onBackPressed()); } - private void setText(TextView textView, String text, OCFile file) { - if (MimeTypeUtil.MIMETYPE_TEXT_MARKDOWN.equals(file.getMimeType())) { - textView.setText(getRenderedMarkdownText(getContext(), text)); + public static void setText(TextView textView, String text, Context context) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN && context != null) { + textView.setText(getRenderedMarkdownText(context, text)); } else { textView.setText(text); } diff --git a/src/main/java/com/owncloud/android/ui/preview/PreviewTextStringFragment.java b/src/main/java/com/owncloud/android/ui/preview/PreviewTextStringFragment.java new file mode 100644 index 000000000000..ab426791627f --- /dev/null +++ b/src/main/java/com/owncloud/android/ui/preview/PreviewTextStringFragment.java @@ -0,0 +1,177 @@ +/* + * + * Nextcloud Android client application + * + * @author Tobias Kaminsky + * Copyright (C) 2019 Tobias Kaminsky + * Copyright (C) 2019 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.owncloud.android.ui.preview; + +import android.accounts.Account; +import android.os.Bundle; +import android.os.Handler; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.nextcloud.android.lib.richWorkspace.RichWorkspaceDirectEditingRemoteOperation; +import com.nextcloud.client.account.UserAccountManager; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.ui.activity.FileDisplayActivity; +import com.owncloud.android.utils.DisplayUtils; +import com.owncloud.android.utils.ThemeUtils; + +import org.jetbrains.annotations.NotNull; + +import javax.inject.Inject; + +import androidx.annotation.NonNull; +import androidx.appcompat.widget.SearchView; +import androidx.core.view.MenuItemCompat; + +public class PreviewTextStringFragment extends PreviewTextFragment { + private static final String TAG = PreviewTextStringFragment.class.getSimpleName(); + private static final String EXTRA_FILE = "FILE"; + + private FloatingActionButton mFabMain; + + @Inject UserAccountManager accountManager; + + /** + * Creates an empty fragment for previews. + *

+ * MUST BE KEPT: the system uses it when tries to re-instantiate a fragment automatically (for instance, when the + * device is turned a aside). + *

+ * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful construction + */ + public PreviewTextStringFragment() { + super(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setHasOptionsMenu(true); + + Bundle args = getArguments(); + + if (args.containsKey(FileDisplayActivity.EXTRA_SEARCH_QUERY)) { + mSearchQuery = args.getString(FileDisplayActivity.EXTRA_SEARCH_QUERY); + } + mSearchOpen = args.getBoolean(FileDisplayActivity.EXTRA_SEARCH, false); + + mHandler = new Handler(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putParcelable(PreviewTextStringFragment.EXTRA_FILE, getFile()); + + super.onSaveInstanceState(outState); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = super.onCreateView(inflater, container, savedInstanceState); + + if (view == null) { + throw new RuntimeException("View may not be null"); + } + + mFabMain = view.findViewById(R.id.text_preview_fab); + mFabMain.setVisibility(View.VISIBLE); + mFabMain.setEnabled(true); + mFabMain.setOnClickListener(v -> edit()); + ThemeUtils.tintFloatingActionButton(mFabMain, R.drawable.ic_edit, getContext()); + + return view; + } + + /** + * {@inheritDoc} + */ + @Override + public void onCreateOptionsMenu(@NotNull Menu menu, @NotNull MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + + MenuItem menuItem = menu.findItem(R.id.action_search); + menuItem.setVisible(true); + mSearchView = (SearchView) MenuItemCompat.getActionView(menuItem); + mSearchView.setOnQueryTextListener(this); + mSearchView.setMaxWidth(Integer.MAX_VALUE); + + if (mSearchOpen) { + mSearchView.setIconified(false); + mSearchView.setQuery(mSearchQuery, true); + mSearchView.clearFocus(); + } + } + + @Override + public void onPrepareOptionsMenu(@NonNull Menu menu) { + super.onPrepareOptionsMenu(menu); + + menu.findItem(R.id.action_sync_account).setVisible(false); + menu.findItem(R.id.action_sort).setVisible(false); + menu.findItem(R.id.action_switch_view).setVisible(false); + } + + void loadAndShowTextPreview() { + if (mTextPreview != null) { + mOriginalText = getFile().getRichWorkspace(); + setText(mTextPreview, mOriginalText, getContext()); + mTextPreview.setVisibility(View.VISIBLE); + } + + if (mMultiView != null) { + mMultiView.setVisibility(View.GONE); + } + } + + private void edit() { + new Thread(() -> { + RemoteOperationResult result = new RichWorkspaceDirectEditingRemoteOperation(getFile().getRemotePath()) + .execute(accountManager.getUser().toPlatformAccount(), getContext()); + + if (result.isSuccess()) { + String url = (String) result.getSingleData(); + containerActivity.getFileOperationsHelper().openRichWorkspaceWithTextEditor(getFile(), + url, + getContext()); + } else { + DisplayUtils.showSnackMessage(getView(), "Error"); + } + }).start(); + } + + // TODO on close clean search query +} diff --git a/src/main/java/com/owncloud/android/utils/FileStorageUtils.java b/src/main/java/com/owncloud/android/utils/FileStorageUtils.java index 578bf500cf72..f9d724485896 100644 --- a/src/main/java/com/owncloud/android/utils/FileStorageUtils.java +++ b/src/main/java/com/owncloud/android/utils/FileStorageUtils.java @@ -223,6 +223,7 @@ public static OCFile fillOCFile(RemoteFile remote) { file.setOwnerDisplayName(remote.getOwnerDisplayName()); file.setNote(remote.getNote()); file.setSharees(new ArrayList<>(Arrays.asList(remote.getSharees()))); + file.setRichWorkspace(remote.getRichWorkspace()); return file; } diff --git a/src/main/res/drawable/ic_edit.xml b/src/main/res/drawable/ic_edit.xml new file mode 100644 index 000000000000..9ff05de2c05d --- /dev/null +++ b/src/main/res/drawable/ic_edit.xml @@ -0,0 +1,32 @@ + + + + + diff --git a/src/main/res/layout/list_header.xml b/src/main/res/layout/list_header.xml new file mode 100644 index 000000000000..bf0453338317 --- /dev/null +++ b/src/main/res/layout/list_header.xml @@ -0,0 +1,36 @@ + + + + + + diff --git a/src/main/res/layout/text_file_preview.xml b/src/main/res/layout/text_file_preview.xml index b95996501ec2..bad110386047 100644 --- a/src/main/res/layout/text_file_preview.xml +++ b/src/main/res/layout/text_file_preview.xml @@ -21,9 +21,9 @@ android:layout_height="match_parent" android:fillViewport="true"> - + android:layout_height="wrap_content"> - - \ No newline at end of file + + + From b4ea014301a21603faebc4f641c0d346a31e5f52 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Thu, 19 Dec 2019 07:33:45 +0100 Subject: [PATCH 12/14] use master branch Signed-off-by: tobiasKaminsky --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 86d9e9d5749e..f51fd32b4cef 100644 --- a/build.gradle +++ b/build.gradle @@ -63,7 +63,7 @@ ext { daggerVersion = "2.25.3" markwonVersion = "4.2.0" prismVersion = "2.0.0" - androidLibraryVersion = "richWorkspace-SNAPSHOT" + androidLibraryVersion = "master-SNAPSHOT" travisBuild = System.getenv("TRAVIS") == "true" From 0d84a2dd34a36c6db9d203bc3f819e3516e44760 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Thu, 19 Dec 2019 08:44:36 +0100 Subject: [PATCH 13/14] warning about used feature in newer SDK Signed-off-by: tobiasKaminsky --- scripts/analysis/findbugs-results.txt | 2 +- scripts/analysis/lint-results.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/analysis/findbugs-results.txt b/scripts/analysis/findbugs-results.txt index ddabef86a0a1..36352541cc85 100644 --- a/scripts/analysis/findbugs-results.txt +++ b/scripts/analysis/findbugs-results.txt @@ -1 +1 @@ -412 +413 diff --git a/scripts/analysis/lint-results.txt b/scripts/analysis/lint-results.txt index 8a9ea1fc4c62..17a46e780670 100644 --- a/scripts/analysis/lint-results.txt +++ b/scripts/analysis/lint-results.txt @@ -1,2 +1,2 @@ DO NOT TOUCH; GENERATED BY DRONE - Lint Report: 73 warnings + Lint Report: 74 warnings From b20767140ffeb2fec8aba3d63cc7af3067beee4e Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Thu, 19 Dec 2019 09:10:23 +0100 Subject: [PATCH 14/14] fix during CI Signed-off-by: tobiasKaminsky --- .../owncloud/android/datamodel/FileDataStorageManager.java | 1 - .../com/owncloud/android/ui/fragment/OCFileListFragment.java | 1 - .../android/ui/preview/PreviewTextStringFragment.java | 5 +---- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java index 39389a7d03a2..1547aab1bcf3 100644 --- a/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -39,7 +39,6 @@ import com.google.gson.JsonSyntaxException; import com.owncloud.android.MainApp; import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; -import com.owncloud.android.lib.common.DirectEditing; import com.owncloud.android.lib.common.network.WebdavEntry; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; diff --git a/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index ae41b0fbb32c..77d7ddaf6564 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -100,7 +100,6 @@ import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; -import org.jetbrains.annotations.NotNull; import org.parceler.Parcels; import java.io.File; diff --git a/src/main/java/com/owncloud/android/ui/preview/PreviewTextStringFragment.java b/src/main/java/com/owncloud/android/ui/preview/PreviewTextStringFragment.java index ab426791627f..4a4912f95413 100644 --- a/src/main/java/com/owncloud/android/ui/preview/PreviewTextStringFragment.java +++ b/src/main/java/com/owncloud/android/ui/preview/PreviewTextStringFragment.java @@ -51,11 +51,8 @@ import androidx.core.view.MenuItemCompat; public class PreviewTextStringFragment extends PreviewTextFragment { - private static final String TAG = PreviewTextStringFragment.class.getSimpleName(); private static final String EXTRA_FILE = "FILE"; - private FloatingActionButton mFabMain; - @Inject UserAccountManager accountManager; /** @@ -107,7 +104,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, throw new RuntimeException("View may not be null"); } - mFabMain = view.findViewById(R.id.text_preview_fab); + FloatingActionButton mFabMain = view.findViewById(R.id.text_preview_fab); mFabMain.setVisibility(View.VISIBLE); mFabMain.setEnabled(true); mFabMain.setOnClickListener(v -> edit());