From 9463d2e66887dfb7cbc5b2bb3b0e2f634bdd478b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Brey=20Vilas?= Date: Fri, 29 Apr 2022 16:03:00 +0200 Subject: [PATCH 01/32] build: Clean up repository configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Put jitpack.io at the bottom, so that it is the last one checked. For libraries that are both in jitpack and elsewhere, this should prevent a lot of errors especially in CI due to jitpack unreliability. - Don't use gradle plugin portal as a mirror for JCenter. JCenter is still up as read-only, and using the gradle plugin portal hides the fact that we have stop using jcenter libraries. - For buildscripts, use gradlePluginPortal() instead of manual maven config - Don't have repository configuration in both app/build.gradle and project build.gradle. Use project gradle instead Signed-off-by: Álvaro Brey Vilas --- app/build.gradle | 9 --------- build.gradle | 7 +++---- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b0d2f0ecc5..28a3131983 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -54,15 +54,6 @@ configurations.configureEach { } } -repositories { - google() - maven { url "https://jitpack.io" } - mavenCentral() - maven { - url 'https://plugins.gradle.org/m2/' - } -} - // semantic versioning for version code def versionMajor = 3 def versionMinor = 20 diff --git a/build.gradle b/build.gradle index fb4bd394fc..3b2d8c71dd 100644 --- a/build.gradle +++ b/build.gradle @@ -26,17 +26,16 @@ buildscript { subprojects { buildscript { repositories { + gradlePluginPortal() google() - maven { - url 'https://plugins.gradle.org/m2/' - } mavenCentral() } } repositories { google() - maven { url "https://jitpack.io" } mavenCentral() + jcenter() + maven { url "https://jitpack.io" } } } From 2bd8fb4240a978dffc53c3313c78c2e50bfe5b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Brey=20Vilas?= Date: Fri, 29 Apr 2022 16:13:04 +0200 Subject: [PATCH 02/32] build: Update last 2 libraries that used JCenter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both of these are present in mavenCentral with a different groupId. For both of them, the version bump is only for the groupId change, there are no breaking changes otherwise. Signed-off-by: Álvaro Brey Vilas --- app/build.gradle | 4 ++-- build.gradle | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 28a3131983..2949c80b9e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -241,7 +241,7 @@ dependencies { implementation 'com.github.chrisbanes:PhotoView:2.3.0' implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.24' implementation 'com.github.tobiaskaminsky:qrcodescanner:0.1.2.4' // 'com.github.blikoon:QRCodeScanner:0.1.2' - implementation 'com.google.android:flexbox:2.0.1' + implementation 'com.google.android.flexbox:flexbox:3.0.0' implementation('com.github.bumptech.glide:glide:3.8.0') { exclude group: "com.android.support" } @@ -275,7 +275,7 @@ dependencies { implementation 'com.simplecityapps:recyclerview-fastscroll:2.0.1' // Shimmer animation - implementation 'com.elyeproj.libraries:loaderviewlibrary:2.0.0' + implementation 'io.github.elye:loaderviewlibrary:3.0.0' // dependencies for markdown rendering implementation "io.noties.markwon:core:$markwonVersion" diff --git a/build.gradle b/build.gradle index 3b2d8c71dd..faefd53b43 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,6 @@ subprojects { repositories { google() mavenCentral() - jcenter() maven { url "https://jitpack.io" } } } From b58749d459e09fa55d842051b619cbc4cebcd89a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Brey=20Vilas?= Date: Fri, 29 Apr 2022 16:50:35 +0200 Subject: [PATCH 03/32] Fix ThemeUtils injection for MediaControlView MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inflation of this view was failing as there was no proper constructor Signed-off-by: Álvaro Brey Vilas --- .../com/nextcloud/client/di/AppComponent.java | 3 ++ .../java/com/owncloud/android/MainApp.java | 31 ++++++++++++++++--- .../android/media/MediaControlView.java | 18 ++++++----- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/di/AppComponent.java b/app/src/main/java/com/nextcloud/client/di/AppComponent.java index 1d05f04b84..90115f97d9 100644 --- a/app/src/main/java/com/nextcloud/client/di/AppComponent.java +++ b/app/src/main/java/com/nextcloud/client/di/AppComponent.java @@ -30,6 +30,7 @@ import com.nextcloud.client.network.NetworkModule; import com.nextcloud.client.onboarding.OnboardingModule; import com.nextcloud.client.preferences.PreferencesModule; import com.owncloud.android.MainApp; +import com.owncloud.android.media.MediaControlView; import javax.inject.Singleton; @@ -55,6 +56,8 @@ public interface AppComponent { void inject(MainApp app); + void inject(MediaControlView mediaControlView); + @Component.Builder interface Builder { @BindsInstance diff --git a/app/src/main/java/com/owncloud/android/MainApp.java b/app/src/main/java/com/owncloud/android/MainApp.java index 6e51b618a0..d1edd348f8 100644 --- a/app/src/main/java/com/owncloud/android/MainApp.java +++ b/app/src/main/java/com/owncloud/android/MainApp.java @@ -45,6 +45,7 @@ import com.nextcloud.client.appinfo.AppInfo; import com.nextcloud.client.core.Clock; import com.nextcloud.client.device.PowerManagementService; import com.nextcloud.client.di.ActivityInjector; +import com.nextcloud.client.di.AppComponent; import com.nextcloud.client.di.DaggerAppComponent; import com.nextcloud.client.errorhandling.ExceptionHandler; import com.nextcloud.client.jobs.BackgroundJobManager; @@ -178,6 +179,8 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector { @SuppressWarnings("unused") private boolean mBound; + private static AppComponent appComponent; + /** * Temporary hack */ @@ -226,10 +229,7 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector { super.attachBaseContext(base); initGlobalContext(this); - DaggerAppComponent.builder() - .application(this) - .build() - .inject(this); + initDagger(); // we don't want to handle crashes occurring inside crash reporter activity/process; // let the platform deal with those @@ -243,6 +243,29 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector { } } + private void initDagger() { + appComponent = DaggerAppComponent.builder() + .application(this) + .build(); + + appComponent.inject(this); + } + + /** + * USE SPARINGLY! This should only be used for injection of Theme classes in custom Views, and they need to + * be added as methods in the {@link AppComponent} itself. + *

+ * Once we adopt Hilt this won't be necessary either, as View is a supported target in Hilt. + * + * @return the {@link AppComponent} for this app + */ + public static AppComponent getAppComponent() { + if (appComponent == null) { + throw new IllegalStateException("Dagger not initialized!"); + } + return appComponent; + } + @SuppressFBWarnings("ST") @Override public void onCreate() { diff --git a/app/src/main/java/com/owncloud/android/media/MediaControlView.java b/app/src/main/java/com/owncloud/android/media/MediaControlView.java index 6de5591b63..d9c378d073 100644 --- a/app/src/main/java/com/owncloud/android/media/MediaControlView.java +++ b/app/src/main/java/com/owncloud/android/media/MediaControlView.java @@ -38,6 +38,7 @@ import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; +import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.utils.theme.ThemeBarUtils; @@ -46,6 +47,8 @@ import com.owncloud.android.utils.theme.ThemeColorUtils; import java.util.Formatter; import java.util.Locale; +import javax.inject.Inject; + /** * View containing controls for a {@link MediaPlayer}. @@ -67,17 +70,18 @@ public class MediaControlView extends FrameLayout implements OnClickListener, On private ImageButton pauseButton; private ImageButton forwardButton; private ImageButton rewindButton; - private final ThemeColorUtils themeColorUtils; - private final ThemeBarUtils themeBarUtils; + + @Inject + ThemeColorUtils themeColorUtils; + @Inject + ThemeBarUtils themeBarUtils; + public MediaControlView(Context context, - AttributeSet attrs, - ThemeColorUtils themeColorUtils, - ThemeBarUtils themeBarUtils) { + AttributeSet attrs) { super(context, attrs); - this.themeColorUtils = themeColorUtils; - this.themeBarUtils = themeBarUtils; + MainApp.getAppComponent().inject(this); FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, From 2560d1be3c5f8ee25325259b665e288faf3fc53b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Brey=20Vilas?= Date: Fri, 29 Apr 2022 16:56:56 +0200 Subject: [PATCH 04/32] Inject ThemeColorUtils into ThemeableSwitchPreference too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Álvaro Brey Vilas --- .../java/com/nextcloud/client/di/AppComponent.java | 3 +++ .../android/ui/ThemeableSwitchPreference.java | 13 ++++++++----- .../android/ui/activity/SettingsActivity.java | 6 ------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/di/AppComponent.java b/app/src/main/java/com/nextcloud/client/di/AppComponent.java index 90115f97d9..95157e38cc 100644 --- a/app/src/main/java/com/nextcloud/client/di/AppComponent.java +++ b/app/src/main/java/com/nextcloud/client/di/AppComponent.java @@ -31,6 +31,7 @@ import com.nextcloud.client.onboarding.OnboardingModule; import com.nextcloud.client.preferences.PreferencesModule; import com.owncloud.android.MainApp; import com.owncloud.android.media.MediaControlView; +import com.owncloud.android.ui.ThemeableSwitchPreference; import javax.inject.Singleton; @@ -58,6 +59,8 @@ public interface AppComponent { void inject(MediaControlView mediaControlView); + void inject(ThemeableSwitchPreference switchPreference); + @Component.Builder interface Builder { @BindsInstance diff --git a/app/src/main/java/com/owncloud/android/ui/ThemeableSwitchPreference.java b/app/src/main/java/com/owncloud/android/ui/ThemeableSwitchPreference.java index b1b8dce81e..181d1bd087 100644 --- a/app/src/main/java/com/owncloud/android/ui/ThemeableSwitchPreference.java +++ b/app/src/main/java/com/owncloud/android/ui/ThemeableSwitchPreference.java @@ -29,9 +29,12 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Switch; +import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.utils.theme.ThemeColorUtils; +import javax.inject.Inject; + import androidx.appcompat.app.AppCompatDelegate; import androidx.core.graphics.drawable.DrawableCompat; @@ -40,22 +43,22 @@ import androidx.core.graphics.drawable.DrawableCompat; * Themeable switch preference TODO Migrate to androidx */ public class ThemeableSwitchPreference extends SwitchPreference { - private ThemeColorUtils themeColorUtils; + @Inject + ThemeColorUtils themeColorUtils; public ThemeableSwitchPreference(Context context) { super(context); + MainApp.getAppComponent().inject(this); } public ThemeableSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + MainApp.getAppComponent().inject(this); } public ThemeableSwitchPreference(Context context, AttributeSet attrs) { super(context, attrs); - } - - public void setThemeColorUtils(ThemeColorUtils themeColorUtils) { - this.themeColorUtils = themeColorUtils; + MainApp.getAppComponent().inject(this); } @Override diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java index 86c38fd74a..63cb5a98a4 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java @@ -540,7 +540,6 @@ public class SettingsActivity extends ThemedPreferenceActivity boolean fShowMediaScanNotifications) { ThemeableSwitchPreference mShowMediaScanNotifications = (ThemeableSwitchPreference) findPreference(PREFERENCE_SHOW_MEDIA_SCAN_NOTIFICATIONS); - mShowMediaScanNotifications.setThemeColorUtils(themeColorUtils); if (fShowMediaScanNotifications) { preferenceCategoryDetails.removePreference(mShowMediaScanNotifications); @@ -550,7 +549,6 @@ public class SettingsActivity extends ThemedPreferenceActivity private void setupHiddenFilesPreference(PreferenceCategory preferenceCategoryDetails, boolean fShowHiddenFilesEnabled) { showHiddenFiles = (ThemeableSwitchPreference) findPreference("show_hidden_files"); - showHiddenFiles.setThemeColorUtils(themeColorUtils); if (fShowHiddenFilesEnabled) { showHiddenFiles.setOnPreferenceClickListener(preference -> { preferences.setShowHiddenFilesEnabled(showHiddenFiles.isChecked()); @@ -617,10 +615,6 @@ public class SettingsActivity extends ThemedPreferenceActivity preferenceCategorySyncedFolders.setTitle(themeTextUtils.getColoredTitle(getString(R.string.drawer_synced_folders), accentColor)); - ThemeableSwitchPreference syncedFolderOnWifiSwitch = - (ThemeableSwitchPreference) findPreference("synced_folder_on_wifi"); - syncedFolderOnWifiSwitch.setThemeColorUtils(themeColorUtils); - if (!getResources().getBoolean(R.bool.syncedFolder_light)) { preferenceScreen.removePreference(preferenceCategorySyncedFolders); } else { From 1ea61c6fe6467621295bfc3c4b300825c665b53a Mon Sep 17 00:00:00 2001 From: nextcloud-android-bot Date: Fri, 29 Apr 2022 15:07:40 +0000 Subject: [PATCH 05/32] Analysis: update lint results to reflect reduced error/warning count Signed-off-by: nextcloud-android-bot --- scripts/analysis/lint-results.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/analysis/lint-results.txt b/scripts/analysis/lint-results.txt index 24702d5bcb..e5e2d4d647 100644 --- a/scripts/analysis/lint-results.txt +++ b/scripts/analysis/lint-results.txt @@ -1,2 +1,2 @@ DO NOT TOUCH; GENERATED BY DRONE - Lint Report: 88 warnings + Lint Report: 87 warnings From f4546d1ceb8e2534a9517575c18b91a04dd9ad57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 May 2022 01:26:20 +0000 Subject: [PATCH 06/32] Bump byteBuddyVersion from 1.12.9 to 1.12.10 Bumps `byteBuddyVersion` from 1.12.9 to 1.12.10. Updates `byte-buddy` from 1.12.9 to 1.12.10 - [Release notes](https://github.com/raphw/byte-buddy/releases) - [Changelog](https://github.com/raphw/byte-buddy/blob/master/release-notes.md) - [Commits](https://github.com/raphw/byte-buddy/compare/byte-buddy-1.12.9...byte-buddy-1.12.10) Updates `byte-buddy-android` from 1.12.9 to 1.12.10 - [Release notes](https://github.com/raphw/byte-buddy/releases) - [Changelog](https://github.com/raphw/byte-buddy/blob/master/release-notes.md) - [Commits](https://github.com/raphw/byte-buddy/compare/byte-buddy-1.12.9...byte-buddy-1.12.10) --- updated-dependencies: - dependency-name: net.bytebuddy:byte-buddy dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: net.bytebuddy:byte-buddy-android dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index fb4bd394fc..e584c54214 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ buildscript { mockitoKotlinVersion = "4.0.0" mockkVersion = "1.12.3" powermockVersion = "2.0.9" - byteBuddyVersion = "1.12.9" + byteBuddyVersion = "1.12.10" espressoVersion = "3.4.0" workRuntime = "2.7.1" fidoVersion = "4.1.0" From 7a2c735795aa43c024c1b1d264b38142670f9186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Brey=20Vilas?= Date: Fri, 29 Apr 2022 16:25:57 +0200 Subject: [PATCH 07/32] Fix contents cut off when scrolling is locked MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Don't need to touch the coordinator params, just set scroll flags properly Signed-off-by: Álvaro Brey Vilas --- .../android/ui/activity/FileDisplayActivity.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java index 3127f4da2f..0f896b823e 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -1545,14 +1545,10 @@ public class FileDisplayActivity extends FileActivity */ @VisibleForTesting public void lockScrolling() { - final CoordinatorLayout.LayoutParams coordinatorParams = (CoordinatorLayout.LayoutParams) binding.rootLayout.getLayoutParams(); - coordinatorParams.setBehavior(null); - binding.rootLayout.setLayoutParams(coordinatorParams); - binding.rootLayout.setNestedScrollingEnabled(false); + binding.appbar.appbar.setExpanded(true, false); final AppBarLayout.LayoutParams appbarParams = (AppBarLayout.LayoutParams) binding.appbar.toolbarFrame.getLayoutParams(); appbarParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL); binding.appbar.toolbarFrame.setLayoutParams(appbarParams); - binding.appbar.appbar.setExpanded(true, false); } /** @@ -1560,9 +1556,6 @@ public class FileDisplayActivity extends FileActivity */ @VisibleForTesting public void resetScrolling() { - final CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) binding.rootLayout.getLayoutParams(); - params.setBehavior(new AppBarLayout.ScrollingViewBehavior()); - binding.rootLayout.setLayoutParams(params); AppBarLayout.LayoutParams appbarParams = (AppBarLayout.LayoutParams) binding.appbar.toolbarFrame.getLayoutParams(); appbarParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS); binding.appbar.toolbarFrame.setLayoutParams(appbarParams); From 13e061e7d7cf7c3c036290732fe00a3c76d1f3bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Brey=20Vilas?= Date: Fri, 29 Apr 2022 17:06:57 +0200 Subject: [PATCH 08/32] PreviewMediaFragment: Remove margin from top bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not needed since previous commit fixing scroll bugs, creates unwanted space Signed-off-by: Álvaro Brey Vilas --- app/src/main/res/layout/fragment_preview_media.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/res/layout/fragment_preview_media.xml b/app/src/main/res/layout/fragment_preview_media.xml index cff7770a9f..70360c412f 100644 --- a/app/src/main/res/layout/fragment_preview_media.xml +++ b/app/src/main/res/layout/fragment_preview_media.xml @@ -26,7 +26,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" - android:layout_marginTop="?attr/actionBarSize" tools:context=".ui.preview.PreviewMediaFragment"> Date: Mon, 2 May 2022 11:02:46 +0200 Subject: [PATCH 09/32] FileDisplayActivity: wait until OCFileListFragment is initialized to trigger browseToRoot() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This solves a bug which caused a "Directory does not exist" popup when coming back from Gallery, as it tried to call browseToRoot on GalleryFragment due it being executed before the fragment transaction. Signed-off-by: Álvaro Brey --- .../com/owncloud/android/ui/activity/FileDisplayActivity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java index 3127f4da2f..982230eb4a 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -513,6 +513,7 @@ public class FileDisplayActivity extends FileActivity Log_OC.d(this, "Switch to oc file fragment"); setLeftFragment(new OCFileListFragment()); + getSupportFragmentManager().executePendingTransactions(); browseToRoot(); } } From b8cf8dade802a247e740e5a2171b569cbb5a4f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Brey=20Vilas?= Date: Wed, 6 Apr 2022 17:50:36 +0200 Subject: [PATCH 10/32] Store file locking capability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Álvaro Brey Vilas --- .../datamodel/FileDataStorageManager.java | 4 ++++ .../com/owncloud/android/db/ProviderMeta.java | 3 ++- .../android/providers/FileContentProvider.java | 16 +++++++++++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java index 7c35fac09a..62f448cf02 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -2075,6 +2075,8 @@ public class FileDataStorageManager { contentValues.put(ProviderTableMeta.CAPABILITIES_USER_STATUS, capability.getUserStatus().getValue()); contentValues.put(ProviderTableMeta.CAPABILITIES_USER_STATUS_SUPPORTS_EMOJI, capability.getUserStatusSupportsEmoji().getValue()); + contentValues.put(ProviderTableMeta.CAPABILITIES_FILES_LOCKING_VERSION, + capability.getFilesLockingVersion()); return contentValues; } @@ -2224,6 +2226,8 @@ public class FileDataStorageManager { capability.setUserStatus(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_USER_STATUS)); capability.setUserStatusSupportsEmoji( getBoolean(cursor, ProviderTableMeta.CAPABILITIES_USER_STATUS_SUPPORTS_EMOJI)); + capability.setFilesLockingVersion( + getString(cursor, ProviderTableMeta.CAPABILITIES_FILES_LOCKING_VERSION)); } return capability; } diff --git a/app/src/main/java/com/owncloud/android/db/ProviderMeta.java b/app/src/main/java/com/owncloud/android/db/ProviderMeta.java index 7657fb40c9..313b9a3590 100644 --- a/app/src/main/java/com/owncloud/android/db/ProviderMeta.java +++ b/app/src/main/java/com/owncloud/android/db/ProviderMeta.java @@ -35,7 +35,7 @@ import java.util.List; */ public class ProviderMeta { public static final String DB_NAME = "filelist"; - public static final int DB_VERSION = 62; + public static final int DB_VERSION = 63; private ProviderMeta() { // No instance @@ -210,6 +210,7 @@ public class ProviderMeta { public static final String CAPABILITIES_FILES_BIGFILECHUNKING = "files_bigfilechunking"; public static final String CAPABILITIES_FILES_UNDELETE = "files_undelete"; public static final String CAPABILITIES_FILES_VERSIONING = "files_versioning"; + public static final String CAPABILITIES_FILES_LOCKING_VERSION = "files_locking_version"; public static final String CAPABILITIES_EXTERNAL_LINKS = "external_links"; public static final String CAPABILITIES_SERVER_NAME = "server_name"; public static final String CAPABILITIES_SERVER_COLOR = "server_color"; diff --git a/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java b/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java index 470520ae2a..e6538b95ec 100644 --- a/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java +++ b/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java @@ -829,7 +829,8 @@ public class FileContentProvider extends ContentProvider { + ProviderTableMeta.CAPABILITIES_DIRECT_EDITING_ETAG + TEXT + ProviderTableMeta.CAPABILITIES_USER_STATUS + INTEGER + ProviderTableMeta.CAPABILITIES_USER_STATUS_SUPPORTS_EMOJI + INTEGER - + ProviderTableMeta.CAPABILITIES_ETAG + " TEXT );"); + + ProviderTableMeta.CAPABILITIES_ETAG + TEXT + + ProviderTableMeta.CAPABILITIES_FILES_LOCKING_VERSION + " TEXT );"); } private void createUploadsTable(SQLiteDatabase db) { @@ -2454,6 +2455,19 @@ public class FileContentProvider extends ContentProvider { } } + if (oldVersion < 63 && newVersion >= 63) { + Log_OC.i(SQL, "Adding file locking version to capability"); + db.beginTransaction(); + try { + db.execSQL(ALTER_TABLE + ProviderTableMeta.CAPABILITIES_TABLE_NAME + ADD_COLUMN + ProviderTableMeta.CAPABILITIES_FILES_LOCKING_VERSION + " TEXT "); + // force refresh + db.execSQL("UPDATE capabilities SET etag = '' WHERE 1=1"); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + if (!upgraded) { Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion)); } From 353afb119cab9e37d03af88016f055af3d2dd18f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Brey=20Vilas?= Date: Wed, 6 Apr 2022 17:51:43 +0200 Subject: [PATCH 11/32] build.gradle: temporary library version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Álvaro Brey Vilas --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ce572b04dc..c40be4aee6 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { daggerVersion = "2.41" markwonVersion = "4.6.2" prismVersion = "2.0.0" - androidLibraryVersion = "master-SNAPSHOT" + androidLibraryVersion = "file_locking-SNAPSHOT" mockitoVersion = "4.5.1" mockitoKotlinVersion = "4.0.0" mockkVersion = "1.12.3" From f5ecaf91d1cfa5e1ddb01a65a20a02be0df0c5d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Brey=20Vilas?= Date: Wed, 6 Apr 2022 18:18:39 +0200 Subject: [PATCH 12/32] Show file locking status on list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also includes a refactor of how ContentValues are created in FileDataStorageManager. The code for that was triplicated. now it's not. Signed-off-by: Álvaro Brey Vilas --- .../datamodel/FileDataStorageManager.java | 163 +++++++----------- .../owncloud/android/datamodel/OCFile.java | 48 ++++++ .../com/owncloud/android/db/ProviderMeta.java | 11 +- .../providers/FileContentProvider.java | 20 ++- .../android/ui/adapter/ListItemViewHolder.kt | 1 + .../android/ui/adapter/OCFileListAdapter.java | 6 + .../ui/adapter/OCFileListItemViewHolder.kt | 2 + .../android/utils/FileStorageUtils.java | 4 + app/src/main/res/layout/list_item.xml | 16 ++ app/src/main/res/values/strings.xml | 1 + 10 files changed, 169 insertions(+), 103 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java index 62f448cf02..533a1509d1 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -208,44 +208,10 @@ public class FileDataStorageManager { public boolean saveFile(OCFile ocFile) { boolean overridden = false; - ContentValues cv = new ContentValues(); - cv.put(ProviderTableMeta.FILE_MODIFIED, ocFile.getModificationTimestamp()); - cv.put( - ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, - ocFile.getModificationTimestampAtLastSyncForData() - ); - cv.put(ProviderTableMeta.FILE_CREATION, ocFile.getCreationTimestamp()); - cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, ocFile.getFileLength()); - cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, ocFile.getMimeType()); - cv.put(ProviderTableMeta.FILE_NAME, ocFile.getFileName()); - cv.put(ProviderTableMeta.FILE_ENCRYPTED_NAME, ocFile.getEncryptedFileName()); - cv.put(ProviderTableMeta.FILE_PARENT, ocFile.getParentId()); - cv.put(ProviderTableMeta.FILE_PATH, ocFile.getRemotePath()); - cv.put(ProviderTableMeta.FILE_PATH_DECRYPTED, ocFile.getDecryptedRemotePath()); - cv.put(ProviderTableMeta.FILE_IS_ENCRYPTED, ocFile.isEncrypted()); - if (!ocFile.isFolder()) { - cv.put(ProviderTableMeta.FILE_STORAGE_PATH, ocFile.getStoragePath()); + final ContentValues cv = createContentValuesForFile(ocFile); + if (ocFile.isFolder()) { + cv.remove(ProviderTableMeta.FILE_STORAGE_PATH); } - cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, user.getAccountName()); - cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, ocFile.getLastSyncDateForProperties()); - cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, ocFile.getLastSyncDateForData()); - cv.put(ProviderTableMeta.FILE_ETAG, ocFile.getEtag()); - cv.put(ProviderTableMeta.FILE_ETAG_ON_SERVER, ocFile.getEtagOnServer()); - cv.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, ocFile.isSharedViaLink() ? 1 : 0); - cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, ocFile.isSharedWithSharee() ? 1 : 0); - cv.put(ProviderTableMeta.FILE_PERMISSIONS, ocFile.getPermissions()); - cv.put(ProviderTableMeta.FILE_REMOTE_ID, ocFile.getRemoteId()); - cv.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL, ocFile.isUpdateThumbnailNeeded()); - cv.put(ProviderTableMeta.FILE_IS_DOWNLOADING, ocFile.isDownloading()); - cv.put(ProviderTableMeta.FILE_ETAG_IN_CONFLICT, ocFile.getEtagInConflict()); - cv.put(ProviderTableMeta.FILE_UNREAD_COMMENTS_COUNT, ocFile.getUnreadCommentsCount()); - cv.put(ProviderTableMeta.FILE_OWNER_ID, ocFile.getOwnerId()); - cv.put(ProviderTableMeta.FILE_OWNER_DISPLAY_NAME, ocFile.getOwnerDisplayName()); - cv.put(ProviderTableMeta.FILE_NOTE, ocFile.getNote()); - cv.put(ProviderTableMeta.FILE_SHAREES, new Gson().toJson(ocFile.getSharees())); - cv.put(ProviderTableMeta.FILE_RICH_WORKSPACE, ocFile.getRichWorkspace()); - cv.put(ProviderTableMeta.FILE_HAS_PREVIEW, ocFile.isPreviewAvailable() ? 1 : 0); - cv.put(ProviderTableMeta.FILE_FAVORITE, ocFile.isFavorite()); boolean sameRemotePath = fileExists(ocFile.getRemotePath()); if (sameRemotePath || @@ -369,7 +335,8 @@ public class FileDataStorageManager { // prepare operations to insert or update files to save in the given folder for (OCFile ocFile : updatedFiles) { - ContentValues contentValues = createContentValueForFile(ocFile, folder); + ContentValues contentValues = createContentValuesForFile(ocFile); + contentValues.put(ProviderTableMeta.FILE_PARENT, folder.getFileId()); if (fileExists(ocFile.getFileId()) || fileExists(ocFile.getRemotePath())) { long fileId; @@ -423,7 +390,7 @@ public class FileDataStorageManager { } // update metadata of folder - ContentValues contentValues = createContentValueForFile(folder); + ContentValues contentValues = createContentValuesForFolder(folder); operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI) .withValues(contentValues) @@ -467,77 +434,70 @@ public class FileDataStorageManager { } } - private ContentValues createContentValueForFile(OCFile folder) { - ContentValues cv = new ContentValues(); - cv.put(ProviderTableMeta.FILE_MODIFIED, folder.getModificationTimestamp()); - cv.put( - ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, - folder.getModificationTimestampAtLastSyncForData() - ); - cv.put(ProviderTableMeta.FILE_CREATION, folder.getCreationTimestamp()); - cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, 0); - cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, folder.getMimeType()); - cv.put(ProviderTableMeta.FILE_NAME, folder.getFileName()); - cv.put(ProviderTableMeta.FILE_PARENT, folder.getParentId()); - cv.put(ProviderTableMeta.FILE_PATH, folder.getRemotePath()); - cv.put(ProviderTableMeta.FILE_PATH_DECRYPTED, folder.getDecryptedRemotePath()); + /** + * Returns a {@link ContentValues} filled with values that are common to both files and folders + * @see #createContentValuesForFile(OCFile) + * @see #createContentValuesForFolder(OCFile) + */ + private ContentValues createContentValuesBase(OCFile fileOrFolder) { + final ContentValues cv = new ContentValues(); + cv.put(ProviderTableMeta.FILE_MODIFIED, fileOrFolder.getModificationTimestamp()); + cv.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, fileOrFolder.getModificationTimestampAtLastSyncForData()); + cv.put(ProviderTableMeta.FILE_PARENT, fileOrFolder.getParentId()); + cv.put(ProviderTableMeta.FILE_CREATION, fileOrFolder.getCreationTimestamp()); + cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, fileOrFolder.getMimeType()); + cv.put(ProviderTableMeta.FILE_NAME, fileOrFolder.getFileName()); + cv.put(ProviderTableMeta.FILE_PATH, fileOrFolder.getRemotePath()); + cv.put(ProviderTableMeta.FILE_PATH_DECRYPTED, fileOrFolder.getDecryptedRemotePath()); cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, user.getAccountName()); - cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, folder.getLastSyncDateForProperties()); - cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, folder.getLastSyncDateForData()); - cv.put(ProviderTableMeta.FILE_ETAG, folder.getEtag()); - cv.put(ProviderTableMeta.FILE_ETAG_ON_SERVER, folder.getEtagOnServer()); - cv.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, folder.isSharedViaLink() ? 1 : 0); - cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, folder.isSharedWithSharee() ? 1 : 0); - cv.put(ProviderTableMeta.FILE_PERMISSIONS, folder.getPermissions()); - cv.put(ProviderTableMeta.FILE_REMOTE_ID, folder.getRemoteId()); - cv.put(ProviderTableMeta.FILE_FAVORITE, folder.isFavorite()); - cv.put(ProviderTableMeta.FILE_IS_ENCRYPTED, folder.isEncrypted()); - cv.put(ProviderTableMeta.FILE_UNREAD_COMMENTS_COUNT, folder.getUnreadCommentsCount()); - cv.put(ProviderTableMeta.FILE_OWNER_ID, folder.getOwnerId()); - cv.put(ProviderTableMeta.FILE_OWNER_DISPLAY_NAME, folder.getOwnerDisplayName()); - cv.put(ProviderTableMeta.FILE_NOTE, folder.getNote()); - cv.put(ProviderTableMeta.FILE_SHAREES, new Gson().toJson(folder.getSharees())); - cv.put(ProviderTableMeta.FILE_RICH_WORKSPACE, folder.getRichWorkspace()); - + cv.put(ProviderTableMeta.FILE_IS_ENCRYPTED, fileOrFolder.isEncrypted()); + cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, fileOrFolder.getLastSyncDateForProperties()); + cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, fileOrFolder.getLastSyncDateForData()); + cv.put(ProviderTableMeta.FILE_ETAG, fileOrFolder.getEtag()); + cv.put(ProviderTableMeta.FILE_ETAG_ON_SERVER, fileOrFolder.getEtagOnServer()); + cv.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, fileOrFolder.isSharedViaLink() ? 1 : 0); + cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, fileOrFolder.isSharedWithSharee() ? 1 : 0); + cv.put(ProviderTableMeta.FILE_PERMISSIONS, fileOrFolder.getPermissions()); + cv.put(ProviderTableMeta.FILE_REMOTE_ID, fileOrFolder.getRemoteId()); + cv.put(ProviderTableMeta.FILE_FAVORITE, fileOrFolder.isFavorite()); + cv.put(ProviderTableMeta.FILE_UNREAD_COMMENTS_COUNT, fileOrFolder.getUnreadCommentsCount()); + cv.put(ProviderTableMeta.FILE_OWNER_ID, fileOrFolder.getOwnerId()); + cv.put(ProviderTableMeta.FILE_OWNER_DISPLAY_NAME, fileOrFolder.getOwnerDisplayName()); + cv.put(ProviderTableMeta.FILE_NOTE, fileOrFolder.getNote()); + cv.put(ProviderTableMeta.FILE_SHAREES, new Gson().toJson(fileOrFolder.getSharees())); + cv.put(ProviderTableMeta.FILE_RICH_WORKSPACE, fileOrFolder.getRichWorkspace()); return cv; } - private ContentValues createContentValueForFile(OCFile file, OCFile folder) { - ContentValues cv = new ContentValues(); - cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp()); - cv.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, file.getModificationTimestampAtLastSyncForData()); - cv.put(ProviderTableMeta.FILE_CREATION, file.getCreationTimestamp()); + /** + * Returns a {@link ContentValues} filled with values for a folder + * @see #createContentValuesForFile(OCFile) + * @see #createContentValuesBase(OCFile) + */ + private ContentValues createContentValuesForFolder(OCFile folder) { + final ContentValues cv = createContentValuesBase(folder); + cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, 0); + return cv; + } + + /** + * Returns a {@link ContentValues} filled with values for a file + * @see #createContentValuesForFolder(OCFile) + * @see #createContentValuesBase(OCFile) + */ + private ContentValues createContentValuesForFile(OCFile file) { + final ContentValues cv = createContentValuesBase(file); cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength()); - cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimeType()); - cv.put(ProviderTableMeta.FILE_NAME, file.getFileName()); cv.put(ProviderTableMeta.FILE_ENCRYPTED_NAME, file.getEncryptedFileName()); - cv.put(ProviderTableMeta.FILE_PARENT, folder.getFileId()); - cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath()); - cv.put(ProviderTableMeta.FILE_PATH_DECRYPTED, file.getDecryptedRemotePath()); cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath()); - cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, user.getAccountName()); - cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDateForProperties()); - cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, file.getLastSyncDateForData()); - cv.put(ProviderTableMeta.FILE_ETAG, file.getEtag()); - cv.put(ProviderTableMeta.FILE_ETAG_ON_SERVER, file.getEtagOnServer()); - cv.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, file.isSharedViaLink() ? 1 : 0); - cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, file.isSharedWithSharee() ? 1 : 0); - cv.put(ProviderTableMeta.FILE_PERMISSIONS, file.getPermissions()); - cv.put(ProviderTableMeta.FILE_REMOTE_ID, file.getRemoteId()); cv.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL, file.isUpdateThumbnailNeeded()); cv.put(ProviderTableMeta.FILE_IS_DOWNLOADING, file.isDownloading()); cv.put(ProviderTableMeta.FILE_ETAG_IN_CONFLICT, file.getEtagInConflict()); - cv.put(ProviderTableMeta.FILE_FAVORITE, file.isFavorite()); - cv.put(ProviderTableMeta.FILE_IS_ENCRYPTED, file.isEncrypted()); - cv.put(ProviderTableMeta.FILE_MOUNT_TYPE, file.getMountType().ordinal()); cv.put(ProviderTableMeta.FILE_HAS_PREVIEW, file.isPreviewAvailable() ? 1 : 0); - cv.put(ProviderTableMeta.FILE_UNREAD_COMMENTS_COUNT, file.getUnreadCommentsCount()); - cv.put(ProviderTableMeta.FILE_OWNER_ID, file.getOwnerId()); - cv.put(ProviderTableMeta.FILE_OWNER_DISPLAY_NAME, file.getOwnerDisplayName()); - cv.put(ProviderTableMeta.FILE_NOTE, file.getNote()); - cv.put(ProviderTableMeta.FILE_SHAREES, new Gson().toJson(file.getSharees())); - cv.put(ProviderTableMeta.FILE_RICH_WORKSPACE, file.getRichWorkspace()); - + cv.put(ProviderTableMeta.FILE_LOCKED, file.isLocked()); + cv.put(ProviderTableMeta.FILE_LOCK_OWNER, file.getLockOwnerId()); + cv.put(ProviderTableMeta.FILE_LOCK_OWNER_DISPLAY_NAME, file.getLockOwnerDisplayName()); + cv.put(ProviderTableMeta.FILE_LOCK_TIMESTAMP, file.getLockTimestamp()); return cv; } @@ -1030,6 +990,11 @@ public class FileDataStorageManager { ocFile.setOwnerDisplayName(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_OWNER_DISPLAY_NAME))); ocFile.setNote(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_NOTE))); ocFile.setRichWorkspace(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_RICH_WORKSPACE))); + ocFile.setLocked(cursor.getInt(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCKED)) == 1); + ocFile.setLockOwnerId(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCK_OWNER))); + ocFile.setLockOwnerDisplayName(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCK_OWNER_DISPLAY_NAME))); + ocFile.setLockTimestamp(cursor.getInt(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCK_TIMESTAMP))); + String sharees = cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_SHAREES)); diff --git a/app/src/main/java/com/owncloud/android/datamodel/OCFile.java b/app/src/main/java/com/owncloud/android/datamodel/OCFile.java index 045a41fb96..7dca2b8364 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/OCFile.java +++ b/app/src/main/java/com/owncloud/android/datamodel/OCFile.java @@ -95,6 +95,10 @@ public class OCFile implements Parcelable, Comparable, ServerFileInterfa String note; private List sharees; private String richWorkspace; + private boolean isLocked; + private String lockOwnerId; + private String lockOwnerDisplayName; + private long lockTimestamp; /** * URI to the local path of the file contents, if stored in the device; cached after first call @@ -162,6 +166,10 @@ public class OCFile implements Parcelable, Comparable, ServerFileInterfa richWorkspace = source.readString(); previewAvailable = source.readInt() == 1; firstShareTimestamp = source.readLong(); + isLocked = source.readInt() == 1; + lockOwnerId = source.readString(); + lockOwnerDisplayName = source.readString(); + lockTimestamp = source.readLong(); } @Override @@ -196,6 +204,10 @@ public class OCFile implements Parcelable, Comparable, ServerFileInterfa dest.writeString(richWorkspace); dest.writeInt(previewAvailable ? 1 : 0); dest.writeLong(firstShareTimestamp); + dest.writeInt(isLocked ? 1:0); + dest.writeString(lockOwnerId); + dest.writeString(lockOwnerDisplayName); + dest.writeLong(lockTimestamp); } public void setDecryptedRemotePath(String path) { @@ -459,6 +471,10 @@ public class OCFile implements Parcelable, Comparable, ServerFileInterfa mountType = WebdavEntry.MountType.INTERNAL; richWorkspace = ""; firstShareTimestamp = 0; + isLocked = false; + lockOwnerId = null; + lockOwnerDisplayName = null; + lockTimestamp = 0; } /** @@ -831,4 +847,36 @@ public class OCFile implements Parcelable, Comparable, ServerFileInterfa public void setFirstShareTimestamp(long firstShareTimestamp) { this.firstShareTimestamp = firstShareTimestamp; } + + public boolean isLocked() { + return isLocked; + } + + public void setLocked(boolean locked) { + isLocked = locked; + } + + public String getLockOwnerId() { + return lockOwnerId; + } + + public void setLockOwnerId(String lockOwnerId) { + this.lockOwnerId = lockOwnerId; + } + + public String getLockOwnerDisplayName() { + return lockOwnerDisplayName; + } + + public void setLockOwnerDisplayName(String lockOwnerDisplayName) { + this.lockOwnerDisplayName = lockOwnerDisplayName; + } + + public long getLockTimestamp() { + return lockTimestamp; + } + + public void setLockTimestamp(long lockTimestamp) { + this.lockTimestamp = lockTimestamp; + } } diff --git a/app/src/main/java/com/owncloud/android/db/ProviderMeta.java b/app/src/main/java/com/owncloud/android/db/ProviderMeta.java index 313b9a3590..d6dbfaa6d0 100644 --- a/app/src/main/java/com/owncloud/android/db/ProviderMeta.java +++ b/app/src/main/java/com/owncloud/android/db/ProviderMeta.java @@ -117,6 +117,10 @@ public class ProviderMeta { public static final String FILE_NOTE = "note"; public static final String FILE_SHAREES = "sharees"; public static final String FILE_RICH_WORKSPACE = "rich_workspace"; + public static final String FILE_LOCKED = "locked"; + public static final String FILE_LOCK_OWNER = "lock_owner"; + public static final String FILE_LOCK_OWNER_DISPLAY_NAME = "lock_owner_display_name"; + public static final String FILE_LOCK_TIMESTAMP = "lock_timestamp"; public static final List FILE_ALL_COLUMNS = Collections.unmodifiableList(Arrays.asList( _ID, @@ -153,8 +157,11 @@ public class ProviderMeta { FILE_OWNER_DISPLAY_NAME, FILE_NOTE, FILE_SHAREES, - FILE_RICH_WORKSPACE)); - + FILE_RICH_WORKSPACE, + FILE_LOCKED, + FILE_LOCK_OWNER, + FILE_LOCK_OWNER_DISPLAY_NAME, + FILE_LOCK_TIMESTAMP)); public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME + " collate nocase asc"; // Columns of ocshares table diff --git a/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java b/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java index e6538b95ec..d6e718026d 100644 --- a/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java +++ b/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java @@ -753,7 +753,11 @@ public class FileContentProvider extends ContentProvider { + ProviderTableMeta.FILE_OWNER_DISPLAY_NAME + TEXT + ProviderTableMeta.FILE_NOTE + TEXT + ProviderTableMeta.FILE_SHAREES + TEXT - + ProviderTableMeta.FILE_RICH_WORKSPACE + " TEXT);" + + ProviderTableMeta.FILE_RICH_WORKSPACE + TEXT + + ProviderTableMeta.FILE_LOCKED + INTEGER // boolean + + ProviderTableMeta.FILE_LOCK_OWNER + TEXT + + ProviderTableMeta.FILE_LOCK_OWNER_DISPLAY_NAME + TEXT + + ProviderTableMeta.FILE_LOCK_TIMESTAMP + " INTEGER );" ); } @@ -2456,12 +2460,24 @@ public class FileContentProvider extends ContentProvider { } if (oldVersion < 63 && newVersion >= 63) { - Log_OC.i(SQL, "Adding file locking version to capability"); + Log_OC.i(SQL, "Adding file locking columns"); db.beginTransaction(); try { + // locking capabilities db.execSQL(ALTER_TABLE + ProviderTableMeta.CAPABILITIES_TABLE_NAME + ADD_COLUMN + ProviderTableMeta.CAPABILITIES_FILES_LOCKING_VERSION + " TEXT "); // force refresh db.execSQL("UPDATE capabilities SET etag = '' WHERE 1=1"); + // locking properties + db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + + ADD_COLUMN + ProviderTableMeta.FILE_LOCKED + " INTEGER "); // boolean + db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + + ADD_COLUMN + ProviderTableMeta.FILE_LOCK_OWNER + " TEXT "); + db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + + ADD_COLUMN + ProviderTableMeta.FILE_LOCK_OWNER_DISPLAY_NAME + " TEXT "); + db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + + ADD_COLUMN + ProviderTableMeta.FILE_LOCK_TIMESTAMP + " INTEGER "); + db.execSQL("UPDATE " + ProviderTableMeta.FILE_TABLE_NAME + " SET " + ProviderTableMeta.FILE_ETAG + " = '' WHERE 1=1"); + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ListItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/ListItemViewHolder.kt index e084283d86..4defcee17d 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/ListItemViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/ListItemViewHolder.kt @@ -32,4 +32,5 @@ internal interface ListItemViewHolder : ListGridItemViewHolder { val lastModification: TextView val overflowMenu: ImageView val sharedAvatars: AvatarGroupLayout + val lockIndicator: View } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java index 10c61f44bd..6ff09d432d 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java @@ -466,6 +466,12 @@ public class OCFileListAdapter extends RecyclerView.Adapter ocFileListFragmentInterface .onOverflowIconClicked(file, view)); } + + if (file.isLocked()) { + holder.getLockIndicator().setVisibility(View.VISIBLE); + } else { + holder.getLockIndicator().setVisibility(View.GONE); + } } private void bindListGridItemViewHolder(ListGridItemViewHolder holder, OCFile file) { diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt index 70a725ea69..508e41f36e 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt @@ -62,6 +62,8 @@ internal class OCFileListItemViewHolder(private var binding: ListItemBinding) : get() = binding.ListItemLayout override val unreadComments: ImageView get() = binding.unreadComments + override val lockIndicator: View + get() = binding.lockIndicator init { binding.favoriteAction.drawable.mutate() diff --git a/app/src/main/java/com/owncloud/android/utils/FileStorageUtils.java b/app/src/main/java/com/owncloud/android/utils/FileStorageUtils.java index bc9f68526c..70313c3dbe 100644 --- a/app/src/main/java/com/owncloud/android/utils/FileStorageUtils.java +++ b/app/src/main/java/com/owncloud/android/utils/FileStorageUtils.java @@ -233,6 +233,10 @@ public final class FileStorageUtils { file.setNote(remote.getNote()); file.setSharees(new ArrayList<>(Arrays.asList(remote.getSharees()))); file.setRichWorkspace(remote.getRichWorkspace()); + file.setLocked(remote.isLocked()); + file.setLockOwnerId(remote.getLockOwner()); + file.setLockOwnerDisplayName(remote.getLockOwnerDisplayName()); + file.setLockTimestamp(remote.getLockTimestamp()); return file; } diff --git a/app/src/main/res/layout/list_item.xml b/app/src/main/res/layout/list_item.xml index de15589669..2875124bb5 100644 --- a/app/src/main/res/layout/list_item.xml +++ b/app/src/main/res/layout/list_item.xml @@ -18,6 +18,7 @@ --> + + + Tap on a page to zoom in Full access Media read-only + This file is locked From fd2435379abe6fa5e8243c4c153624e2edbdc5ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Brey=20Vilas?= Date: Fri, 8 Apr 2022 11:32:15 +0200 Subject: [PATCH 13/32] FileDataStorageManager: small cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove two unused methods - Make some fields final Signed-off-by: Álvaro Brey Vilas --- .../datamodel/FileDataStorageManager.java | 102 +----------------- .../owncloud/android/datamodel/OCFile.java | 12 +-- 2 files changed, 9 insertions(+), 105 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java index 533a1509d1..92650b9835 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -83,9 +83,9 @@ public class FileDataStorageManager { public static final int ROOT_PARENT_ID = 0; public static final String NULL_STRING = "null"; - private ContentResolver contentResolver; - private ContentProviderClient contentProviderClient; - private User user; + private final ContentResolver contentResolver; + private final ContentProviderClient contentProviderClient; + private final User user; public FileDataStorageManager(User user, ContentResolver contentResolver) { this.contentProviderClient = null; @@ -1389,86 +1389,6 @@ public class FileDataStorageManager { } } - public void updateSharedFiles(Collection sharedFiles) { - resetShareFlagsInAllFiles(); - - if (sharedFiles != null) { - ArrayList operations = new ArrayList<>(sharedFiles.size()); - - // prepare operations to insert or update files to save in the given folder - for (OCFile file : sharedFiles) { - ContentValues cv = new ContentValues(); - cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp()); - cv.put( - ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, - file.getModificationTimestampAtLastSyncForData() - ); - cv.put(ProviderTableMeta.FILE_CREATION, file.getCreationTimestamp()); - cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength()); - cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimeType()); - cv.put(ProviderTableMeta.FILE_NAME, file.getFileName()); - cv.put(ProviderTableMeta.FILE_PARENT, file.getParentId()); - cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath()); - if (!file.isFolder()) { - cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath()); - } - cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, user.getAccountName()); - cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDateForProperties()); - cv.put( - ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, - file.getLastSyncDateForData() - ); - cv.put(ProviderTableMeta.FILE_ETAG, file.getEtag()); - cv.put(ProviderTableMeta.FILE_ETAG_ON_SERVER, file.getEtagOnServer()); - cv.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, file.isSharedViaLink() ? 1 : 0); - cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, file.isSharedWithSharee() ? 1 : 0); - cv.put(ProviderTableMeta.FILE_PERMISSIONS, file.getPermissions()); - cv.put(ProviderTableMeta.FILE_REMOTE_ID, file.getRemoteId()); - cv.put(ProviderTableMeta.FILE_FAVORITE, file.isFavorite()); - cv.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL, file.isUpdateThumbnailNeeded() ? 1 : 0); - cv.put(ProviderTableMeta.FILE_IS_DOWNLOADING, file.isDownloading() ? 1 : 0); - cv.put(ProviderTableMeta.FILE_ETAG_IN_CONFLICT, file.getEtagInConflict()); - - boolean existsByPath = fileExists(file.getRemotePath()); - if (existsByPath || fileExists(file.getFileId())) { - // updating an existing file - operations.add( - ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI). - withValues(cv). - withSelection(ProviderTableMeta._ID + "=?", - new String[]{String.valueOf(file.getFileId())}) - .build()); - - } else { - // adding a new file - operations.add( - ContentProviderOperation.newInsert(ProviderTableMeta.CONTENT_URI). - withValues(cv). - build() - ); - } - } - - // apply operations in batch - if (operations.size() > 0) { - @SuppressWarnings("unused") - ContentProviderResult[] results = null; - Log_OC.d(TAG, String.format(Locale.ENGLISH, SENDING_TO_FILECONTENTPROVIDER_MSG, operations.size())); - try { - if (getContentResolver() != null) { - results = getContentResolver().applyBatch(MainApp.getAuthority(), operations); - } else { - results = getContentProviderClient().applyBatch(operations); - } - - } catch (OperationApplicationException | RemoteException e) { - Log_OC.e(TAG, EXCEPTION_MSG + e.getMessage(), e); - } - } - } - - } - public void removeShare(OCShare share) { Uri contentUriShare = ProviderTableMeta.CONTENT_URI_SHARE; String where = ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + AND + @@ -2227,22 +2147,6 @@ public class FileDataStorageManager { } } - public void saveVirtual(VirtualFolderType type, OCFile file) { - ContentValues cv = new ContentValues(); - cv.put(ProviderTableMeta.VIRTUAL_TYPE, type.toString()); - cv.put(ProviderTableMeta.VIRTUAL_OCFILE_ID, file.getFileId()); - - if (getContentResolver() != null) { - getContentResolver().insert(ProviderTableMeta.CONTENT_URI_VIRTUAL, cv); - } else { - try { - getContentProviderClient().insert(ProviderTableMeta.CONTENT_URI_VIRTUAL, cv); - } catch (RemoteException e) { - Log_OC.e(TAG, FAILED_TO_INSERT_MSG + e.getMessage(), e); - } - } - } - public List getAllGalleryItems() { return getGalleryItems(0, Long.MAX_VALUE); } diff --git a/app/src/main/java/com/owncloud/android/datamodel/OCFile.java b/app/src/main/java/com/owncloud/android/datamodel/OCFile.java index 7dca2b8364..1865da993e 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/OCFile.java +++ b/app/src/main/java/com/owncloud/android/datamodel/OCFile.java @@ -95,7 +95,7 @@ public class OCFile implements Parcelable, Comparable, ServerFileInterfa String note; private List sharees; private String richWorkspace; - private boolean isLocked; + private boolean locked; private String lockOwnerId; private String lockOwnerDisplayName; private long lockTimestamp; @@ -166,7 +166,7 @@ public class OCFile implements Parcelable, Comparable, ServerFileInterfa richWorkspace = source.readString(); previewAvailable = source.readInt() == 1; firstShareTimestamp = source.readLong(); - isLocked = source.readInt() == 1; + locked = source.readInt() == 1; lockOwnerId = source.readString(); lockOwnerDisplayName = source.readString(); lockTimestamp = source.readLong(); @@ -204,7 +204,7 @@ public class OCFile implements Parcelable, Comparable, ServerFileInterfa dest.writeString(richWorkspace); dest.writeInt(previewAvailable ? 1 : 0); dest.writeLong(firstShareTimestamp); - dest.writeInt(isLocked ? 1:0); + dest.writeInt(locked ? 1:0); dest.writeString(lockOwnerId); dest.writeString(lockOwnerDisplayName); dest.writeLong(lockTimestamp); @@ -471,7 +471,7 @@ public class OCFile implements Parcelable, Comparable, ServerFileInterfa mountType = WebdavEntry.MountType.INTERNAL; richWorkspace = ""; firstShareTimestamp = 0; - isLocked = false; + locked = false; lockOwnerId = null; lockOwnerDisplayName = null; lockTimestamp = 0; @@ -849,11 +849,11 @@ public class OCFile implements Parcelable, Comparable, ServerFileInterfa } public boolean isLocked() { - return isLocked; + return locked; } public void setLocked(boolean locked) { - isLocked = locked; + this.locked = locked; } public String getLockOwnerId() { From 356db04aa8c7fd83dee7da1dc3c981eba7a19b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Brey=20Vilas?= Date: Fri, 8 Apr 2022 16:34:19 +0200 Subject: [PATCH 14/32] Add ability to toggle file lock from file context menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Álvaro Brey Vilas --- .../android/files/FileMenuFilter.java | 26 ++++++++++- .../android/ui/events/FileLockEvent.kt | 22 +++++++++ .../ui/fragment/OCFileListFragment.java | 38 +++++++++++++++ .../ui/helpers/FileOperationsHelper.java | 7 +++ app/src/main/res/layout/list_item.xml | 46 ++++++++----------- app/src/main/res/menu/item_file.xml | 12 +++++ app/src/main/res/values/strings.xml | 3 ++ 7 files changed, 125 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/com/owncloud/android/ui/events/FileLockEvent.kt diff --git a/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java b/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java index edf5107489..4020dcbc74 100644 --- a/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java +++ b/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java @@ -201,6 +201,7 @@ public class FileMenuFilter { filterUnsetEncrypted(toShow, toHide, endToEndEncryptionEnabled); filterSetPictureAs(toShow, toHide); filterStream(toShow, toHide); + filterLock(toShow, toHide); } private void filterShareFile(List toShow, List toHide, OCCapability capability) { @@ -252,9 +253,23 @@ public class FileMenuFilter { } } + private void filterLock(List toShow, List toHide) { + // TODO only allow locking files (not dirs), and only allow unlocking file if lock is owned by the user + if (files.isEmpty()) { + toHide.add(R.id.action_lock_file); + toHide.add(R.id.action_unlock_file); + } else if (allLocked()) { + toHide.add(R.id.action_lock_file); + toShow.add(R.id.action_unlock_file); + } else { + toHide.add(R.id.action_unlock_file); + toShow.add(R.id.action_lock_file); + } + } + private void filterEncrypt(List toShow, List toHide, boolean endToEndEncryptionEnabled) { if (files.isEmpty() || !isSingleSelection() || isSingleFile() || isEncryptedFolder() - || !endToEndEncryptionEnabled) { + || !endToEndEncryptionEnabled) { toHide.add(R.id.action_encrypted); } else { toShow.add(R.id.action_encrypted); @@ -572,4 +587,13 @@ public class FileMenuFilter { } return true; } + + private boolean allLocked() { + for (OCFile file : files) { + if (!file.isLocked()) { + return false; + } + } + return true; + } } diff --git a/app/src/main/java/com/owncloud/android/ui/events/FileLockEvent.kt b/app/src/main/java/com/owncloud/android/ui/events/FileLockEvent.kt new file mode 100644 index 0000000000..71179d556e --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/events/FileLockEvent.kt @@ -0,0 +1,22 @@ +/** + * Nextcloud Android client application + * + * @author Álvaro + * Copyright (C) 2017 Mario Danic + * + * 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 //www.gnu.org/licenses/>. + */ +package com.owncloud.android.ui.events + +data class FileLockEvent(val filePath: String, val shouldLock: Boolean) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 262b5e79bb..aca561b797 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -48,6 +48,7 @@ import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.behavior.HideBottomViewOnScrollBehavior; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.snackbar.Snackbar; +import com.nextcloud.android.lib.resources.files.ToggleFileLockRemoteOperation; import com.nextcloud.android.lib.richWorkspace.RichWorkspaceDirectEditingRemoteOperation; import com.nextcloud.client.account.User; import com.nextcloud.client.account.UserAccountManager; @@ -56,6 +57,7 @@ import com.nextcloud.client.di.Injectable; import com.nextcloud.client.network.ClientFactory; import com.nextcloud.client.preferences.AppPreferences; import com.nextcloud.client.utils.Throttler; +import com.nextcloud.common.NextcloudClient; import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.datamodel.ArbitraryDataProvider; @@ -92,6 +94,7 @@ import com.owncloud.android.ui.events.ChangeMenuEvent; import com.owncloud.android.ui.events.CommentsEvent; import com.owncloud.android.ui.events.EncryptionEvent; import com.owncloud.android.ui.events.FavoriteEvent; +import com.owncloud.android.ui.events.FileLockEvent; import com.owncloud.android.ui.events.SearchEvent; import com.owncloud.android.ui.helpers.FileOperationsHelper; import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface; @@ -1146,6 +1149,10 @@ public class OCFileListFragment extends ExtendedListFragment implements } else if (itemId == R.id.action_unset_encrypted) { mContainerActivity.getFileOperationsHelper().toggleEncryption(singleFile, false); return true; + } else if (itemId == R.id.action_lock_file) { + mContainerActivity.getFileOperationsHelper().toggleFileLock(singleFile, true); + } else if (itemId == R.id.action_unlock_file) { + mContainerActivity.getFileOperationsHelper().toggleFileLock(singleFile, false); } } @@ -1192,6 +1199,8 @@ public class OCFileListFragment extends ExtendedListFragment implements } else if (itemId == R.id.action_send_file) { mContainerActivity.getFileOperationsHelper().sendFiles(checkedFiles); return true; + } else if (itemId == R.id.action_lock_file) { + // TODO call lock API } return false; @@ -1642,6 +1651,35 @@ public class OCFileListFragment extends ExtendedListFragment implements } } + @Subscribe(threadMode = ThreadMode.BACKGROUND) + public void onMessageEvent(FileLockEvent event) { + final User user = accountManager.getUser(); + + try { + new Handler(Looper.getMainLooper()).post(() -> setLoading(true)); + NextcloudClient client = clientFactory.createNextcloudClient(user); + ToggleFileLockRemoteOperation operation = new ToggleFileLockRemoteOperation(event.getShouldLock(), event.getFilePath()); + RemoteOperationResult result = operation.execute(client); + + if (result.isSuccess()) { + // TODO only refresh the modified file? + new Handler(Looper.getMainLooper()).post(this::onRefresh); + } else { + Snackbar.make(getRecyclerView(), + R.string.error_file_lock, + Snackbar.LENGTH_LONG).show(); + } + + } catch (ClientFactory.CreationException e) { + Log_OC.e(TAG, "Cannot create client", e); + Snackbar.make(getRecyclerView(), + R.string.error_file_lock, + Snackbar.LENGTH_LONG).show(); + } finally { + new Handler(Looper.getMainLooper()).post(() -> setLoading(false)); + } + } + protected void setTitle(@StringRes final int title) { setTitle(getContext().getString(title)); } diff --git a/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java b/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java index 850e15bfa9..a2b5c20cc6 100755 --- a/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java +++ b/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java @@ -79,6 +79,7 @@ import com.owncloud.android.ui.dialog.SendFilesDialog; import com.owncloud.android.ui.dialog.SendShareDialog; import com.owncloud.android.ui.events.EncryptionEvent; import com.owncloud.android.ui.events.FavoriteEvent; +import com.owncloud.android.ui.events.FileLockEvent; import com.owncloud.android.ui.events.SyncEventFinished; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.FileStorageUtils; @@ -918,6 +919,12 @@ public class FileOperationsHelper { } } + public void toggleFileLock(OCFile file, boolean shouldBeLocked) { + if (file.isLocked() != shouldBeLocked) { + EventBus.getDefault().post(new FileLockEvent(file.getRemotePath(), shouldBeLocked)); + } + } + public void renameFile(OCFile file, String newFilename) { // RenameFile Intent service = new Intent(fileActivity, OperationsService.class); diff --git a/app/src/main/res/layout/list_item.xml b/app/src/main/res/layout/list_item.xml index 2875124bb5..4dbcd39e70 100644 --- a/app/src/main/res/layout/list_item.xml +++ b/app/src/main/res/layout/list_item.xml @@ -80,21 +80,6 @@ app:layout_constraintStart_toEndOf="@+id/thumbnail_layout" app:layout_constraintTop_toTopOf="@+id/thumbnail_layout" /> - - - - @@ -164,7 +150,6 @@ android:id="@+id/unreadComments" android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_centerVertical="true" android:clickable="true" android:contentDescription="@string/unread_comments" android:focusable="true" @@ -177,8 +162,6 @@ android:id="@+id/sharedIcon" android:layout_width="48dp" android:layout_height="match_parent" - android:layout_centerVertical="true" - android:layout_toEndOf="@id/unreadComments" android:clickable="true" android:contentDescription="@string/shared_icon_share" android:focusable="true" @@ -189,19 +172,28 @@ + + - + diff --git a/app/src/main/res/menu/item_file.xml b/app/src/main/res/menu/item_file.xml index 02955f6cf1..cfb88b9697 100644 --- a/app/src/main/res/menu/item_file.xml +++ b/app/src/main/res/menu/item_file.xml @@ -40,6 +40,18 @@ app:showAsAction="never" android:showAsAction="never" /> + + + + Full access Media read-only This file is locked + Lock + Unlock + Error changing file lock status From 68b2535481f3bb85e247080f4a1f85e36cd98610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Brey=20Vilas?= Date: Wed, 20 Apr 2022 16:50:49 +0200 Subject: [PATCH 15/32] Retrieve and persist more locking attributes from remote files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Álvaro Brey Vilas --- .../datamodel/FileDataStorageManager.java | 11 +++ .../owncloud/android/datamodel/OCFile.java | 71 +++++++++++++++++-- .../com/owncloud/android/db/ProviderMeta.java | 10 ++- .../providers/FileContentProvider.java | 14 +++- .../android/utils/FileStorageUtils.java | 4 ++ 5 files changed, 102 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java index 92650b9835..1ca6353977 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -45,6 +45,7 @@ import com.owncloud.android.lib.common.network.WebdavEntry; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation; +import com.owncloud.android.lib.resources.files.model.FileLockType; import com.owncloud.android.lib.resources.files.model.RemoteFile; import com.owncloud.android.lib.resources.shares.OCShare; import com.owncloud.android.lib.resources.shares.ShareType; @@ -495,9 +496,14 @@ public class FileDataStorageManager { cv.put(ProviderTableMeta.FILE_ETAG_IN_CONFLICT, file.getEtagInConflict()); cv.put(ProviderTableMeta.FILE_HAS_PREVIEW, file.isPreviewAvailable() ? 1 : 0); cv.put(ProviderTableMeta.FILE_LOCKED, file.isLocked()); + cv.put(ProviderTableMeta.FILE_LOCK_TYPE, file.getLockType() != null ? file.getLockType().getValue() : -1); cv.put(ProviderTableMeta.FILE_LOCK_OWNER, file.getLockOwnerId()); cv.put(ProviderTableMeta.FILE_LOCK_OWNER_DISPLAY_NAME, file.getLockOwnerDisplayName()); + cv.put(ProviderTableMeta.FILE_LOCK_OWNER_EDITOR, file.getLockOwnerEditor()); cv.put(ProviderTableMeta.FILE_LOCK_TIMESTAMP, file.getLockTimestamp()); + cv.put(ProviderTableMeta.FILE_LOCK_TIMEOUT, file.getLockTimeout()); + cv.put(ProviderTableMeta.FILE_LOCK_TOKEN, file.getLockToken()); + return cv; } @@ -991,9 +997,14 @@ public class FileDataStorageManager { ocFile.setNote(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_NOTE))); ocFile.setRichWorkspace(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_RICH_WORKSPACE))); ocFile.setLocked(cursor.getInt(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCKED)) == 1); + final int lockTypeInt = cursor.getInt(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCK_TYPE)); + ocFile.setLockType(lockTypeInt != -1 ? FileLockType.fromValue(lockTypeInt) : null); ocFile.setLockOwnerId(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCK_OWNER))); ocFile.setLockOwnerDisplayName(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCK_OWNER_DISPLAY_NAME))); + ocFile.setLockOwnerEditor(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCK_OWNER_EDITOR))); ocFile.setLockTimestamp(cursor.getInt(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCK_TIMESTAMP))); + ocFile.setLockTimeout(cursor.getInt(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCK_TIMEOUT))); + ocFile.setLockToken(cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_LOCK_TOKEN))); String sharees = cursor.getString(cursor.getColumnIndexOrThrow(ProviderTableMeta.FILE_SHAREES)); diff --git a/app/src/main/java/com/owncloud/android/datamodel/OCFile.java b/app/src/main/java/com/owncloud/android/datamodel/OCFile.java index 1865da993e..25f87fdef8 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/OCFile.java +++ b/app/src/main/java/com/owncloud/android/datamodel/OCFile.java @@ -33,6 +33,7 @@ import com.owncloud.android.R; import com.owncloud.android.lib.common.network.WebdavEntry; import com.owncloud.android.lib.common.network.WebdavUtils; import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.lib.resources.files.model.FileLockType; import com.owncloud.android.lib.resources.files.model.ServerFileInterface; import com.owncloud.android.lib.resources.shares.ShareeUser; import com.owncloud.android.utils.MimeType; @@ -96,13 +97,22 @@ public class OCFile implements Parcelable, Comparable, ServerFileInterfa private List sharees; private String richWorkspace; private boolean locked; + @Nullable + private FileLockType lockType; + @Nullable private String lockOwnerId; + @Nullable private String lockOwnerDisplayName; + @Nullable + private String lockOwnerEditor; private long lockTimestamp; + private long lockTimeout; + @Nullable + private String lockToken; /** - * URI to the local path of the file contents, if stored in the device; cached after first call - * to {@link #getStorageUri()} + * URI to the local path of the file contents, if stored in the device; cached after first call to {@link + * #getStorageUri()} */ private Uri localUri; @@ -166,10 +176,14 @@ public class OCFile implements Parcelable, Comparable, ServerFileInterfa richWorkspace = source.readString(); previewAvailable = source.readInt() == 1; firstShareTimestamp = source.readLong(); - locked = source.readInt() == 1; + locked = source.readInt() == 1; + lockType = FileLockType.fromValue(source.readInt()); lockOwnerId = source.readString(); lockOwnerDisplayName = source.readString(); + lockOwnerDisplayName = source.readString(); lockTimestamp = source.readLong(); + lockTimeout = source.readLong(); + lockToken = source.readString(); } @Override @@ -204,10 +218,14 @@ public class OCFile implements Parcelable, Comparable, ServerFileInterfa dest.writeString(richWorkspace); dest.writeInt(previewAvailable ? 1 : 0); dest.writeLong(firstShareTimestamp); - dest.writeInt(locked ? 1:0); + dest.writeInt(locked ? 1 : 0); + dest.writeInt(lockType != null ? lockType.getValue() : -1); dest.writeString(lockOwnerId); dest.writeString(lockOwnerDisplayName); + dest.writeString(lockOwnerEditor); dest.writeLong(lockTimestamp); + dest.writeLong(lockTimeout); + dest.writeString(lockToken); } public void setDecryptedRemotePath(String path) { @@ -472,9 +490,13 @@ public class OCFile implements Parcelable, Comparable, ServerFileInterfa richWorkspace = ""; firstShareTimestamp = 0; locked = false; + lockType = null; lockOwnerId = null; lockOwnerDisplayName = null; + lockOwnerEditor = null; lockTimestamp = 0; + lockTimeout = 0; + lockToken = null; } /** @@ -856,22 +878,42 @@ public class OCFile implements Parcelable, Comparable, ServerFileInterfa this.locked = locked; } + @Nullable + public FileLockType getLockType() { + return lockType; + } + + public void setLockType(@Nullable FileLockType lockType) { + this.lockType = lockType; + } + + @Nullable public String getLockOwnerId() { return lockOwnerId; } - public void setLockOwnerId(String lockOwnerId) { + public void setLockOwnerId(@Nullable String lockOwnerId) { this.lockOwnerId = lockOwnerId; } + @Nullable public String getLockOwnerDisplayName() { return lockOwnerDisplayName; } - public void setLockOwnerDisplayName(String lockOwnerDisplayName) { + public void setLockOwnerDisplayName(@Nullable String lockOwnerDisplayName) { this.lockOwnerDisplayName = lockOwnerDisplayName; } + @Nullable + public String getLockOwnerEditor() { + return lockOwnerEditor; + } + + public void setLockOwnerEditor(@Nullable String lockOwnerEditor) { + this.lockOwnerEditor = lockOwnerEditor; + } + public long getLockTimestamp() { return lockTimestamp; } @@ -879,4 +921,21 @@ public class OCFile implements Parcelable, Comparable, ServerFileInterfa public void setLockTimestamp(long lockTimestamp) { this.lockTimestamp = lockTimestamp; } + + public long getLockTimeout() { + return lockTimeout; + } + + public void setLockTimeout(long lockTimeout) { + this.lockTimeout = lockTimeout; + } + + @Nullable + public String getLockToken() { + return lockToken; + } + + public void setLockToken(@Nullable String lockToken) { + this.lockToken = lockToken; + } } diff --git a/app/src/main/java/com/owncloud/android/db/ProviderMeta.java b/app/src/main/java/com/owncloud/android/db/ProviderMeta.java index d6dbfaa6d0..ceed28adb4 100644 --- a/app/src/main/java/com/owncloud/android/db/ProviderMeta.java +++ b/app/src/main/java/com/owncloud/android/db/ProviderMeta.java @@ -118,9 +118,13 @@ public class ProviderMeta { public static final String FILE_SHAREES = "sharees"; public static final String FILE_RICH_WORKSPACE = "rich_workspace"; public static final String FILE_LOCKED = "locked"; + public static final String FILE_LOCK_TYPE = "lock_type"; public static final String FILE_LOCK_OWNER = "lock_owner"; public static final String FILE_LOCK_OWNER_DISPLAY_NAME = "lock_owner_display_name"; + public static final String FILE_LOCK_OWNER_EDITOR = "lock_owner_editor"; public static final String FILE_LOCK_TIMESTAMP = "lock_timestamp"; + public static final String FILE_LOCK_TIMEOUT = "lock_timeout"; + public static final String FILE_LOCK_TOKEN = "lock_token"; public static final List FILE_ALL_COLUMNS = Collections.unmodifiableList(Arrays.asList( _ID, @@ -159,9 +163,13 @@ public class ProviderMeta { FILE_SHAREES, FILE_RICH_WORKSPACE, FILE_LOCKED, + FILE_LOCK_TYPE, FILE_LOCK_OWNER, FILE_LOCK_OWNER_DISPLAY_NAME, - FILE_LOCK_TIMESTAMP)); + FILE_LOCK_OWNER_EDITOR, + FILE_LOCK_TIMESTAMP, + FILE_LOCK_TIMEOUT, + FILE_LOCK_TOKEN)); public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME + " collate nocase asc"; // Columns of ocshares table diff --git a/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java b/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java index d6e718026d..bace44ab43 100644 --- a/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java +++ b/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java @@ -755,9 +755,13 @@ public class FileContentProvider extends ContentProvider { + ProviderTableMeta.FILE_SHAREES + TEXT + ProviderTableMeta.FILE_RICH_WORKSPACE + TEXT + ProviderTableMeta.FILE_LOCKED + INTEGER // boolean + + ProviderTableMeta.FILE_LOCK_TYPE + INTEGER + ProviderTableMeta.FILE_LOCK_OWNER + TEXT + ProviderTableMeta.FILE_LOCK_OWNER_DISPLAY_NAME + TEXT - + ProviderTableMeta.FILE_LOCK_TIMESTAMP + " INTEGER );" + + ProviderTableMeta.FILE_LOCK_OWNER_EDITOR + TEXT + + ProviderTableMeta.FILE_LOCK_TIMESTAMP + INTEGER + + ProviderTableMeta.FILE_LOCK_TIMEOUT + INTEGER + + ProviderTableMeta.FILE_LOCK_TOKEN + " TEXT );" ); } @@ -2470,12 +2474,20 @@ public class FileContentProvider extends ContentProvider { // locking properties db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + ADD_COLUMN + ProviderTableMeta.FILE_LOCKED + " INTEGER "); // boolean + db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + + ADD_COLUMN + ProviderTableMeta.FILE_LOCK_TYPE + " INTEGER "); db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + ADD_COLUMN + ProviderTableMeta.FILE_LOCK_OWNER + " TEXT "); db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + ADD_COLUMN + ProviderTableMeta.FILE_LOCK_OWNER_DISPLAY_NAME + " TEXT "); + db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + + ADD_COLUMN + ProviderTableMeta.FILE_LOCK_OWNER_EDITOR + " TEXT "); db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + ADD_COLUMN + ProviderTableMeta.FILE_LOCK_TIMESTAMP + " INTEGER "); + db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + + ADD_COLUMN + ProviderTableMeta.FILE_LOCK_TIMEOUT + " INTEGER "); + db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME + + ADD_COLUMN + ProviderTableMeta.FILE_LOCK_TOKEN + " TEXT "); db.execSQL("UPDATE " + ProviderTableMeta.FILE_TABLE_NAME + " SET " + ProviderTableMeta.FILE_ETAG + " = '' WHERE 1=1"); db.setTransactionSuccessful(); diff --git a/app/src/main/java/com/owncloud/android/utils/FileStorageUtils.java b/app/src/main/java/com/owncloud/android/utils/FileStorageUtils.java index 70313c3dbe..b8b013f806 100644 --- a/app/src/main/java/com/owncloud/android/utils/FileStorageUtils.java +++ b/app/src/main/java/com/owncloud/android/utils/FileStorageUtils.java @@ -234,9 +234,13 @@ public final class FileStorageUtils { file.setSharees(new ArrayList<>(Arrays.asList(remote.getSharees()))); file.setRichWorkspace(remote.getRichWorkspace()); file.setLocked(remote.isLocked()); + file.setLockType(remote.getLockType()); file.setLockOwnerId(remote.getLockOwner()); file.setLockOwnerDisplayName(remote.getLockOwnerDisplayName()); + file.setLockOwnerEditor(remote.getLockOwnerEditor()); file.setLockTimestamp(remote.getLockTimestamp()); + file.setLockTimeout(remote.getLockTimeout()); + file.setLockToken(remote.getLockToken()); return file; } From a88b03a0c88eed200626da6b074b002ec3addab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Brey=20Vilas?= Date: Wed, 20 Apr 2022 17:39:01 +0200 Subject: [PATCH 16/32] Show lock details when tapping on lock indicator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Álvaro Brey Vilas --- .../java/com/nextcloud/utils/TimeConstants.kt | 28 ++++ .../android/ui/adapter/OCFileListAdapter.java | 1 + .../ui/dialog/LockInfoDialogFragment.kt | 138 ++++++++++++++++++ .../ui/fragment/OCFileListFragment.java | 11 +- .../OCFileListFragmentInterface.java | 2 + app/src/main/res/layout/lock_info_dialog.xml | 61 ++++++++ app/src/main/res/values/strings.xml | 4 + 7 files changed, 243 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/utils/TimeConstants.kt create mode 100644 app/src/main/java/com/owncloud/android/ui/dialog/LockInfoDialogFragment.kt create mode 100644 app/src/main/res/layout/lock_info_dialog.xml diff --git a/app/src/main/java/com/nextcloud/utils/TimeConstants.kt b/app/src/main/java/com/nextcloud/utils/TimeConstants.kt new file mode 100644 index 0000000000..881514ea33 --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/TimeConstants.kt @@ -0,0 +1,28 @@ +/* + * Nextcloud Android client application + * + * @author Álvaro Brey Vilas + * Copyright (C) 2022 Álvaro Brey Vilas + * Copyright (C) 2022 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.utils + +@Suppress("MagicNumber") +object TimeConstants { + @JvmStatic + val MILLIS_PER_SECOND = 1000 +} diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java index 6ff09d432d..5f2f377143 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java @@ -469,6 +469,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter ocFileListFragmentInterface.onLockIndicatorClicked(file)); } else { holder.getLockIndicator().setVisibility(View.GONE); } diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/LockInfoDialogFragment.kt b/app/src/main/java/com/owncloud/android/ui/dialog/LockInfoDialogFragment.kt new file mode 100644 index 0000000000..da2db0d13d --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/dialog/LockInfoDialogFragment.kt @@ -0,0 +1,138 @@ +/* + * Nextcloud Android client application + * + * @author Álvaro Brey Vilas + * Copyright (C) 2022 Álvaro Brey Vilas + * Copyright (C) 2022 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.owncloud.android.ui.dialog + +import android.app.Dialog +import android.graphics.Typeface +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.text.style.StyleSpan +import android.view.View +import android.widget.ImageView +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.nextcloud.client.account.User +import com.nextcloud.utils.TimeConstants +import com.owncloud.android.R +import com.owncloud.android.databinding.LockInfoDialogBinding +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.utils.DisplayUtils +import com.owncloud.android.utils.theme.ThemeButtonUtils + +/** + * Dialog that shows lock information for a locked file + */ +class LockInfoDialogFragment(private val user: User, private val file: OCFile) : + DialogFragment(), + DisplayUtils.AvatarGenerationListener { + private lateinit var binding: LockInfoDialogBinding + + override fun onStart() { + super.onStart() + dialog?.let { + val alertDialog = it as AlertDialog + ThemeButtonUtils.themeBorderlessButton(alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)) + } + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + // Inflate the layout for the dialog + val inflater = requireActivity().layoutInflater + binding = LockInfoDialogBinding.inflate(inflater, null, false) + + val view = binding.root + + // Setup layout + if (file.isLocked) { + binding.lockedByUsername.text = getUserAndTimestampText() + loadAvatar() + setRemainingTime() + } else { + setNotLocked() + } + + // Build the dialog + val dialog = MaterialAlertDialogBuilder(requireActivity(), R.style.Theme_ownCloud_Dialog) + .setTitle(R.string.file_lock_dialog_title) + .setView(view) + .setPositiveButton(R.string.dismiss) { _, _ -> + dismiss() + } + .create() + + return dialog + } + + private fun getUserAndTimestampText(): CharSequence { + val username = file.lockOwnerDisplayName ?: file.lockOwnerId + val lockTimestampMillis = file.lockTimestamp * TimeConstants.MILLIS_PER_SECOND + return DisplayUtils.createTextWithSpan( + getString( + R.string.locked_by_user_date, username, DisplayUtils.unixTimeToHumanReadable(lockTimestampMillis) + ), + username, + StyleSpan(Typeface.BOLD) + ) + } + + private fun loadAvatar() { + DisplayUtils.setAvatar( + user, + file.lockOwnerId!!, + file.lockOwnerDisplayName, + this, + requireContext().resources.getDimension(R.dimen.list_item_avatar_icon_radius), + resources, + binding.lockedByAvatar, + context + ) + } + + private fun setRemainingTime() { + if (file.lockTimestamp == 0L || file.lockTimeout == 0L) { + binding.lockExpiration.visibility = View.GONE + } else { + val expirationText = getExpirationRelativeText() + binding.lockExpiration.text = getString(R.string.lock_expiration_info, expirationText) + binding.lockExpiration.visibility = View.VISIBLE + } + } + + private fun getExpirationRelativeText(): CharSequence? { + val expirationTimestamp = (file.lockTimestamp + file.lockTimeout) * TimeConstants.MILLIS_PER_SECOND + return DisplayUtils.getRelativeTimestamp(context, expirationTimestamp, true) + } + + private fun setNotLocked() { + binding.lockExpiration.visibility = View.GONE + binding.lockedByAvatar.visibility = View.GONE + binding.lockedByUsername.text = getString(R.string.file_not_locked) + } + + override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any) { + (callContext as ImageView).setImageDrawable(avatarDrawable) + } + + override fun shouldCallGeneratedCallback(tag: String, callContext: Any): Boolean { + return (callContext as ImageView).tag == tag + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index aca561b797..39b5bd3f21 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -86,6 +86,7 @@ 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; +import com.owncloud.android.ui.dialog.LockInfoDialogFragment; import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment; import com.owncloud.android.ui.dialog.RenameFileDialogFragment; import com.owncloud.android.ui.dialog.SetupEncryptionDialogFragment; @@ -184,6 +185,7 @@ public class OCFileListFragment extends ExtendedListFragment implements private static final String DIALOG_CREATE_FOLDER = "DIALOG_CREATE_FOLDER"; private static final String DIALOG_CREATE_DOCUMENT = "DIALOG_CREATE_DOCUMENT"; private static final String DIALOG_BOTTOM_SHEET = "DIALOG_BOTTOM_SHEET"; + private static final String DIALOG_LOCK_DETAILS = "DIALOG_LOCK_DETAILS"; private static final int SINGLE_SELECTION = 1; private static final int NOT_ENOUGH_SPACE_FRAG_REQUEST_CODE = 2; @@ -606,7 +608,7 @@ public class OCFileListFragment extends ExtendedListFragment implements public void newPresentation() { ChooseRichDocumentsTemplateDialogFragment.newInstance(mFile, ChooseRichDocumentsTemplateDialogFragment.Type.PRESENTATION) - .show(requireActivity().getSupportFragmentManager(), DIALOG_CREATE_DOCUMENT); + .show(requireActivity().getSupportFragmentManager(), DIALOG_CREATE_DOCUMENT); } @Override @@ -616,10 +618,15 @@ public class OCFileListFragment extends ExtendedListFragment implements } } + @Override + public void onLockIndicatorClicked(OCFile file) { + new LockInfoDialogFragment(accountManager.getUser(), file).show(requireActivity().getSupportFragmentManager(), DIALOG_LOCK_DETAILS); + } + @Override public void showTemplate(Creator creator, String headline) { ChooseTemplateDialogFragment.newInstance(mFile, creator, headline).show(requireActivity().getSupportFragmentManager(), - DIALOG_CREATE_DOCUMENT); + DIALOG_CREATE_DOCUMENT); } /** diff --git a/app/src/main/java/com/owncloud/android/ui/interfaces/OCFileListFragmentInterface.java b/app/src/main/java/com/owncloud/android/ui/interfaces/OCFileListFragmentInterface.java index 5b2fd546d5..b884f236bf 100644 --- a/app/src/main/java/com/owncloud/android/ui/interfaces/OCFileListFragmentInterface.java +++ b/app/src/main/java/com/owncloud/android/ui/interfaces/OCFileListFragmentInterface.java @@ -48,4 +48,6 @@ public interface OCFileListFragmentInterface { boolean isLoading(); void onHeaderClicked(); + + void onLockIndicatorClicked(OCFile file); } diff --git a/app/src/main/res/layout/lock_info_dialog.xml b/app/src/main/res/layout/lock_info_dialog.xml new file mode 100644 index 0000000000..b7bfec1b52 --- /dev/null +++ b/app/src/main/res/layout/lock_info_dialog.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ee60212356..760ac249e3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1012,4 +1012,8 @@ Lock Unlock Error changing file lock status + Lock details + Locked by %1$s at %2$s + Expires: %1$s + File is not locked From a902fb6f845f7f19e45dbfb5bf2c83c140b66915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Brey=20Vilas?= Date: Thu, 21 Apr 2022 12:12:45 +0200 Subject: [PATCH 17/32] FileMenuFilter: properly filter lock/unlock depending on lock status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Álvaro Brey Vilas --- .../android/files/FileLockingHelper.kt | 38 +++++++++++++ .../android/files/FileMenuFilter.java | 55 +++++++++++-------- 2 files changed, 71 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/android/files/FileLockingHelper.kt diff --git a/app/src/main/java/com/nextcloud/android/files/FileLockingHelper.kt b/app/src/main/java/com/nextcloud/android/files/FileLockingHelper.kt new file mode 100644 index 0000000000..849903102a --- /dev/null +++ b/app/src/main/java/com/nextcloud/android/files/FileLockingHelper.kt @@ -0,0 +1,38 @@ +/* + * Nextcloud Android client application + * + * @author Álvaro Brey Vilas + * Copyright (C) 2022 Álvaro Brey Vilas + * Copyright (C) 2022 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.android.files + +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.lib.resources.files.model.FileLockType + +object FileLockingHelper { + /** + * Checks whether the given `userId` can unlock the [OCFile]. + */ + @JvmStatic + fun canUserUnlockFile(userId: String, file: OCFile): Boolean { + if (!file.isLocked || file.lockOwnerId == null || file.lockType != FileLockType.MANUAL) { + return false + } + return file.lockOwnerId == userId + } +} diff --git a/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java b/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java index 4020dcbc74..00455523e8 100644 --- a/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java +++ b/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java @@ -21,12 +21,14 @@ package com.owncloud.android.files; +import android.accounts.AccountManager; import android.content.ContentResolver; import android.content.Context; import android.view.Menu; import android.view.MenuItem; import com.google.gson.Gson; +import com.nextcloud.android.files.FileLockingHelper; import com.nextcloud.client.account.User; import com.owncloud.android.R; import com.owncloud.android.datamodel.ArbitraryDataProvider; @@ -58,12 +60,13 @@ public class FileMenuFilter { private static final int SINGLE_SELECT_ITEMS = 1; public static final String SEND_OFF = "off"; - private int numberOfAllFiles; - private Collection files; - private ComponentsGetter componentsGetter; - private Context context; - private boolean overflowMenu; - private User user; + private final int numberOfAllFiles; + private final Collection files; + private final ComponentsGetter componentsGetter; + private final Context context; + private final boolean overflowMenu; + private final User user; + private final String userId; /** * Constructor @@ -88,6 +91,10 @@ public class FileMenuFilter { this.context = context; this.overflowMenu = overflowMenu; this.user = user; + userId = AccountManager + .get(context) + .getUserData(this.user.toPlatformAccount(), + com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_USER_ID); } /** @@ -202,6 +209,7 @@ public class FileMenuFilter { filterSetPictureAs(toShow, toHide); filterStream(toShow, toHide); filterLock(toShow, toHide); + filterUnlock(toShow, toHide); } private void filterShareFile(List toShow, List toHide, OCCapability capability) { @@ -254,16 +262,28 @@ public class FileMenuFilter { } private void filterLock(List toShow, List toHide) { - // TODO only allow locking files (not dirs), and only allow unlocking file if lock is owned by the user - if (files.isEmpty()) { + if (files.isEmpty() || !isSingleSelection()) { toHide.add(R.id.action_lock_file); - toHide.add(R.id.action_unlock_file); - } else if (allLocked()) { - toHide.add(R.id.action_lock_file); - toShow.add(R.id.action_unlock_file); } else { + OCFile file = files.iterator().next(); + if (file.isLocked() || file.isFolder()) { + toHide.add(R.id.action_lock_file); + } else { + toShow.add(R.id.action_lock_file); + } + } + } + + private void filterUnlock(List toShow, List toHide) { + if (files.isEmpty() || !isSingleSelection()) { toHide.add(R.id.action_unlock_file); - toShow.add(R.id.action_lock_file); + } else { + OCFile file = files.iterator().next(); + if (FileLockingHelper.canUserUnlockFile(userId, file)) { + toShow.add(R.id.action_unlock_file); + } else { + toHide.add(R.id.action_unlock_file); + } } } @@ -587,13 +607,4 @@ public class FileMenuFilter { } return true; } - - private boolean allLocked() { - for (OCFile file : files) { - if (!file.isLocked()) { - return false; - } - } - return true; - } } From 46ccddcef40097654a76fe5885e7664cada45336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Brey=20Vilas?= Date: Thu, 21 Apr 2022 12:42:48 +0200 Subject: [PATCH 18/32] Combine lock indicator with overflow icon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Requested by design Signed-off-by: Álvaro Brey Vilas --- .../android/ui/adapter/ListItemViewHolder.kt | 1 - .../android/ui/adapter/OCFileListAdapter.java | 5 ++--- .../android/ui/adapter/OCFileListItemViewHolder.kt | 2 -- app/src/main/res/drawable/ic_locked_dots_small.xml | 13 +++++++++++++ app/src/main/res/layout/list_item.xml | 13 ------------- 5 files changed, 15 insertions(+), 19 deletions(-) create mode 100644 app/src/main/res/drawable/ic_locked_dots_small.xml diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ListItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/ListItemViewHolder.kt index 4defcee17d..e084283d86 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/ListItemViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/ListItemViewHolder.kt @@ -32,5 +32,4 @@ internal interface ListItemViewHolder : ListGridItemViewHolder { val lastModification: TextView val overflowMenu: ImageView val sharedAvatars: AvatarGroupLayout - val lockIndicator: View } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java index 5f2f377143..70c91d9512 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java @@ -468,10 +468,9 @@ public class OCFileListAdapter extends RecyclerView.Adapter ocFileListFragmentInterface.onLockIndicatorClicked(file)); + holder.getOverflowMenu().setImageResource(R.drawable.ic_locked_dots_small); } else { - holder.getLockIndicator().setVisibility(View.GONE); + holder.getOverflowMenu().setImageResource(R.drawable.ic_dots_vertical); } } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt index 508e41f36e..70a725ea69 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt @@ -62,8 +62,6 @@ internal class OCFileListItemViewHolder(private var binding: ListItemBinding) : get() = binding.ListItemLayout override val unreadComments: ImageView get() = binding.unreadComments - override val lockIndicator: View - get() = binding.lockIndicator init { binding.favoriteAction.drawable.mutate() diff --git a/app/src/main/res/drawable/ic_locked_dots_small.xml b/app/src/main/res/drawable/ic_locked_dots_small.xml new file mode 100644 index 0000000000..7d43c2b832 --- /dev/null +++ b/app/src/main/res/drawable/ic_locked_dots_small.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/layout/list_item.xml b/app/src/main/res/layout/list_item.xml index 4dbcd39e70..2538d6199d 100644 --- a/app/src/main/res/layout/list_item.xml +++ b/app/src/main/res/layout/list_item.xml @@ -177,19 +177,6 @@ android:contentDescription="@string/shared_avatar_desc" android:visibility="visible" /> - - Date: Thu, 21 Apr 2022 18:00:43 +0200 Subject: [PATCH 19/32] Show file lock info in actions menu instead of separate dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Requested by design team Signed-off-by: Álvaro Brey Vilas --- .../android/files/FileActionsPopupMenu.kt | 94 ++++++++++++ .../java/com/nextcloud/utils/TimeConstants.kt | 4 +- .../android/files/FileMenuFilter.java | 30 +++- .../ui/dialog/LockInfoDialogFragment.kt | 138 ------------------ .../ui/fragment/OCFileListFragment.java | 17 +-- .../OCFileListFragmentInterface.java | 2 - .../main/res/color/popup_item_text_color.xml | 25 ++++ app/src/main/res/layout/lock_info_dialog.xml | 61 -------- app/src/main/res/menu/item_file.xml | 55 +++++-- app/src/main/res/values/strings.xml | 7 +- app/src/main/res/values/styles.xml | 4 + 11 files changed, 202 insertions(+), 235 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/android/files/FileActionsPopupMenu.kt delete mode 100644 app/src/main/java/com/owncloud/android/ui/dialog/LockInfoDialogFragment.kt create mode 100644 app/src/main/res/color/popup_item_text_color.xml delete mode 100644 app/src/main/res/layout/lock_info_dialog.xml diff --git a/app/src/main/java/com/nextcloud/android/files/FileActionsPopupMenu.kt b/app/src/main/java/com/nextcloud/android/files/FileActionsPopupMenu.kt new file mode 100644 index 0000000000..650c957db5 --- /dev/null +++ b/app/src/main/java/com/nextcloud/android/files/FileActionsPopupMenu.kt @@ -0,0 +1,94 @@ +/* + * Nextcloud Android client application + * + * @author Álvaro Brey Vilas + * Copyright (C) 2022 Álvaro Brey Vilas + * Copyright (C) 2022 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.android.files + +import android.content.Context +import android.graphics.Typeface +import android.os.Build +import android.text.style.StyleSpan +import android.view.ContextThemeWrapper +import android.view.MenuItem +import android.view.View +import androidx.appcompat.widget.PopupMenu +import com.nextcloud.utils.TimeConstants +import com.owncloud.android.R +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.files.FileMenuFilter +import com.owncloud.android.lib.resources.files.model.FileLockType +import com.owncloud.android.utils.DisplayUtils + +/** + * Wrapper around PopupMenu with file locking info + */ +class FileActionsPopupMenu( + private val context: Context, + anchor: View, + private val file: OCFile, + fileMenuFilter: FileMenuFilter +) : PopupMenu(wrapContext(context), anchor) { + + init { + this.inflate(R.menu.item_file) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + menu.setGroupDividerEnabled(true) + } + fileMenuFilter.filter(menu, true) + + if (file.isLocked) { + menu.findItem(R.id.action_locked_by).title = getLockedByText() + showLockedUntil() + } + } + + private fun showLockedUntil() { + val lockedUntil: MenuItem = menu.findItem(R.id.action_locked_until) + if (file.lockTimestamp == 0L || file.lockTimeout == 0L) { + lockedUntil.isVisible = false + } else { + lockedUntil.title = context.getString(R.string.lock_expiration_info, getExpirationRelativeText()) + lockedUntil.isVisible = true + } + } + + private fun getLockedByText(): CharSequence { + val username = file.lockOwnerDisplayName ?: file.lockOwnerId + val resource = when (file.lockType) { + FileLockType.COLLABORATIVE -> R.string.locked_by_app + else -> R.string.locked_by + } + return DisplayUtils.createTextWithSpan( + context.getString(resource, username), + username, + StyleSpan(Typeface.BOLD) + ) + } + + private fun getExpirationRelativeText(): CharSequence? { + val expirationTimestamp = (file.lockTimestamp + file.lockTimeout) * TimeConstants.MILLIS_PER_SECOND + return DisplayUtils.getRelativeTimestamp(context, expirationTimestamp, true) + } + + companion object { + private fun wrapContext(context: Context): Context = + ContextThemeWrapper(context, R.style.Nextcloud_Widget_PopupMenu) + } +} diff --git a/app/src/main/java/com/nextcloud/utils/TimeConstants.kt b/app/src/main/java/com/nextcloud/utils/TimeConstants.kt index 881514ea33..af9bae28fd 100644 --- a/app/src/main/java/com/nextcloud/utils/TimeConstants.kt +++ b/app/src/main/java/com/nextcloud/utils/TimeConstants.kt @@ -21,8 +21,6 @@ package com.nextcloud.utils -@Suppress("MagicNumber") object TimeConstants { - @JvmStatic - val MILLIS_PER_SECOND = 1000 + const val MILLIS_PER_SECOND = 1000 } diff --git a/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java b/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java index 00455523e8..15cfac0861 100644 --- a/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java +++ b/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java @@ -132,11 +132,23 @@ public class FileMenuFilter { filter(toShow, toHide, inSingleFileFragment); for (int i : toShow) { - showMenuItem(menu.findItem(i)); + final MenuItem item = menu.findItem(i); + if (item != null) { + showMenuItem(item); + } else { + // group + menu.setGroupVisible(i, true); + } } for (int i : toHide) { - hideMenuItem(menu.findItem(i)); + final MenuItem item = menu.findItem(i); + if (item != null) { + hideMenuItem(item); + } else { + // group + menu.setGroupVisible(i, false); + } } } } @@ -210,6 +222,7 @@ public class FileMenuFilter { filterStream(toShow, toHide); filterLock(toShow, toHide); filterUnlock(toShow, toHide); + filterLockInfo(toShow, toHide); } private void filterShareFile(List toShow, List toHide, OCCapability capability) { @@ -287,6 +300,19 @@ public class FileMenuFilter { } } + private void filterLockInfo(List toShow, List toHide) { + if (files.isEmpty() || !isSingleSelection()) { + toHide.add(R.id.menu_group_lock_info); + } else { + OCFile file = files.iterator().next(); + if (file.isLocked()) { + toShow.add(R.id.menu_group_lock_info); + } else { + toHide.add(R.id.menu_group_lock_info); + } + } + } + private void filterEncrypt(List toShow, List toHide, boolean endToEndEncryptionEnabled) { if (files.isEmpty() || !isSingleSelection() || isSingleFile() || isEncryptedFolder() || !endToEndEncryptionEnabled) { diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/LockInfoDialogFragment.kt b/app/src/main/java/com/owncloud/android/ui/dialog/LockInfoDialogFragment.kt deleted file mode 100644 index da2db0d13d..0000000000 --- a/app/src/main/java/com/owncloud/android/ui/dialog/LockInfoDialogFragment.kt +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Nextcloud Android client application - * - * @author Álvaro Brey Vilas - * Copyright (C) 2022 Álvaro Brey Vilas - * Copyright (C) 2022 Nextcloud GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.owncloud.android.ui.dialog - -import android.app.Dialog -import android.graphics.Typeface -import android.graphics.drawable.Drawable -import android.os.Bundle -import android.text.style.StyleSpan -import android.view.View -import android.widget.ImageView -import androidx.appcompat.app.AlertDialog -import androidx.fragment.app.DialogFragment -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.nextcloud.client.account.User -import com.nextcloud.utils.TimeConstants -import com.owncloud.android.R -import com.owncloud.android.databinding.LockInfoDialogBinding -import com.owncloud.android.datamodel.OCFile -import com.owncloud.android.utils.DisplayUtils -import com.owncloud.android.utils.theme.ThemeButtonUtils - -/** - * Dialog that shows lock information for a locked file - */ -class LockInfoDialogFragment(private val user: User, private val file: OCFile) : - DialogFragment(), - DisplayUtils.AvatarGenerationListener { - private lateinit var binding: LockInfoDialogBinding - - override fun onStart() { - super.onStart() - dialog?.let { - val alertDialog = it as AlertDialog - ThemeButtonUtils.themeBorderlessButton(alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)) - } - } - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - // Inflate the layout for the dialog - val inflater = requireActivity().layoutInflater - binding = LockInfoDialogBinding.inflate(inflater, null, false) - - val view = binding.root - - // Setup layout - if (file.isLocked) { - binding.lockedByUsername.text = getUserAndTimestampText() - loadAvatar() - setRemainingTime() - } else { - setNotLocked() - } - - // Build the dialog - val dialog = MaterialAlertDialogBuilder(requireActivity(), R.style.Theme_ownCloud_Dialog) - .setTitle(R.string.file_lock_dialog_title) - .setView(view) - .setPositiveButton(R.string.dismiss) { _, _ -> - dismiss() - } - .create() - - return dialog - } - - private fun getUserAndTimestampText(): CharSequence { - val username = file.lockOwnerDisplayName ?: file.lockOwnerId - val lockTimestampMillis = file.lockTimestamp * TimeConstants.MILLIS_PER_SECOND - return DisplayUtils.createTextWithSpan( - getString( - R.string.locked_by_user_date, username, DisplayUtils.unixTimeToHumanReadable(lockTimestampMillis) - ), - username, - StyleSpan(Typeface.BOLD) - ) - } - - private fun loadAvatar() { - DisplayUtils.setAvatar( - user, - file.lockOwnerId!!, - file.lockOwnerDisplayName, - this, - requireContext().resources.getDimension(R.dimen.list_item_avatar_icon_radius), - resources, - binding.lockedByAvatar, - context - ) - } - - private fun setRemainingTime() { - if (file.lockTimestamp == 0L || file.lockTimeout == 0L) { - binding.lockExpiration.visibility = View.GONE - } else { - val expirationText = getExpirationRelativeText() - binding.lockExpiration.text = getString(R.string.lock_expiration_info, expirationText) - binding.lockExpiration.visibility = View.VISIBLE - } - } - - private fun getExpirationRelativeText(): CharSequence? { - val expirationTimestamp = (file.lockTimestamp + file.lockTimeout) * TimeConstants.MILLIS_PER_SECOND - return DisplayUtils.getRelativeTimestamp(context, expirationTimestamp, true) - } - - private fun setNotLocked() { - binding.lockExpiration.visibility = View.GONE - binding.lockedByAvatar.visibility = View.GONE - binding.lockedByUsername.text = getString(R.string.file_not_locked) - } - - override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any) { - (callContext as ImageView).setImageDrawable(avatarDrawable) - } - - override fun shouldCallGeneratedCallback(tag: String, callContext: Any): Boolean { - return (callContext as ImageView).tag == tag - } -} diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 39b5bd3f21..d959cd76c6 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -41,13 +41,13 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; -import android.widget.PopupMenu; import android.widget.Toast; import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.behavior.HideBottomViewOnScrollBehavior; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.snackbar.Snackbar; +import com.nextcloud.android.files.FileActionsPopupMenu; import com.nextcloud.android.lib.resources.files.ToggleFileLockRemoteOperation; import com.nextcloud.android.lib.richWorkspace.RichWorkspaceDirectEditingRemoteOperation; import com.nextcloud.client.account.User; @@ -86,7 +86,6 @@ 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; -import com.owncloud.android.ui.dialog.LockInfoDialogFragment; import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment; import com.owncloud.android.ui.dialog.RenameFileDialogFragment; import com.owncloud.android.ui.dialog.SetupEncryptionDialogFragment; @@ -573,19 +572,20 @@ public class OCFileListFragment extends ExtendedListFragment implements @Override public void onOverflowIconClicked(OCFile file, View view) { throttler.run("overflowClick", () -> { - PopupMenu popup = new PopupMenu(getActivity(), view); - popup.inflate(R.menu.item_file); FileMenuFilter mf = new FileMenuFilter(mAdapter.getFiles().size(), Collections.singleton(file), mContainerActivity, getActivity(), true, accountManager.getUser()); - mf.filter(popup.getMenu(), true); + + final FileActionsPopupMenu popup = new FileActionsPopupMenu(requireContext(), view, file, mf); + popup.setOnMenuItemClickListener(item -> { Set checkedFiles = new HashSet<>(); checkedFiles.add(file); return onFileActionChosen(item, checkedFiles); }); + popup.show(); }); } @@ -594,7 +594,7 @@ public class OCFileListFragment extends ExtendedListFragment implements public void newDocument() { ChooseRichDocumentsTemplateDialogFragment.newInstance(mFile, ChooseRichDocumentsTemplateDialogFragment.Type.DOCUMENT) - .show(requireActivity().getSupportFragmentManager(), DIALOG_CREATE_DOCUMENT); + .show(requireActivity().getSupportFragmentManager(), DIALOG_CREATE_DOCUMENT); } @Override @@ -618,11 +618,6 @@ public class OCFileListFragment extends ExtendedListFragment implements } } - @Override - public void onLockIndicatorClicked(OCFile file) { - new LockInfoDialogFragment(accountManager.getUser(), file).show(requireActivity().getSupportFragmentManager(), DIALOG_LOCK_DETAILS); - } - @Override public void showTemplate(Creator creator, String headline) { ChooseTemplateDialogFragment.newInstance(mFile, creator, headline).show(requireActivity().getSupportFragmentManager(), diff --git a/app/src/main/java/com/owncloud/android/ui/interfaces/OCFileListFragmentInterface.java b/app/src/main/java/com/owncloud/android/ui/interfaces/OCFileListFragmentInterface.java index b884f236bf..5b2fd546d5 100644 --- a/app/src/main/java/com/owncloud/android/ui/interfaces/OCFileListFragmentInterface.java +++ b/app/src/main/java/com/owncloud/android/ui/interfaces/OCFileListFragmentInterface.java @@ -48,6 +48,4 @@ public interface OCFileListFragmentInterface { boolean isLoading(); void onHeaderClicked(); - - void onLockIndicatorClicked(OCFile file); } diff --git a/app/src/main/res/color/popup_item_text_color.xml b/app/src/main/res/color/popup_item_text_color.xml new file mode 100644 index 0000000000..546c45680f --- /dev/null +++ b/app/src/main/res/color/popup_item_text_color.xml @@ -0,0 +1,25 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/lock_info_dialog.xml b/app/src/main/res/layout/lock_info_dialog.xml deleted file mode 100644 index b7bfec1b52..0000000000 --- a/app/src/main/res/layout/lock_info_dialog.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/item_file.xml b/app/src/main/res/menu/item_file.xml index cfb88b9697..6b3da5c029 100644 --- a/app/src/main/res/menu/item_file.xml +++ b/app/src/main/res/menu/item_file.xml @@ -18,9 +18,40 @@ along with this program. If not, see . -->

+ xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + tools:ignore="AppCompatResource"> + + + + + + + + + - - - - + + Full access Media read-only This file is locked - Lock - Unlock + Lock file + Unlock file Error changing file lock status Lock details - Locked by %1$s at %2$s + Locked by %1$s + Locked by %1$s app Expires: %1$s File is not locked diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 7bfe9da706..2e107829e1 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -386,6 +386,10 @@ @color/text_color + + + +