Direct editing support

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

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
This commit is contained in:
tobiasKaminsky 2019-11-01 12:09:08 +01:00
parent b0758e4b74
commit 59585d0f5b
No known key found for this signature in database
GPG Key ID: 0E00D4D47D0C5AF7
30 changed files with 1095 additions and 339 deletions

View File

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

View File

@ -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);
}

View File

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

View File

@ -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
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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))
}
override fun loadUrl(url: String?) {
if (url.isNullOrEmpty()) {
TextEditorLoadUrlTask(this, user.get(), file).execute()
} else {
super.loadUrl(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)
}
return String.format(appString, androidVersion, appVersion)
val userAgent = applicationContext.resources.getString(R.string.only_office_user_agent)
return String.format(userAgent, deviceInfo.androidVersion, appInfo.getAppVersion(this))
}
}

View File

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

View File

@ -0,0 +1,155 @@
/*
*
* Nextcloud Android client application
*
* @author Tobias Kaminsky
* Copyright (C) 2019 Tobias Kaminsky
* Copyright (C) 2019 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.nextcloud.client.account.CurrentAccountProvider;
import com.nextcloud.client.network.ClientFactory;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.Template;
import com.owncloud.android.ui.dialog.ChooseRichDocumentsTemplateDialogFragment;
import com.owncloud.android.utils.NextcloudServer;
import com.owncloud.android.utils.glide.CustomGlideStreamLoader;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* Adapter for handling Templates, used to create files out of it via RichDocuments app
*/
public class RichDocumentsTemplateAdapter extends RecyclerView.Adapter<RichDocumentsTemplateAdapter.ViewHolder> {
private List<Template> templateList = new ArrayList<>();
private ClickListener clickListener;
private Context context;
private ChooseRichDocumentsTemplateDialogFragment.Type type;
private CurrentAccountProvider currentAccountProvider;
private ClientFactory clientFactory;
public RichDocumentsTemplateAdapter(
ChooseRichDocumentsTemplateDialogFragment.Type type,
ClickListener clickListener,
Context context,
CurrentAccountProvider currentAccountProvider,
ClientFactory clientFactory
) {
this.clickListener = clickListener;
this.type = type;
this.context = context;
this.currentAccountProvider = currentAccountProvider;
this.clientFactory = clientFactory;
}
@NonNull
@Override
@NextcloudServer(max = 18) // remove entire class
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.template_button, parent, false));
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.setData(templateList.get(position));
}
public void setTemplateList(List<Template> templateList) {
this.templateList = templateList;
}
@Override
public int getItemCount() {
return templateList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
@BindView(R.id.name)
public TextView name;
@BindView(R.id.thumbnail)
public ImageView thumbnail;
private Template template;
public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (clickListener != null) {
clickListener.onClick(template);
}
}
public void setData(Template template) {
this.template = template;
int placeholder;
switch (type) {
case DOCUMENT:
placeholder = R.drawable.file_doc;
break;
case SPREADSHEET:
placeholder = R.drawable.file_xls;
break;
case PRESENTATION:
placeholder = R.drawable.file_ppt;
break;
default:
placeholder = R.drawable.file;
break;
}
Glide.with(context).using(new CustomGlideStreamLoader(currentAccountProvider, clientFactory)).
load(template.getThumbnailLink())
.placeholder(placeholder)
.error(placeholder)
.into(thumbnail);
name.setText(template.getName());
}
}
public interface ClickListener {
void onClick(Template template);
}
}

View File

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

View File

@ -0,0 +1,74 @@
/*
*
* Nextcloud Android client application
*
* @author Tobias Kaminsky
* Copyright (C) 2020 Tobias Kaminsky
* Copyright (C) 2020 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.asynctasks;
import android.accounts.Account;
import android.os.AsyncTask;
import com.nextcloud.client.account.User;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.operations.RichDocumentsUrlOperation;
import com.owncloud.android.ui.activity.EditorWebView;
import java.lang.ref.WeakReference;
public class RichDocumentsLoadUrlTask extends AsyncTask<Void, Void, String> {
private Account account;
private WeakReference<EditorWebView> editorWebViewWeakReference;
private OCFile file;
public RichDocumentsLoadUrlTask(EditorWebView editorWebView, User user, OCFile file) {
this.account = user.toPlatformAccount();
this.editorWebViewWeakReference = new WeakReference<>(editorWebView);
this.file = file;
}
@Override
protected String doInBackground(Void... voids) {
final EditorWebView editorWebView = editorWebViewWeakReference.get();
if (editorWebView == null) {
return "";
}
RemoteOperationResult result = new RichDocumentsUrlOperation(file.getLocalId()).execute(account, editorWebView);
if (!result.isSuccess()) {
return "";
}
return (String) result.getData().get(0);
}
@Override
protected void onPostExecute(String url) {
EditorWebView editorWebView = editorWebViewWeakReference.get();
if (editorWebView == null) {
return;
}
editorWebView.onUrlLoaded(url);
}
}

View File

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

View File

@ -0,0 +1,339 @@
/*
*
* Nextcloud Android client application
*
* @author Tobias Kaminsky
* Copyright (C) 2019 Tobias Kaminsky
* Copyright (C) 2019 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.dialog;
import android.accounts.Account;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager.LayoutParams;
import android.widget.EditText;
import com.nextcloud.client.account.CurrentAccountProvider;
import com.nextcloud.client.account.User;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.network.ClientFactory;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.Template;
import com.owncloud.android.files.CreateFileFromTemplateOperation;
import com.owncloud.android.files.FetchTemplateOperation;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation;
import com.owncloud.android.lib.resources.files.model.RemoteFile;
import com.owncloud.android.ui.activity.ExternalSiteWebView;
import com.owncloud.android.ui.activity.RichDocumentsEditorWebView;
import com.owncloud.android.ui.adapter.RichDocumentsTemplateAdapter;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.FileStorageUtils;
import com.owncloud.android.utils.NextcloudServer;
import com.owncloud.android.utils.ThemeUtils;
import org.parceler.Parcels;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* Dialog to show templates for new documents/spreadsheets/presentations.
*/
public class ChooseRichDocumentsTemplateDialogFragment extends DialogFragment implements DialogInterface.OnClickListener,
RichDocumentsTemplateAdapter.ClickListener, Injectable {
private static final String ARG_PARENT_FOLDER = "PARENT_FOLDER";
private static final String ARG_TYPE = "TYPE";
private static final String TAG = ChooseRichDocumentsTemplateDialogFragment.class.getSimpleName();
private static final String DOT = ".";
private RichDocumentsTemplateAdapter adapter;
private OCFile parentFolder;
private OwnCloudClient client;
@Inject CurrentAccountProvider currentAccount;
@Inject ClientFactory clientFactory;
public enum Type {
DOCUMENT,
SPREADSHEET,
PRESENTATION
}
@BindView(R.id.list)
RecyclerView listView;
@BindView(R.id.filename)
EditText fileName;
@NextcloudServer(max = 18) // will be removed in favor of generic direct editing
public static ChooseRichDocumentsTemplateDialogFragment newInstance(OCFile parentFolder, Type type) {
ChooseRichDocumentsTemplateDialogFragment frag = new ChooseRichDocumentsTemplateDialogFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_PARENT_FOLDER, parentFolder);
args.putString(ARG_TYPE, type.name());
frag.setArguments(args);
return frag;
}
@Override
public void onStart() {
super.onStart();
int color = ThemeUtils.primaryAccentColor(getContext());
AlertDialog alertDialog = (AlertDialog) getDialog();
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(color);
alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(color);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle arguments = getArguments();
if (arguments == null) {
throw new IllegalArgumentException("Arguments may not be null");
}
Activity activity = getActivity();
if (activity == null) {
throw new IllegalArgumentException("Activity may not be null");
}
int accentColor = ThemeUtils.primaryAccentColor(getContext());
parentFolder = arguments.getParcelable(ARG_PARENT_FOLDER);
Type type = Type.valueOf(arguments.getString(ARG_TYPE));
// Inflate the layout for the dialog
LayoutInflater inflater = activity.getLayoutInflater();
@SuppressLint("InflateParams") View view = inflater.inflate(R.layout.choose_template, null);
ButterKnife.bind(this, view);
fileName.requestFocus();
fileName.getBackground().setColorFilter(accentColor, PorterDuff.Mode.SRC_ATOP);
try {
Account account = currentAccount.getCurrentAccount();
OwnCloudAccount ocAccount = new OwnCloudAccount(account, activity);
client = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, getContext());
new FetchTemplateTask(this, client).execute(type);
} catch (Exception e) {
Log_OC.e(TAG, "Loading stream url not possible: " + e);
}
listView.setHasFixedSize(true);
listView.setLayoutManager(new GridLayoutManager(activity, 2));
adapter = new RichDocumentsTemplateAdapter(type, this, getContext(), currentAccount, clientFactory);
listView.setAdapter(adapter);
// Build the dialog
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setView(view)
.setNegativeButton(R.string.common_cancel, this)
.setTitle(ThemeUtils.getColoredTitle(getResources().getString(R.string.select_template), accentColor));
Dialog dialog = builder.create();
Window window = dialog.getWindow();
if (window != null) {
window.setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
}
return dialog;
}
private void createFromTemplate(Template template, String path) {
new CreateFileFromTemplateTask(this, client, template, path, currentAccount.getUser()).execute();
}
public void setTemplateList(List<Template> templateList) {
adapter.setTemplateList(templateList);
adapter.notifyDataSetChanged();
}
@Override
public void onClick(Template template) {
String name = fileName.getText().toString();
String path = parentFolder.getRemotePath() + name;
if (name.isEmpty() || name.equalsIgnoreCase(DOT + template.getExtension())) {
DisplayUtils.showSnackMessage(listView, R.string.enter_filename);
} else if (!name.endsWith(template.getExtension())) {
createFromTemplate(template, path + DOT + template.getExtension());
} else {
createFromTemplate(template, path);
}
}
@Override
public void onClick(DialogInterface dialog, int which) {
// cancel is handled by dialog itself, no other button available
}
private static class CreateFileFromTemplateTask extends AsyncTask<Void, Void, String> {
private OwnCloudClient client;
private WeakReference<ChooseRichDocumentsTemplateDialogFragment> chooseTemplateDialogFragmentWeakReference;
private Template template;
private String path;
private User user;
private OCFile file;
CreateFileFromTemplateTask(ChooseRichDocumentsTemplateDialogFragment chooseTemplateDialogFragment,
OwnCloudClient client,
Template template,
String path,
User user
) {
this.client = client;
this.chooseTemplateDialogFragmentWeakReference = new WeakReference<>(chooseTemplateDialogFragment);
this.template = template;
this.path = path;
this.user = user;
}
@Override
protected String doInBackground(Void... voids) {
RemoteOperationResult result = new CreateFileFromTemplateOperation(path, template.getId()).execute(client);
if (result.isSuccess()) {
// get file
RemoteOperationResult newFileResult = new ReadFileRemoteOperation(path).execute(client);
if (newFileResult.isSuccess()) {
OCFile temp = FileStorageUtils.fillOCFile((RemoteFile) newFileResult.getData().get(0));
if (chooseTemplateDialogFragmentWeakReference.get() != null) {
FileDataStorageManager storageManager = new FileDataStorageManager(
user.toPlatformAccount(),
chooseTemplateDialogFragmentWeakReference.get().requireContext().getContentResolver());
storageManager.saveFile(temp);
file = storageManager.getFileByPath(path);
return result.getData().get(0).toString();
} else {
return "";
}
} else {
return "";
}
} else {
return "";
}
}
@Override
protected void onPostExecute(String url) {
ChooseRichDocumentsTemplateDialogFragment fragment = chooseTemplateDialogFragmentWeakReference.get();
if (fragment != null && fragment.isAdded()) {
if (url.isEmpty()) {
DisplayUtils.showSnackMessage(fragment.listView, "Error creating file from template");
} else {
Intent collaboraWebViewIntent = new Intent(MainApp.getAppContext(), RichDocumentsEditorWebView.class);
collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_TITLE, "Collabora");
collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_URL, url);
collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_FILE, file);
collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_SHOW_SIDEBAR, false);
collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_TEMPLATE, Parcels.wrap(template));
fragment.startActivity(collaboraWebViewIntent);
fragment.dismiss();
}
} else {
Log_OC.e(TAG, "Error creating file from template!");
}
}
}
private static class FetchTemplateTask extends AsyncTask<Type, Void, List<Template>> {
private OwnCloudClient client;
private WeakReference<ChooseRichDocumentsTemplateDialogFragment> chooseTemplateDialogFragmentWeakReference;
FetchTemplateTask(ChooseRichDocumentsTemplateDialogFragment chooseTemplateDialogFragment, OwnCloudClient client) {
this.client = client;
this.chooseTemplateDialogFragmentWeakReference = new WeakReference<>(chooseTemplateDialogFragment);
}
@Override
protected List<Template> doInBackground(Type... type) {
FetchTemplateOperation fetchTemplateOperation = new FetchTemplateOperation(type[0]);
RemoteOperationResult result = fetchTemplateOperation.execute(client);
if (!result.isSuccess()) {
return new ArrayList<>();
}
List<Template> templateList = new ArrayList<>();
for (Object object : result.getData()) {
templateList.add((Template) object);
}
return templateList;
}
@Override
protected void onPostExecute(List<Template> templateList) {
ChooseRichDocumentsTemplateDialogFragment fragment = chooseTemplateDialogFragmentWeakReference.get();
if (fragment != null) {
if (templateList.isEmpty()) {
DisplayUtils.showSnackMessage(fragment.listView, R.string.error_retrieving_templates);
} else {
fragment.setTemplateList(templateList);
String name = DOT + templateList.get(0).getExtension();
fragment.fileName.setText(name);
}
} else {
Log_OC.e(TAG, "Error streaming file: no previewMediaFragment!");
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?><!--
~
~ Nextcloud Android client application
~
~ @author Tobias Kaminsky
~ Copyright (C) 2019 Tobias Kaminsky
~ Copyright (C) 2019 Nextcloud GmbH
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingLeft="@dimen/standard_padding"
android:paddingTop="@dimen/standard_half_padding"
android:paddingRight="@dimen/standard_padding"
android:paddingBottom="@dimen/standard_padding"
tools:ignore="UseCompoundDrawables">
<ImageView
android:id="@+id/creator_thumbnail"
android:layout_width="24dp"
android:layout_height="24dp"
android:contentDescription="@null"
android:src="@drawable/file_ppt" />
<TextView
android:id="@+id/creator_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="@dimen/standard_margin"
android:layout_marginLeft="@dimen/standard_margin"
android:text="@string/create_new_presentation"
android:textColor="@color/text_color"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>

View File

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

View File

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