From 59585d0f5b8f013a7836c2bc5cf958108cea2bc2 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Fri, 1 Nov 2019 12:09:08 +0100 Subject: [PATCH 1/3] Direct editing support - abstract EditorWebView - support direct editing endpoint Create new files via direct editing Signed-off-by: tobiasKaminsky --- settings.gradle | 2 +- .../com/nextcloud/client/appinfo/AppInfo.java | 3 + .../nextcloud/client/appinfo/AppInfoImpl.java | 21 ++ .../com/nextcloud/client/device/DeviceInfo.kt | 5 + .../nextcloud/client/di/ComponentsModule.java | 4 + .../com/owncloud/android/db/ProviderMeta.java | 2 +- .../android/files/FetchTemplateOperation.java | 9 +- .../android/files/FileMenuFilter.java | 162 +++++---- .../providers/FileContentProvider.java | 16 +- .../android/ui/activity/EditorWebView.java | 74 ++-- .../activity/RichDocumentsEditorWebView.java | 59 +-- .../android/ui/activity/TextEditorWebView.kt | 47 +-- .../android/ui/adapter/OCFileListAdapter.java | 10 +- .../adapter/RichDocumentsTemplateAdapter.java | 155 ++++++++ .../android/ui/adapter/TemplateAdapter.java | 51 +-- .../asynctasks/RichDocumentsLoadUrlTask.java | 74 ++++ ...rlTask.java => TextEditorLoadUrlTask.java} | 32 +- ...seRichDocumentsTemplateDialogFragment.java | 339 ++++++++++++++++++ .../dialog/ChooseTemplateDialogFragment.java | 169 ++++++--- .../ui/fragment/FileDetailFragment.java | 6 +- .../OCFileListBottomSheetActions.java | 7 + .../fragment/OCFileListBottomSheetDialog.java | 48 ++- .../ui/fragment/OCFileListFragment.java | 47 ++- .../ui/preview/PreviewImageFragment.java | 12 +- .../ui/preview/PreviewMediaFragment.java | 6 +- .../ui/preview/PreviewTextFileFragment.java | 4 +- .../ui/preview/PreviewTextFragment.java | 4 +- ...file_list_actions_bottom_sheet_creator.xml | 51 +++ ...ile_list_actions_bottom_sheet_fragment.xml | 18 + src/main/res/values/strings.xml | 1 + 30 files changed, 1097 insertions(+), 341 deletions(-) create mode 100644 src/main/java/com/owncloud/android/ui/adapter/RichDocumentsTemplateAdapter.java create mode 100644 src/main/java/com/owncloud/android/ui/asynctasks/RichDocumentsLoadUrlTask.java rename src/main/java/com/owncloud/android/ui/asynctasks/{LoadUrlTask.java => TextEditorLoadUrlTask.java} (68%) create mode 100644 src/main/java/com/owncloud/android/ui/dialog/ChooseRichDocumentsTemplateDialogFragment.java create mode 100644 src/main/res/layout/file_list_actions_bottom_sheet_creator.xml 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/java/com/nextcloud/client/appinfo/AppInfo.java b/src/main/java/com/nextcloud/client/appinfo/AppInfo.java index 3e8049e53364..e3588316576a 100644 --- a/src/main/java/com/nextcloud/client/appinfo/AppInfo.java +++ b/src/main/java/com/nextcloud/client/appinfo/AppInfo.java @@ -19,6 +19,8 @@ */ package com.nextcloud.client.appinfo; +import android.content.Context; + /** * This class provides general, static information about application * build. @@ -36,4 +38,5 @@ public interface AppInfo { boolean isDebugBuild(); + String getAppVersion(Context context); } diff --git a/src/main/java/com/nextcloud/client/appinfo/AppInfoImpl.java b/src/main/java/com/nextcloud/client/appinfo/AppInfoImpl.java index 747bf8b3a868..75aa07fd605e 100644 --- a/src/main/java/com/nextcloud/client/appinfo/AppInfoImpl.java +++ b/src/main/java/com/nextcloud/client/appinfo/AppInfoImpl.java @@ -19,7 +19,12 @@ */ package com.nextcloud.client.appinfo; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; + import com.owncloud.android.BuildConfig; +import com.owncloud.android.lib.common.utils.Log_OC; class AppInfoImpl implements AppInfo { @@ -32,4 +37,20 @@ public String getFormattedVersionCode() { public boolean isDebugBuild() { return BuildConfig.DEBUG; } + + @Override + public String getAppVersion(Context context) { + try { + PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + if (pInfo != null) { + return pInfo.versionName; + } else { + return "n/a"; + } + } catch (PackageManager.NameNotFoundException e) { + Log_OC.e(this, "Trying to get packageName", e.getCause()); + + return "n/a"; + } + } } diff --git a/src/main/java/com/nextcloud/client/device/DeviceInfo.kt b/src/main/java/com/nextcloud/client/device/DeviceInfo.kt index 68d70b90929c..a37e41a7dddf 100644 --- a/src/main/java/com/nextcloud/client/device/DeviceInfo.kt +++ b/src/main/java/com/nextcloud/client/device/DeviceInfo.kt @@ -29,8 +29,13 @@ import java.util.Locale class DeviceInfo { val vendor: String = Build.MANUFACTURER.toLowerCase(Locale.ROOT) val apiLevel: Int = Build.VERSION.SDK_INT + val androidVersion = Build.VERSION.RELEASE fun hasCamera(context: Context): Boolean { return context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA) } + + fun editorSupported(): Boolean { + return apiLevel < Build.VERSION_CODES.LOLLIPOP + } } diff --git a/src/main/java/com/nextcloud/client/di/ComponentsModule.java b/src/main/java/com/nextcloud/client/di/ComponentsModule.java index e5c44dbf5cc1..64cbf9db57bb 100644 --- a/src/main/java/com/nextcloud/client/di/ComponentsModule.java +++ b/src/main/java/com/nextcloud/client/di/ComponentsModule.java @@ -63,6 +63,7 @@ import com.owncloud.android.ui.activity.UploadFilesActivity; import com.owncloud.android.ui.activity.UploadListActivity; import com.owncloud.android.ui.activity.UserInfoActivity; +import com.owncloud.android.ui.dialog.ChooseRichDocumentsTemplateDialogFragment; import com.owncloud.android.ui.dialog.ChooseTemplateDialogFragment; import com.owncloud.android.ui.dialog.MultipleAccountsDialog; import com.owncloud.android.ui.fragment.ExtendedListFragment; @@ -136,6 +137,9 @@ abstract class ComponentsModule { @ContributesAndroidInjector abstract FileDetailActivitiesFragment fileDetailActivitiesFragment(); @ContributesAndroidInjector abstract FileDetailSharingFragment fileDetailSharingFragment(); @ContributesAndroidInjector abstract ChooseTemplateDialogFragment chooseTemplateDialogFragment(); + + @ContributesAndroidInjector + abstract ChooseRichDocumentsTemplateDialogFragment chooseRichDocumentsTemplateDialogFragment(); @ContributesAndroidInjector abstract PreviewImageFragment previewImageFragment(); @ContributesAndroidInjector abstract ContactListFragment chooseContactListFragment(); @ContributesAndroidInjector abstract PreviewMediaFragment previewMediaFragment(); diff --git a/src/main/java/com/owncloud/android/db/ProviderMeta.java b/src/main/java/com/owncloud/android/db/ProviderMeta.java index fb488528fbf4..b5e9d3e8b636 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 = 52; + public static final int DB_VERSION = 53; private ProviderMeta() { // No instance diff --git a/src/main/java/com/owncloud/android/files/FetchTemplateOperation.java b/src/main/java/com/owncloud/android/files/FetchTemplateOperation.java index 3c8abad3c315..ba7ec2455d93 100644 --- a/src/main/java/com/owncloud/android/files/FetchTemplateOperation.java +++ b/src/main/java/com/owncloud/android/files/FetchTemplateOperation.java @@ -25,7 +25,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.ui.dialog.ChooseTemplateDialogFragment; +import com.owncloud.android.ui.dialog.ChooseRichDocumentsTemplateDialogFragment; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.methods.GetMethod; @@ -41,14 +41,14 @@ public class FetchTemplateOperation extends RemoteOperation { private static final int SYNC_CONNECTION_TIMEOUT = 5000; private static final String TEMPLATE_URL = "/ocs/v2.php/apps/richdocuments/api/v1/templates/"; - private ChooseTemplateDialogFragment.Type type; + private ChooseRichDocumentsTemplateDialogFragment.Type type; // JSON node names private static final String NODE_OCS = "ocs"; private static final String NODE_DATA = "data"; private static final String JSON_FORMAT = "?format=json"; - public FetchTemplateOperation(ChooseTemplateDialogFragment.Type type) { + public FetchTemplateOperation(ChooseRichDocumentsTemplateDialogFragment.Type type) { this.type = type; } @@ -81,7 +81,8 @@ protected RemoteOperationResult run(OwnCloudClient client) { templateArray.add(new Template(templateObject.getInt("id"), templateObject.getString("name"), templateObject.optString("preview"), - Template.Type.valueOf(templateObject.getString("type")), + Template.Type.valueOf(templateObject.getString("type") + .toUpperCase(Locale.ROOT)), templateObject.getString("extension"))); } diff --git a/src/main/java/com/owncloud/android/files/FileMenuFilter.java b/src/main/java/com/owncloud/android/files/FileMenuFilter.java index d14788df03fb..9d2d8d97ff7a 100644 --- a/src/main/java/com/owncloud/android/files/FileMenuFilter.java +++ b/src/main/java/com/owncloud/android/files/FileMenuFilter.java @@ -29,6 +29,8 @@ import android.view.MenuItem; import com.google.gson.Gson; +import com.nextcloud.client.account.User; +import com.nextcloud.client.device.DeviceInfo; import com.owncloud.android.R; import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.OCFile; @@ -58,45 +60,62 @@ public class FileMenuFilter { private static final int SINGLE_SELECT_ITEMS = 1; - private int mNumberOfAllFiles; - private Collection mFiles; - private ComponentsGetter mComponentsGetter; - private Account mAccount; - private Context mContext; - private boolean mOverflowMenu; + private int numberOfAllFiles; + private Collection files; + private ComponentsGetter componentsGetter; + private Account account; + private Context context; + private boolean overflowMenu; + private DeviceInfo deviceInfo; + private User user; /** * Constructor * * @param numberOfAllFiles Number of all displayed files - * @param targetFiles Collection of {@link OCFile} file targets of the action to filter in the {@link Menu}. + * @param files Collection of {@link OCFile} file targets of the action to filter in the {@link Menu}. * @param account ownCloud {@link Account} holding targetFile. - * @param cg Accessor to app components, needed to access synchronization services + * @param componentsGetter Accessor to app components, needed to access synchronization services * @param context Android {@link Context}, needed to access build setup resources. * @param overflowMenu true if the overflow menu items are being filtered */ - public FileMenuFilter(int numberOfAllFiles, Collection targetFiles, Account account, - ComponentsGetter cg, Context context, boolean overflowMenu) { - mNumberOfAllFiles = numberOfAllFiles; - mFiles = targetFiles; - mAccount = account; - mComponentsGetter = cg; - mContext = context; - mOverflowMenu = overflowMenu; + public FileMenuFilter(int numberOfAllFiles, + Collection files, + Account account, + ComponentsGetter componentsGetter, + Context context, + boolean overflowMenu, + DeviceInfo deviceInfo, + User user + ) { + this.numberOfAllFiles = numberOfAllFiles; + this.files = files; + this.account = account; + this.componentsGetter = componentsGetter; + this.context = context; + this.overflowMenu = overflowMenu; + this.deviceInfo = deviceInfo; + this.user = user; } /** * Constructor * - * @param targetFile {@link OCFile} target of the action to filter in the {@link Menu}. + * @param file {@link OCFile} target of the action to filter in the {@link Menu}. * @param account ownCloud {@link Account} holding targetFile. - * @param cg Accessor to app components, needed to access synchronization services + * @param componentsGetter Accessor to app components, needed to access synchronization services * @param context Android {@link Context}, needed to access build setup resources. * @param overflowMenu true if the overflow menu items are being filtered */ - public FileMenuFilter(OCFile targetFile, Account account, ComponentsGetter cg, Context context, - boolean overflowMenu) { - this(1, Collections.singletonList(targetFile), account, cg, context, overflowMenu); + public FileMenuFilter(OCFile file, + Account account, + ComponentsGetter componentsGetter, + Context context, + boolean overflowMenu, + DeviceInfo deviceInfo, + User user + ) { + this(1, Collections.singletonList(file), account, componentsGetter, context, overflowMenu, deviceInfo, user); } /** @@ -108,7 +127,7 @@ public FileMenuFilter(OCFile targetFile, Account account, ComponentsGetter cg, C * @param isMediaSupported True is media playback is supported for this user */ public void filter(Menu menu, boolean inSingleFileFragment, boolean isMediaSupported) { - if (mFiles == null || mFiles.isEmpty()) { + if (files == null || files.isEmpty()) { hideAll(menu); } else { List toShow = new ArrayList<>(); @@ -177,7 +196,7 @@ private void filter(List toShow, boolean isMediaSupported, Menu menu) { boolean synchronizing = anyFileSynchronizing(); - OCCapability capability = mComponentsGetter.getStorageManager().getCapability(mAccount.name); + OCCapability capability = componentsGetter.getStorageManager().getCapability(account.name); boolean endToEndEncryptionEnabled = capability.getEndToEndEncryption().isTrue(); filterEdit(toShow, toHide, capability); @@ -202,8 +221,8 @@ private void filter(List toShow, private void filterShareFile(List toShow, List toHide, OCCapability capability) { if (containsEncryptedFile() || (!isShareViaLinkAllowed() && !isShareWithUsersAllowed()) || - !isSingleSelection() || !isShareApiEnabled(capability) || !mFiles.iterator().next().canReshare() - || mOverflowMenu) { + !isSingleSelection() || !isShareApiEnabled(capability) || !files.iterator().next().canReshare() + || overflowMenu) { toHide.add(R.id.action_send_share_file); } else { toShow.add(R.id.action_send_share_file); @@ -219,7 +238,7 @@ private void filterDetails(List toShow, List toHide) { } private void filterFavorite(List toShow, List toHide, boolean synchronizing) { - if (mFiles.isEmpty() || synchronizing || allFavorites()) { + if (files.isEmpty() || synchronizing || allFavorites()) { toHide.add(R.id.action_favorite); } else { toShow.add(R.id.action_favorite); @@ -227,7 +246,7 @@ private void filterFavorite(List toShow, List toHide, boolean } private void filterUnfavorite(List toShow, List toHide, boolean synchronizing) { - if (mFiles.isEmpty() || synchronizing || allNotFavorites()) { + if (files.isEmpty() || synchronizing || allNotFavorites()) { toHide.add(R.id.action_unset_favorite); } else { toShow.add(R.id.action_unset_favorite); @@ -235,7 +254,7 @@ private void filterUnfavorite(List toShow, List toHide, boolea } private void filterEncrypt(List toShow, List toHide, boolean endToEndEncryptionEnabled) { - if (mFiles.isEmpty() || !isSingleSelection() || isSingleFile() || isEncryptedFolder() + if (files.isEmpty() || !isSingleSelection() || isSingleFile() || isEncryptedFolder() || !endToEndEncryptionEnabled) { toHide.add(R.id.action_encrypted); } else { @@ -244,7 +263,7 @@ private void filterEncrypt(List toShow, List toHide, boolean e } private void filterUnsetEncrypted(List toShow, List toHide, boolean endToEndEncryptionEnabled) { - if (mFiles.isEmpty() || !isSingleSelection() || isSingleFile() || !isEncryptedFolder() + if (files.isEmpty() || !isSingleSelection() || isSingleFile() || !isEncryptedFolder() || !endToEndEncryptionEnabled) { toHide.add(R.id.action_unset_encrypted); } else { @@ -253,7 +272,7 @@ private void filterUnsetEncrypted(List toShow, List toHide, bo } private void filterSetPictureAs(List toShow, List toHide) { - if (isSingleImage() && !MimeTypeUtil.isSVG(mFiles.iterator().next())) { + if (isSingleImage() && !MimeTypeUtil.isSVG(files.iterator().next())) { toShow.add(R.id.action_set_as_wallpaper); } else { toHide.add(R.id.action_set_as_wallpaper); @@ -264,15 +283,15 @@ private void filterEdit(List toShow, List toHide, OCCapability capability ) { - if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + if (deviceInfo.editorSupported()) { toHide.add(R.id.action_edit); return; } - String mimeType = mFiles.iterator().next().getMimeType(); + String mimeType = files.iterator().next().getMimeType(); - if (isRichDocumentEditingSupported(capability, mimeType) || isEditorAvailable(mContext.getContentResolver(), - mAccount, + if (isRichDocumentEditingSupported(capability, mimeType) || isEditorAvailable(context.getContentResolver(), + user, mimeType)) { toShow.add(R.id.action_edit); } else { @@ -280,13 +299,14 @@ private void filterEdit(List toShow, } } - public static boolean isEditorAvailable(ContentResolver contentResolver, Account account, String mimeType) { - return getEditor(contentResolver, account, mimeType) != null; + public static boolean isEditorAvailable(ContentResolver contentResolver, User user, String mimeType) { + return getEditor(contentResolver, user, mimeType) != null; } @Nullable - public static Editor getEditor(ContentResolver contentResolver, Account account, String mimeType) { - String json = new ArbitraryDataProvider(contentResolver).getValue(account, ArbitraryDataProvider.DIRECT_EDITING); + public static Editor getEditor(ContentResolver contentResolver, User user, String mimeType) { + String json = new ArbitraryDataProvider(contentResolver).getValue(user.toPlatformAccount(), + ArbitraryDataProvider.DIRECT_EDITING); if (json.isEmpty()) { return null; @@ -315,7 +335,7 @@ private boolean isRichDocumentEditingSupported(OCCapability capability, String m } private void filterSync(List toShow, List toHide, boolean synchronizing) { - if (mFiles.isEmpty() || (!anyFileDown() && !containsFolder()) || synchronizing) { + if (files.isEmpty() || (!anyFileDown() && !containsFolder()) || synchronizing) { toHide.add(R.id.action_sync_file); } else { toShow.add(R.id.action_sync_file); @@ -323,7 +343,7 @@ private void filterSync(List toShow, List toHide, boolean sync } private void filterCancelSync(List toShow, List toHide, boolean synchronizing) { - if (mFiles.isEmpty() || !synchronizing) { + if (files.isEmpty() || !synchronizing) { toHide.add(R.id.action_cancel_sync); } else { toShow.add(R.id.action_cancel_sync); @@ -344,7 +364,7 @@ private void filterDeselectAll(List toShow, List toHide, boole toHide.add(R.id.action_deselect_all_action_menu); } else { // Show only if at least one item is selected. - if (mFiles.isEmpty() || mOverflowMenu) { + if (files.isEmpty() || overflowMenu) { toHide.add(R.id.action_deselect_all_action_menu); } else { toShow.add(R.id.action_deselect_all_action_menu); @@ -355,7 +375,7 @@ private void filterDeselectAll(List toShow, List toHide, boole private void filterSelectAll(List toShow, List toHide, boolean inSingleFileFragment) { if (!inSingleFileFragment) { // Show only if at least one item isn't selected. - if (mFiles.size() >= mNumberOfAllFiles || mOverflowMenu) { + if (files.size() >= numberOfAllFiles || overflowMenu) { toHide.add(R.id.action_select_all_action_menu); } else { toShow.add(R.id.action_select_all_action_menu); @@ -367,7 +387,7 @@ private void filterSelectAll(List toShow, List toHide, boolean } private void filterRemove(List toShow, List toHide, boolean synchronizing) { - if (mFiles.isEmpty() || synchronizing || containsEncryptedFolder()) { + if (files.isEmpty() || synchronizing || containsEncryptedFolder()) { toHide.add(R.id.action_remove_file); } else { toShow.add(R.id.action_remove_file); @@ -375,7 +395,7 @@ private void filterRemove(List toShow, List toHide, boolean sy } private void filterMoveCopy(List toShow, List toHide, boolean synchronizing) { - if (mFiles.isEmpty() || synchronizing || containsEncryptedFile() || containsEncryptedFolder()) { + if (files.isEmpty() || synchronizing || containsEncryptedFile() || containsEncryptedFolder()) { toHide.add(R.id.action_move); toHide.add(R.id.action_copy); } else { @@ -393,7 +413,7 @@ private void filterRename(List toShow, List toHide, boolean sy } private void filterDownload(List toShow, List toHide, boolean synchronizing) { - if (mFiles.isEmpty() || containsFolder() || anyFileDown() || synchronizing) { + if (files.isEmpty() || containsFolder() || anyFileDown() || synchronizing) { toHide.add(R.id.action_download_file); } else { toShow.add(R.id.action_download_file); @@ -401,7 +421,7 @@ private void filterDownload(List toShow, List toHide, boolean } private void filterStream(List toShow, List toHide, boolean isMediaSupported) { - if (mFiles.isEmpty() || !isSingleFile() || !isSingleMedia() || !isMediaSupported) { + if (files.isEmpty() || !isSingleFile() || !isSingleMedia() || !isMediaSupported) { toHide.add(R.id.action_stream_media); } else { toShow.add(R.id.action_stream_media); @@ -410,10 +430,10 @@ private void filterStream(List toShow, List toHide, boolean is private boolean anyFileSynchronizing() { boolean synchronizing = false; - if (mComponentsGetter != null && !mFiles.isEmpty() && mAccount != null) { - OperationsServiceBinder opsBinder = mComponentsGetter.getOperationsServiceBinder(); - FileUploaderBinder uploaderBinder = mComponentsGetter.getFileUploaderBinder(); - FileDownloaderBinder downloaderBinder = mComponentsGetter.getFileDownloaderBinder(); + if (componentsGetter != null && !files.isEmpty() && account != null) { + OperationsServiceBinder opsBinder = componentsGetter.getOperationsServiceBinder(); + FileUploaderBinder uploaderBinder = componentsGetter.getFileUploaderBinder(); + FileDownloaderBinder downloaderBinder = componentsGetter.getFileDownloaderBinder(); synchronizing = anyFileSynchronizing(opsBinder) || // comparing local and remote anyFileDownloading(downloaderBinder) || anyFileUploading(uploaderBinder); @@ -424,8 +444,8 @@ private boolean anyFileSynchronizing() { private boolean anyFileSynchronizing(OperationsServiceBinder opsBinder) { boolean synchronizing = false; if (opsBinder != null) { - for (Iterator iterator = mFiles.iterator(); !synchronizing && iterator.hasNext(); ) { - synchronizing = opsBinder.isSynchronizing(mAccount, iterator.next()); + for (Iterator iterator = files.iterator(); !synchronizing && iterator.hasNext(); ) { + synchronizing = opsBinder.isSynchronizing(account, iterator.next()); } } return synchronizing; @@ -434,8 +454,8 @@ private boolean anyFileSynchronizing(OperationsServiceBinder opsBinder) { private boolean anyFileDownloading(FileDownloaderBinder downloaderBinder) { boolean downloading = false; if (downloaderBinder != null) { - for (Iterator iterator = mFiles.iterator(); !downloading && iterator.hasNext(); ) { - downloading = downloaderBinder.isDownloading(mAccount, iterator.next()); + for (Iterator iterator = files.iterator(); !downloading && iterator.hasNext(); ) { + downloading = downloaderBinder.isDownloading(account, iterator.next()); } } return downloading; @@ -444,8 +464,8 @@ private boolean anyFileDownloading(FileDownloaderBinder downloaderBinder) { private boolean anyFileUploading(FileUploaderBinder uploaderBinder) { boolean uploading = false; if (uploaderBinder != null) { - for (Iterator iterator = mFiles.iterator(); !uploading && iterator.hasNext(); ) { - uploading = uploaderBinder.isUploading(mAccount, iterator.next()); + for (Iterator iterator = files.iterator(); !uploading && iterator.hasNext(); ) { + uploading = uploaderBinder.isUploading(account, iterator.next()); } } return uploading; @@ -459,26 +479,26 @@ private boolean isShareApiEnabled(OCCapability capability) { } private boolean isShareWithUsersAllowed() { - return mContext != null && - mContext.getResources().getBoolean(R.bool.share_with_users_feature); + return context != null && + context.getResources().getBoolean(R.bool.share_with_users_feature); } private boolean isShareViaLinkAllowed() { - return mContext != null && - mContext.getResources().getBoolean(R.bool.share_via_link_feature); + return context != null && + context.getResources().getBoolean(R.bool.share_via_link_feature); } private boolean isSingleSelection() { - return mFiles.size() == SINGLE_SELECT_ITEMS; + return files.size() == SINGLE_SELECT_ITEMS; } private boolean isSingleFile() { - return isSingleSelection() && !mFiles.iterator().next().isFolder(); + return isSingleSelection() && !files.iterator().next().isFolder(); } private boolean isEncryptedFolder() { if (isSingleSelection()) { - OCFile file = mFiles.iterator().next(); + OCFile file = files.iterator().next(); return file.isFolder() && file.isEncrypted(); } else { @@ -487,16 +507,16 @@ private boolean isEncryptedFolder() { } private boolean isSingleImage() { - return isSingleSelection() && MimeTypeUtil.isImage(mFiles.iterator().next()); + return isSingleSelection() && MimeTypeUtil.isImage(files.iterator().next()); } private boolean isSingleMedia() { - OCFile file = mFiles.iterator().next(); + OCFile file = files.iterator().next(); return isSingleSelection() && (MimeTypeUtil.isVideo(file) || MimeTypeUtil.isAudio(file)); } private boolean containsEncryptedFile() { - for (OCFile file : mFiles) { + for (OCFile file : files) { if (!file.isFolder() && file.isEncrypted()) { return true; } @@ -505,7 +525,7 @@ private boolean containsEncryptedFile() { } private boolean containsEncryptedFolder() { - for (OCFile file : mFiles) { + for (OCFile file : files) { if (file.isFolder() && file.isEncrypted()) { return true; } @@ -514,7 +534,7 @@ private boolean containsEncryptedFolder() { } private boolean containsFolder() { - for (OCFile file : mFiles) { + for (OCFile file : files) { if (file.isFolder()) { return true; } @@ -523,7 +543,7 @@ private boolean containsFolder() { } private boolean anyFileDown() { - for (OCFile file : mFiles) { + for (OCFile file : files) { if (file.isDown()) { return true; } @@ -532,7 +552,7 @@ private boolean anyFileDown() { } private boolean allFavorites() { - for (OCFile file : mFiles) { + for (OCFile file : files) { if (!file.isFavorite()) { return false; } @@ -541,7 +561,7 @@ private boolean allFavorites() { } private boolean allNotFavorites() { - for (OCFile file : mFiles) { + for (OCFile file : files) { if (file.isFavorite()) { return false; } diff --git a/src/main/java/com/owncloud/android/providers/FileContentProvider.java b/src/main/java/com/owncloud/android/providers/FileContentProvider.java index e9292b62dff8..bf3c8a9099aa 100644 --- a/src/main/java/com/owncloud/android/providers/FileContentProvider.java +++ b/src/main/java/com/owncloud/android/providers/FileContentProvider.java @@ -2085,8 +2085,8 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion)); } - if (oldVersion < 52 && newVersion >= 52) { - Log_OC.i(SQL, "Entering in the #52 add rich workspace to file table"); + if (oldVersion < 53 && newVersion >= 53) { + Log_OC.i(SQL, "Entering in the #53 add rich workspace to file table"); db.beginTransaction(); try { db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + @@ -2103,17 +2103,5 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion)); } } - - @Override - public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (oldVersion == 25 && newVersion == 24) { - db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + - REMOVE_COLUMN + ProviderTableMeta.FILE_IS_ENCRYPTED); - db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + - REMOVE_COLUMN + ProviderTableMeta.FILE_ENCRYPTED_NAME); - db.execSQL(ALTER_TABLE + ProviderTableMeta.CAPABILITIES_TABLE_NAME + - REMOVE_COLUMN + ProviderTableMeta.CAPABILITIES_END_TO_END_ENCRYPTION); - } - } } } 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 dbbaf9c5a369..906533776be8 100644 --- a/src/main/java/com/owncloud/android/ui/activity/EditorWebView.java +++ b/src/main/java/com/owncloud/android/ui/activity/EditorWebView.java @@ -27,7 +27,6 @@ import android.graphics.Bitmap; import android.os.Bundle; import android.os.Handler; -import android.text.TextUtils; import android.view.View; import android.webkit.JavascriptInterface; import android.widget.ImageView; @@ -39,8 +38,6 @@ import com.owncloud.android.R; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.ThumbnailsCacheManager; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.ui.asynctasks.LoadUrlTask; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.MimeTypeUtil; import com.owncloud.android.utils.ThemeUtils; @@ -53,32 +50,30 @@ public abstract class EditorWebView extends ExternalSiteWebView { @Getter @Setter protected Snackbar loadingSnackbar; - protected OCFile file; + + protected String fileName; + protected String mimeType; @BindView(R.id.progressBar2) ProgressBar progressBar; @BindView(R.id.thumbnail) - ImageView thumbnail; + ImageView thumbnailView; @BindView(R.id.filename) - TextView fileName; + TextView fileNameTextView; private Unbinder unbinder; private static final String TAG = EditorWebView.class.getSimpleName(); - protected void loadUrl(String url, OCFile file) { - if (TextUtils.isEmpty(url)) { - new LoadUrlTask(this, getAccount(), file).execute(); - } else { - webview.loadUrl(url); - } + protected void loadUrl(String url) { + webview.loadUrl(url); } protected void hideLoading() { - thumbnail.setVisibility(View.GONE); - fileName.setVisibility(View.GONE); + thumbnailView.setVisibility(View.GONE); + fileNameTextView.setVisibility(View.GONE); progressBar.setVisibility(View.GONE); webview.setVisibility(View.VISIBLE); @@ -127,19 +122,29 @@ protected void onCreate(Bundle savedInstanceState) { unbinder = ButterKnife.bind(this); - file = getIntent().getParcelableExtra(ExternalSiteWebView.EXTRA_FILE); + setFile(getIntent().getParcelableExtra(ExternalSiteWebView.EXTRA_FILE)); + + if (getFile() == null) { + Toast.makeText(getApplicationContext(), + R.string.richdocuments_failed_to_load_document, Toast.LENGTH_LONG).show(); + finish(); + } + + if (getFile() != null) { + fileName = getFile().getFileName(); + } initLoadingScreen(); } protected void initLoadingScreen() { - setThumbnail(file, thumbnail); - fileName.setText(file.getFileName()); + setThumbnailView(); + fileNameTextView.setText(fileName); } private void openShareDialog() { Intent intent = new Intent(this, ShareActivity.class); - intent.putExtra(FileActivity.EXTRA_FILE, file); + intent.putExtra(FileActivity.EXTRA_FILE, getFile()); intent.putExtra(FileActivity.EXTRA_ACCOUNT, getAccount()); startActivity(intent); } @@ -152,12 +157,15 @@ protected void onDestroy() { super.onDestroy(); } - protected void setThumbnail(OCFile file, ImageView thumbnailView) { + protected void setThumbnailView() { // Todo minimize: only icon by mimetype - + OCFile file = getFile(); if (file.isFolder()) { thumbnailView.setImageDrawable(MimeTypeUtil.getFolderTypeIcon(file.isSharedWithMe() || - file.isSharedWithSharee(), file.isSharedViaLink(), file.isEncrypted(), file.getMountType(), + file.isSharedWithSharee(), + file.isSharedViaLink(), + file.isEncrypted(), + file.getMountType(), this)); } else { if ((MimeTypeUtil.isImage(file) || MimeTypeUtil.isVideo(file)) && file.getRemoteId() != null) { @@ -172,30 +180,6 @@ protected void setThumbnail(OCFile file, ImageView thumbnailView) { } 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())) { 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 db5e40c7667f..8b05a3acb0ab 100644 --- a/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.java +++ b/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.java @@ -32,6 +32,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.text.TextUtils; import android.view.KeyEvent; import android.webkit.JavascriptInterface; import android.webkit.ValueCallback; @@ -39,26 +40,23 @@ import android.webkit.WebView; import android.widget.Toast; -import com.bumptech.glide.Glide; import com.nextcloud.client.account.CurrentAccountProvider; import com.nextcloud.client.account.User; import com.nextcloud.client.network.ClientFactory; import com.owncloud.android.R; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.datamodel.Template; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.operations.RichDocumentsCreateAssetOperation; import com.owncloud.android.ui.asynctasks.PrintAsyncTask; +import com.owncloud.android.ui.asynctasks.RichDocumentsLoadUrlTask; import com.owncloud.android.ui.fragment.OCFileListFragment; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.FileStorageUtils; -import com.owncloud.android.utils.glide.CustomGlideStreamLoader; import org.json.JSONException; import org.json.JSONObject; -import org.parceler.Parcels; import java.io.File; import java.lang.ref.WeakReference; @@ -130,49 +128,9 @@ public boolean onShowFileChooser(WebView webView, ValueCallback filePathC }); // load url in background - loadUrl(getIntent().getStringExtra(EXTRA_URL), file); + loadUrl(getIntent().getStringExtra(EXTRA_URL)); } - @Override - protected void initLoadingScreen() { - if (file == null) { - fileName.setText(R.string.create_file_from_template); - - Template template = Parcels.unwrap(getIntent().getParcelableExtra(EXTRA_TEMPLATE)); - - int placeholder; - - switch (template.getType()) { - case DOCUMENT: - placeholder = R.drawable.file_doc; - break; - - case SPREADSHEET: - placeholder = R.drawable.file_xls; - break; - - case PRESENTATION: - placeholder = R.drawable.file_ppt; - break; - - default: - placeholder = R.drawable.file; - break; - } - - Glide.with(this).using(new CustomGlideStreamLoader(currentAccountProvider, clientFactory)) - .load(template.getThumbnailLink()) - .placeholder(placeholder) - .error(placeholder) - .into(thumbnail); - } else { - setThumbnail(file, thumbnail); - fileName.setText(file.getFileName()); - } - } - - - @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); @@ -292,6 +250,15 @@ private void downloadFile(Uri url) { downloadmanager.enqueue(request); } + @Override + protected void loadUrl(String url) { + if (TextUtils.isEmpty(url)) { + new RichDocumentsLoadUrlTask(this, getUser().get(), getFile()).execute(); + } else { + super.loadUrl(url); + } + } + private class RichDocumentsMobileInterface extends MobileInterface { @JavascriptInterface public void insertGraphic() { @@ -328,7 +295,7 @@ public void fileRename(String renameString) { try { JSONObject renameJson = new JSONObject(renameString); String newName = renameJson.getString(NEW_NAME); - file.setFileName(newName); + getFile().setFileName(newName); } catch (JSONException e) { Log_OC.e(this, "Failed to parse rename json message: " + e); } 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 a7d707f081b1..a715e3d3e49a 100644 --- a/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt +++ b/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt @@ -21,24 +21,33 @@ package com.owncloud.android.ui.activity -import android.annotation.SuppressLint -import android.content.pm.PackageManager.NameNotFoundException import android.os.Build import android.os.Bundle +import android.widget.Toast import androidx.annotation.RequiresApi +import com.nextcloud.client.appinfo.AppInfo +import com.nextcloud.client.device.DeviceInfo import com.owncloud.android.R import com.owncloud.android.files.FileMenuFilter -import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.ui.asynctasks.TextEditorLoadUrlTask +import javax.inject.Inject @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) class TextEditorWebView : EditorWebView() { + @Inject + lateinit var appInfo: AppInfo + @Inject + lateinit var deviceInfo: DeviceInfo - @SuppressLint("AddJavascriptInterface") - // suppress warning as webview is only used >= Lollipop override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val editor = FileMenuFilter.getEditor(contentResolver, account, file.mimeType) + if (!user.isPresent) { + Toast.makeText(this, getString(R.string.failed_to_start_editor), Toast.LENGTH_LONG).show() + finish() + } + + val editor = FileMenuFilter.getEditor(contentResolver, user.get(), file.mimeType) if (editor != null && editor.id == "onlyoffice") { webview.settings.userAgentString = generateOnlyOfficeUserAgent() @@ -46,22 +55,20 @@ class TextEditorWebView : EditorWebView() { webview.addJavascriptInterface(MobileInterface(), "DirectEditingMobileInterface") - loadUrl(intent.getStringExtra(ExternalSiteWebView.EXTRA_URL), file) + loadUrl(intent.getStringExtra(ExternalSiteWebView.EXTRA_URL)) } - private fun generateOnlyOfficeUserAgent(): String { - val appString = applicationContext.resources.getString(R.string.only_office_user_agent) - val packageName = applicationContext.packageName - val androidVersion = Build.VERSION.RELEASE - var appVersion = "" - try { - val pInfo = applicationContext.packageManager.getPackageInfo(packageName, 0) - if (pInfo != null) { - appVersion = pInfo.versionName - } - } catch (e: NameNotFoundException) { - Log_OC.e(this, "Trying to get packageName", e.cause) + override fun loadUrl(url: String?) { + if (url.isNullOrEmpty()) { + TextEditorLoadUrlTask(this, user.get(), file).execute() + } else { + super.loadUrl(url) } - return String.format(appString, androidVersion, appVersion) + } + + private fun generateOnlyOfficeUserAgent(): String { + val userAgent = applicationContext.resources.getString(R.string.only_office_user_agent) + + return String.format(userAgent, deviceInfo.androidVersion, appInfo.getAppVersion(this)) } } 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 2cca0809b191..33d0b539fe40 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java @@ -673,11 +673,13 @@ private String generateFooterText(int filesCount, int foldersCount) { } public OCFile getItem(int position) { - if (shouldShowHeader()) { - return mFiles.get(position - 1); - } else { - return mFiles.get(position); + int newPosition = position; + + if (shouldShowHeader() && position > 0) { + newPosition = position - 1; } + + return mFiles.get(newPosition); } private boolean shouldShowHeader() { diff --git a/src/main/java/com/owncloud/android/ui/adapter/RichDocumentsTemplateAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/RichDocumentsTemplateAdapter.java new file mode 100644 index 000000000000..353e3a153087 --- /dev/null +++ b/src/main/java/com/owncloud/android/ui/adapter/RichDocumentsTemplateAdapter.java @@ -0,0 +1,155 @@ +/* + * + * Nextcloud Android client application + * + * @author Tobias Kaminsky + * Copyright (C) 2019 Tobias Kaminsky + * Copyright (C) 2019 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.owncloud.android.ui.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.nextcloud.client.account.CurrentAccountProvider; +import com.nextcloud.client.network.ClientFactory; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.Template; +import com.owncloud.android.ui.dialog.ChooseRichDocumentsTemplateDialogFragment; +import com.owncloud.android.utils.NextcloudServer; +import com.owncloud.android.utils.glide.CustomGlideStreamLoader; + +import java.util.ArrayList; +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import butterknife.BindView; +import butterknife.ButterKnife; + +/** + * Adapter for handling Templates, used to create files out of it via RichDocuments app + */ +public class RichDocumentsTemplateAdapter extends RecyclerView.Adapter { + + private List