Merge branch 'master' into media-view-options

This commit is contained in:
surinder-tsys 2022-05-04 10:16:30 +05:30 committed by GitHub
commit 2d6b504284
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 1214 additions and 292 deletions

View file

@ -1,3 +1,15 @@
## 3.20.0 (May 3rd, 2022)
- Built-in PDF viewer
- Built-in document scanner
- Better choices for storage permissions
- File locking support
- Better UI for media gallery
- Many bugfixes and improvements
Minimum: NC 16 Server, Android 6.0 Marshmallow
For a full list, please see https://github.com/nextcloud/android/milestone/64
## 3.19.1 (March 10, 2022)
- Minor fixes and improvements

View file

@ -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
@ -250,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"
}
@ -284,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"
@ -327,6 +318,7 @@ dependencies {
// Android JUnit Runner
androidTestImplementation "androidx.test:runner:$androidxTestVersion"
androidTestUtil "androidx.test:orchestrator:$androidxTestVersion"
androidTestImplementation "androidx.test:core-ktx:$androidxTestVersion"
// Espresso
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"

View file

@ -202,7 +202,9 @@ naming:
functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$'
excludeClassPattern: '$^'
ignoreOverridden: true
excludes: "**/*Test.kt"
excludes:
- "**/*Test.kt"
- "**/*IT.kt"
FunctionParameterNaming:
active: true
parameterPattern: '[a-z][A-Za-z0-9]*'
@ -324,7 +326,9 @@ style:
ignoreAnnotation: false
ignoreNamedArgument: true
ignoreEnums: false
excludes: "**/*Test.kt"
excludes:
- "**/*Test.kt"
- "**/*IT.kt"
MandatoryBracesIfStatements:
active: false
MaxLineLength:

View file

@ -36,7 +36,7 @@ import org.mockito.kotlin.eq
import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
class ContentResolverHelperTest {
class ContentResolverHelperIT {
companion object {
private val URI = Uri.parse("http://foo.bar")

View file

@ -0,0 +1,211 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.owncloud.android.files
import android.view.Menu
import androidx.appcompat.view.menu.MenuBuilder
import androidx.test.core.app.launchActivity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.nextcloud.client.TestActivity
import com.nextcloud.client.account.User
import com.owncloud.android.AbstractIT
import com.owncloud.android.R
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.files.services.FileDownloader
import com.owncloud.android.files.services.FileUploader
import com.owncloud.android.lib.resources.files.model.FileLockType
import com.owncloud.android.lib.resources.status.CapabilityBooleanType
import com.owncloud.android.lib.resources.status.OCCapability
import com.owncloud.android.services.OperationsService
import com.owncloud.android.ui.activity.ComponentsGetter
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class FileMenuFilterIT : AbstractIT() {
@MockK
private lateinit var mockComponentsGetter: ComponentsGetter
@MockK
private lateinit var mockStorageManager: FileDataStorageManager
@MockK
private lateinit var mockFileUploaderBinder: FileUploader.FileUploaderBinder
@MockK
private lateinit var mockFileDownloaderBinder: FileDownloader.FileDownloaderBinder
@MockK
private lateinit var mockOperationsServiceBinder: OperationsService.OperationsServiceBinder
@Before
fun setup() {
MockKAnnotations.init(this)
every { mockFileUploaderBinder.isUploading(any(), any()) } returns false
every { mockComponentsGetter.fileUploaderBinder } returns mockFileUploaderBinder
every { mockFileDownloaderBinder.isDownloading(any(), any()) } returns false
every { mockComponentsGetter.fileDownloaderBinder } returns mockFileDownloaderBinder
every { mockOperationsServiceBinder.isSynchronizing(any(), any()) } returns false
every { mockComponentsGetter.operationsServiceBinder } returns mockOperationsServiceBinder
}
@Test
fun filter_noLockingCapability_lockItemsInvisible() {
val capability = OCCapability().apply {
endToEndEncryption = CapabilityBooleanType.UNKNOWN
}
val file = OCFile("/foo.md")
testLockingVisibilities(
capability,
file,
ExpectedLockVisibilities(lockFile = false, unlockFile = false, lockedBy = false, lockedUntil = false)
)
}
@Test
fun filter_lockingCapability_fileUnlocked_lockVisible() {
val capability = OCCapability().apply {
endToEndEncryption = CapabilityBooleanType.UNKNOWN
filesLockingVersion = "1.0"
}
val file = OCFile("/foo.md")
testLockingVisibilities(
capability,
file,
ExpectedLockVisibilities(lockFile = true, unlockFile = false, lockedBy = false, lockedUntil = false)
)
}
@Test
fun filter_lockingCapability_fileLocked_lockedByAndProps() {
val capability = OCCapability().apply {
endToEndEncryption = CapabilityBooleanType.UNKNOWN
filesLockingVersion = "1.0"
}
val file = OCFile("/foo.md").apply {
isLocked = true
lockType = FileLockType.MANUAL
lockOwnerId = user.accountName.split("@")[0]
lockOwnerDisplayName = "TEST"
lockTimestamp = 1000 // irrelevant
lockTimeout = 1000 // irrelevant
}
testLockingVisibilities(
capability,
file,
ExpectedLockVisibilities(lockFile = false, unlockFile = true, lockedBy = true, lockedUntil = true)
)
}
@Test
fun filter_lockingCapability_fileLockedByOthers_lockedByAndProps() {
val capability = OCCapability().apply {
endToEndEncryption = CapabilityBooleanType.UNKNOWN
filesLockingVersion = "1.0"
}
val file = OCFile("/foo.md").apply {
isLocked = true
lockType = FileLockType.MANUAL
lockOwnerId = "A_DIFFERENT_USER"
lockOwnerDisplayName = "A_DIFFERENT_USER"
lockTimestamp = 1000 // irrelevant
lockTimeout = 1000 // irrelevant
}
testLockingVisibilities(
capability,
file,
ExpectedLockVisibilities(lockFile = false, unlockFile = false, lockedBy = true, lockedUntil = true)
)
}
private data class ExpectedLockVisibilities(
val lockFile: Boolean,
val unlockFile: Boolean,
val lockedBy: Boolean,
val lockedUntil: Boolean
)
private fun configureCapability(capability: OCCapability) {
every { mockStorageManager.getCapability(any<User>()) } returns capability
every { mockStorageManager.getCapability(any<String>()) } returns capability
every { mockComponentsGetter.storageManager } returns mockStorageManager
}
private fun getMenu(activity: TestActivity): Menu {
val inflater = activity.menuInflater
val menu = MenuBuilder(activity)
inflater.inflate(R.menu.item_file, menu)
return menu
}
private fun testLockingVisibilities(
capability: OCCapability,
file: OCFile,
expectedLockVisibilities: ExpectedLockVisibilities
) {
configureCapability(capability)
launchActivity<TestActivity>().use {
it.onActivity { activity ->
val menu = getMenu(activity)
val sut = FileMenuFilter(file, mockComponentsGetter, activity, true, user)
sut.filter(menu, false)
Assert.assertEquals(
expectedLockVisibilities.lockFile,
menu.findItem(R.id.action_lock_file).isVisible
)
Assert.assertEquals(
expectedLockVisibilities.unlockFile,
menu.findItem(R.id.action_unlock_file).isVisible
)
Assert.assertEquals(
expectedLockVisibilities.lockedBy,
menu.findItem(R.id.action_locked_by).isVisible
)
Assert.assertEquals(
expectedLockVisibilities.lockedUntil,
menu.findItem(R.id.action_locked_until).isVisible
)
// locked by and until should always be disabled, they're not real actions
Assert.assertFalse(menu.findItem(R.id.action_locked_by).isEnabled)
Assert.assertFalse(menu.findItem(R.id.action_locked_until).isEnabled)
}
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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
}
}

View file

@ -0,0 +1,78 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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.Menu
import android.view.MenuItem
import com.nextcloud.utils.TimeConstants
import com.owncloud.android.R
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.resources.files.model.FileLockType
import com.owncloud.android.utils.DisplayUtils
/**
* Customizes a Menu to show locking information
*/
class FileLockingMenuCustomization(val context: Context) {
fun customizeMenu(menu: Menu, file: OCFile) {
if (file.isLocked) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
menu.setGroupDividerEnabled(true)
}
menu.findItem(R.id.action_locked_by).title = getLockedByText(file)
showLockedUntil(menu, file)
}
}
private fun getLockedByText(file: OCFile): 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 showLockedUntil(menu: Menu, file: OCFile) {
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(file))
lockedUntil.isVisible = true
}
}
private fun getExpirationRelativeText(file: OCFile): CharSequence? {
val expirationTimestamp = (file.lockTimestamp + file.lockTimeout) * TimeConstants.MILLIS_PER_SECOND
return DisplayUtils.getRelativeTimestamp(context, expirationTimestamp, true)
}
}

View file

@ -0,0 +1,42 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.android.files
import android.content.Context
import android.view.ContextThemeWrapper
import android.view.View
import androidx.appcompat.widget.PopupMenu
import com.owncloud.android.R
/**
* This is a [PopupMenu] with grayed out disabled elements
*/
class ThemedPopupMenu(
context: Context,
anchor: View
) : PopupMenu(wrapContext(context), anchor) {
companion object {
private fun wrapContext(context: Context): Context =
ContextThemeWrapper(context, R.style.Nextcloud_Widget_PopupMenu)
}
}

View file

@ -30,6 +30,8 @@ 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 com.owncloud.android.ui.ThemeableSwitchPreference;
import javax.inject.Singleton;
@ -55,6 +57,10 @@ public interface AppComponent {
void inject(MainApp app);
void inject(MediaControlView mediaControlView);
void inject(ThemeableSwitchPreference switchPreference);
@Component.Builder
interface Builder {
@BindsInstance

View file

@ -0,0 +1,26 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.utils
object TimeConstants {
const val MILLIS_PER_SECOND = 1000
}

View file

@ -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);
}
/**
* <b>USE SPARINGLY!</b> 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.
* <p>
* 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() {

View file

@ -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;
@ -70,6 +71,7 @@ import java.util.Set;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
public class FileDataStorageManager {
@ -83,9 +85,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;
@ -208,44 +210,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 +337,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 +392,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,76 +436,76 @@ 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)
*/
@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
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());
final FileLockType lockType = file.getLockType();
cv.put(ProviderTableMeta.FILE_LOCK_TYPE, lockType != null ? lockType.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;
}
@ -1030,6 +999,16 @@ 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);
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));
@ -1424,86 +1403,6 @@ public class FileDataStorageManager {
}
}
public void updateSharedFiles(Collection<OCFile> sharedFiles) {
resetShareFlagsInAllFiles();
if (sharedFiles != null) {
ArrayList<ContentProviderOperation> 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 +
@ -2075,6 +1974,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 +2125,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;
}
@ -2258,22 +2161,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<OCFile> getAllGalleryItems() {
return getGalleryItems(0, Long.MAX_VALUE);
}

View file

@ -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;
@ -95,10 +96,23 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
String note;
private List<ShareeUser> 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;
@ -162,6 +176,14 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
richWorkspace = source.readString();
previewAvailable = source.readInt() == 1;
firstShareTimestamp = source.readLong();
locked = source.readInt() == 1;
lockType = FileLockType.fromValue(source.readInt());
lockOwnerId = source.readString();
lockOwnerDisplayName = source.readString();
lockOwnerEditor = source.readString();
lockTimestamp = source.readLong();
lockTimeout = source.readLong();
lockToken = source.readString();
}
@Override
@ -196,6 +218,14 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
dest.writeString(richWorkspace);
dest.writeInt(previewAvailable ? 1 : 0);
dest.writeLong(firstShareTimestamp);
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) {
@ -459,6 +489,14 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
mountType = WebdavEntry.MountType.INTERNAL;
richWorkspace = "";
firstShareTimestamp = 0;
locked = false;
lockType = null;
lockOwnerId = null;
lockOwnerDisplayName = null;
lockOwnerEditor = null;
lockTimestamp = 0;
lockTimeout = 0;
lockToken = null;
}
/**
@ -831,4 +869,73 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
public void setFirstShareTimestamp(long firstShareTimestamp) {
this.firstShareTimestamp = firstShareTimestamp;
}
public boolean isLocked() {
return locked;
}
public void setLocked(boolean locked) {
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(@Nullable String lockOwnerId) {
this.lockOwnerId = lockOwnerId;
}
@Nullable
public String getLockOwnerDisplayName() {
return 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;
}
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;
}
}

View file

@ -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
@ -117,6 +117,14 @@ 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_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<String> FILE_ALL_COLUMNS = Collections.unmodifiableList(Arrays.asList(
_ID,
@ -153,8 +161,15 @@ public class ProviderMeta {
FILE_OWNER_DISPLAY_NAME,
FILE_NOTE,
FILE_SHAREES,
FILE_RICH_WORKSPACE));
FILE_RICH_WORKSPACE,
FILE_LOCKED,
FILE_LOCK_TYPE,
FILE_LOCK_OWNER,
FILE_LOCK_OWNER_DISPLAY_NAME,
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
@ -210,6 +225,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";

View file

@ -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<OCFile> files;
private ComponentsGetter componentsGetter;
private Context context;
private boolean overflowMenu;
private User user;
private final int numberOfAllFiles;
private final Collection<OCFile> 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);
}
/**
@ -125,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);
}
}
}
}
@ -181,11 +200,13 @@ public class FileMenuFilter {
boolean synchronizing = anyFileSynchronizing();
OCCapability capability = componentsGetter.getStorageManager().getCapability(user.getAccountName());
boolean endToEndEncryptionEnabled = capability.getEndToEndEncryption().isTrue();
boolean fileLockingEnabled = capability.getFilesLockingVersion() != null;
filterEdit(toShow, toHide, capability);
filterDownload(toShow, toHide, synchronizing);
filterRename(toShow, toHide, synchronizing);
filterMoveCopy(toShow, toHide, synchronizing);
filterCopy(toShow, toHide, synchronizing);
filterMove(toShow, toHide, synchronizing);
filterRemove(toShow, toHide, synchronizing);
filterSelectAll(toShow, toHide, inSingleFileFragment);
filterDeselectAll(toShow, toHide, inSingleFileFragment);
@ -201,6 +222,9 @@ public class FileMenuFilter {
filterUnsetEncrypted(toShow, toHide, endToEndEncryptionEnabled);
filterSetPictureAs(toShow, toHide);
filterStream(toShow, toHide);
filterLock(toShow, toHide, fileLockingEnabled);
filterUnlock(toShow, toHide, fileLockingEnabled);
filterLockInfo(toShow, toHide, fileLockingEnabled);
}
private void filterShareFile(List<Integer> toShow, List<Integer> toHide, OCCapability capability) {
@ -252,6 +276,45 @@ public class FileMenuFilter {
}
}
private void filterLock(List<Integer> toShow, List<Integer> toHide, boolean fileLockingEnabled) {
if (files.isEmpty() || !isSingleSelection() || !fileLockingEnabled) {
toHide.add(R.id.action_lock_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<Integer> toShow, List<Integer> toHide, boolean fileLockingEnabled) {
if (files.isEmpty() || !isSingleSelection() || !fileLockingEnabled) {
toHide.add(R.id.action_unlock_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);
}
}
}
private void filterLockInfo(List<Integer> toShow, List<Integer> toHide, boolean fileLockingEnabled) {
if (files.isEmpty() || !isSingleSelection() || !fileLockingEnabled) {
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<Integer> toShow, List<Integer> toHide, boolean endToEndEncryptionEnabled) {
if (files.isEmpty() || !isSingleSelection() || isSingleFile() || isEncryptedFolder()
|| !endToEndEncryptionEnabled) {
@ -391,25 +454,32 @@ public class FileMenuFilter {
}
private void filterRemove(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
if (files.isEmpty() || synchronizing || containsEncryptedFolder()) {
if (files.isEmpty() || synchronizing || containsEncryptedFolder() || containsLockedFile()) {
toHide.add(R.id.action_remove_file);
} else {
toShow.add(R.id.action_remove_file);
}
}
private void filterMoveCopy(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
if (files.isEmpty() || synchronizing || containsEncryptedFile() || containsEncryptedFolder()) {
private void filterMove(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
if (files.isEmpty() || synchronizing || containsEncryptedFile() || containsEncryptedFolder() || containsLockedFile()) {
toHide.add(R.id.action_move);
toHide.add(R.id.action_copy);
} else {
toShow.add(R.id.action_move);
}
}
private void filterCopy(List<Integer> toShow, List<Integer> toHide, boolean synchronizing) {
if (files.isEmpty() || synchronizing || containsEncryptedFile() || containsEncryptedFolder()) {
toHide.add(R.id.action_copy);
} else {
toShow.add(R.id.action_copy);
}
}
private void filterRename(Collection<Integer> toShow, Collection<Integer> toHide, boolean synchronizing) {
if (!isSingleSelection() || synchronizing || containsEncryptedFile() || containsEncryptedFolder()) {
if (!isSingleSelection() || synchronizing || containsEncryptedFile() || containsEncryptedFolder() || containsLockedFile()) {
toHide.add(R.id.action_rename_file);
} else {
toShow.add(R.id.action_rename_file);
@ -528,6 +598,15 @@ public class FileMenuFilter {
return false;
}
private boolean containsLockedFile() {
for (OCFile file : files) {
if (file.isLocked()) {
return true;
}
}
return false;
}
private boolean containsEncryptedFolder() {
for (OCFile file : files) {
if (file.isFolder() && file.isEncrypted()) {

View file

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

View file

@ -753,7 +753,15 @@ 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_TYPE + INTEGER
+ ProviderTableMeta.FILE_LOCK_OWNER + TEXT
+ ProviderTableMeta.FILE_LOCK_OWNER_DISPLAY_NAME + TEXT
+ ProviderTableMeta.FILE_LOCK_OWNER_EDITOR + TEXT
+ ProviderTableMeta.FILE_LOCK_TIMESTAMP + INTEGER
+ ProviderTableMeta.FILE_LOCK_TIMEOUT + INTEGER
+ ProviderTableMeta.FILE_LOCK_TOKEN + " TEXT );"
);
}
@ -829,7 +837,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 +2463,39 @@ public class FileContentProvider extends ContentProvider {
}
}
if (oldVersion < 63 && newVersion >= 63) {
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_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();
} finally {
db.endTransaction();
}
}
if (!upgraded) {
Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
}

View file

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

View file

@ -513,6 +513,7 @@ public class FileDisplayActivity extends FileActivity
Log_OC.d(this, "Switch to oc file fragment");
setLeftFragment(new OCFileListFragment());
getSupportFragmentManager().executePendingTransactions();
browseToRoot();
}
}
@ -1545,14 +1546,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 +1557,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);

View file

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

View file

@ -466,6 +466,12 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
holder.getOverflowMenu().setOnClickListener(view -> ocFileListFragmentInterface
.onOverflowIconClicked(file, view));
}
if (file.isLocked()) {
holder.getOverflowMenu().setImageResource(R.drawable.ic_locked_dots_small);
} else {
holder.getOverflowMenu().setImageResource(R.drawable.ic_dots_vertical);
}
}
private void bindListGridItemViewHolder(ListGridItemViewHolder holder, OCFile file) {

View file

@ -0,0 +1,23 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.events
data class FileLockEvent(val filePath: String, val shouldLock: Boolean)

View file

@ -41,13 +41,15 @@ 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.FileLockingMenuCustomization;
import com.nextcloud.android.files.ThemedPopupMenu;
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 +58,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 +95,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;
@ -181,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;
@ -568,7 +573,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
@Override
public void onOverflowIconClicked(OCFile file, View view) {
throttler.run("overflowClick", () -> {
PopupMenu popup = new PopupMenu(getActivity(), view);
final ThemedPopupMenu popup = new ThemedPopupMenu(requireContext(), view);
popup.inflate(R.menu.item_file);
FileMenuFilter mf = new FileMenuFilter(mAdapter.getFiles().size(),
Collections.singleton(file),
@ -576,11 +581,13 @@ public class OCFileListFragment extends ExtendedListFragment implements
true,
accountManager.getUser());
mf.filter(popup.getMenu(), true);
new FileLockingMenuCustomization(requireContext()).customizeMenu(popup.getMenu(), file);
popup.setOnMenuItemClickListener(item -> {
Set<OCFile> checkedFiles = new HashSet<>();
checkedFiles.add(file);
return onFileActionChosen(item, checkedFiles);
});
popup.show();
});
}
@ -741,6 +748,9 @@ public class OCFileListFragment extends ExtendedListFragment implements
// Determine if we need to finish the action mode because there are no items selected
if (checkedCount == 0 && !mIsActionModeNew) {
exitSelectionMode();
} else if (checkedCount == 1) {
// customize for locking if file is locked
new FileLockingMenuCustomization(requireContext()).customizeMenu(menu, checkedFiles.iterator().next());
}
return true;
@ -1146,6 +1156,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 +1206,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 +1658,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<Void> 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));
}

View file

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

View file

@ -233,6 +233,14 @@ 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.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;
}

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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 <https://www.gnu.org/licenses/>.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/text_color" android:state_enabled="true"/>
<item android:color="@color/disabled_text" />
</selector>

View file

@ -0,0 +1,33 @@
<!--
~ 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 <https://www.gnu.org/licenses/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="m7.9482,15.6104c0.3479,0 0.682,-0.1385 0.9282,-0.3847 0.2462,-0.2462 0.3847,-0.5803 0.3847,-0.9282 0,-0.7286 -0.5908,-1.3129 -1.3129,-1.3129 -0.3479,0 -0.682,0.1385 -0.9282,0.3847 -0.2462,0.2462 -0.3847,0.5803 -0.3847,0.9282 0,0.3479 0.1385,0.682 0.3847,0.9282 0.2462,0.2462 0.5803,0.3847 0.9282,0.3847M11.8868,9.7025c0.3479,0 0.682,0.1385 0.9282,0.3847 0.2462,0.2462 0.3847,0.5803 0.3847,0.9282v6.5644c0,0.3479 -0.1385,0.682 -0.3847,0.9282 -0.2462,0.2462 -0.5803,0.3847 -0.9282,0.3847H4.0095c-0.3479,0 -0.682,-0.1385 -0.9282,-0.3847 -0.2462,-0.2462 -0.3847,-0.5803 -0.3847,-0.9282v-6.5644c0,-0.7286 0.5908,-1.3129 1.3129,-1.3129H4.666V8.3896c0,-0.8704 0.3459,-1.7054 0.961,-2.3212 0.6157,-0.6151 1.4507,-0.961 2.3212,-0.961 0.8704,0 1.7054,0.3459 2.3212,0.961 0.6151,0.6157 0.961,1.4507 0.961,2.3212v1.3129h0.6564M7.9482,6.4203c-0.5219,0 -1.0234,0.2074 -1.3923,0.577C6.1863,7.3662 5.9789,7.8677 5.9789,8.3896V9.7025H9.9175V8.3896c0,-0.5219 -0.2074,-1.0234 -0.577,-1.3923C8.9716,6.6277 8.47,6.4203 7.9482,6.4203Z"
android:strokeWidth="0.656438"
android:fillColor="#666666"/>
<path
android:pathData="m19.3033,8c1.1,0 2,-0.9 2,-2 0,-1.1 -0.9,-2 -2,-2 -1.1,0 -2,0.9 -2,2 0,1.1 0.9,2 2,2zM19.3033,10c-1.1,0 -2,0.9 -2,2 0,1.1 0.9,2 2,2 1.1,0 2,-0.9 2,-2 0,-1.1 -0.9,-2 -2,-2zM19.3033,16c-1.1,0 -2,0.9 -2,2 0,1.1 0.9,2 2,2 1.1,0 2,-0.9 2,-2 0,-1.1 -0.9,-2 -2,-2z"
android:fillColor="#666666"/>
</vector>

View file

@ -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">
<ImageView

View file

@ -18,6 +18,7 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/ListItemLayout"
android:layout_width="match_parent"
android:layout_height="@dimen/standard_list_item_size"
@ -137,10 +138,11 @@
</LinearLayout>
<RelativeLayout
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:gravity="center_vertical|end"
android:orientation="horizontal"
android:paddingStart="@dimen/standard_half_padding"
android:paddingEnd="@dimen/zero">
@ -148,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"
@ -161,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"
@ -173,10 +172,8 @@
<com.owncloud.android.ui.AvatarGroupLayout
android:id="@+id/sharedAvatars"
android:layout_width="100dp"
android:gravity="center_vertical"
android:layout_height="@dimen/file_icon_size"
android:layout_alignEnd="@id/sharedIcon"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/sharedIcon"
android:contentDescription="@string/shared_avatar_desc"
android:visibility="visible" />
@ -184,8 +181,6 @@
android:id="@+id/custom_checkbox"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/sharedAvatars"
android:clickable="false"
android:contentDescription="@string/checkbox"
android:focusable="false"
@ -197,8 +192,6 @@
android:id="@+id/overflow_menu"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/custom_checkbox"
android:clickable="true"
android:contentDescription="@string/overflow_menu"
android:focusable="true"
@ -206,5 +199,5 @@
android:paddingEnd="12dp"
android:src="@drawable/ic_dots_vertical" />
</RelativeLayout>
</LinearLayout>
</LinearLayout>

View file

@ -111,7 +111,7 @@
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/appbar"
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
android:theme="@style/Theme.ToolbarWithDisabled"
app:popupTheme="@style/Theme.AppCompat.DayNight.NoActionBar"
tools:visibility="gone">

View file

@ -22,6 +22,37 @@
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="AppCompatResource">
<group
android:id="@+id/menu_group_lock_info"
android:visible="false"
android:enabled="false"
tools:visible="true">
<item
android:id="@+id/action_locked_by"
android:showAsAction="never"
android:title="Locked by %1$s"
android:enabled="false"
app:showAsAction="never"
tools:ignore="HardcodedText"
tools:title="Locked by Username With Surname at bla bla bla bla bla" />
<item
android:id="@+id/action_locked_until"
android:showAsAction="never"
android:enabled="false"
android:title="Lock expires: %1$s"
app:showAsAction="never"
tools:ignore="HardcodedText"
tools:title="Lock expires: in 20 minutes" />
</group>
<item
android:id="@+id/action_unlock_file"
android:showAsAction="never"
android:title="@string/unlock_file"
app:showAsAction="never" />
<item
android:id="@+id/action_edit"
android:title="@string/action_edit"
@ -46,6 +77,12 @@
app:showAsAction="never"
android:showAsAction="never" />
<item
android:id="@+id/action_lock_file"
android:showAsAction="never"
android:title="@string/lock_file"
app:showAsAction="never" />
<item
android:id="@+id/action_rename_file"
android:title="@string/common_rename"

View file

@ -396,6 +396,7 @@
<string name="local_file_not_found_message">ملف غير موجود في نظام الملفات المحلي</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">لا توجد مجلدات أخرى.</string>
<string name="lock_file">أقفل الملف</string>
<string name="log_send_mail_subject">%1$s سجلات تطبيق الأندرويد</string>
<string name="log_send_no_mail_app">لا يوجد تطبيق لارسال السجلات . الرجاء تثبيت عميل البريد الالكتروني.</string>
<string name="login">تسجيل الدخول</string>
@ -755,6 +756,7 @@
<string name="trashbin_file_not_restored">الملف %1$s لا يمكن إسترجاعه!</string>
<string name="trashbin_loading_failed">فشل تحميل سلة المحذوفات</string>
<string name="trashbin_not_emptied">تعذر حذف الملفات نهائياً!</string>
<string name="unlock_file">فتح قفل الملف</string>
<string name="unread_comments">توجد تعليقات غير مقروءة</string>
<string name="unset_encrypted">لم يتم تحديد التشفير</string>
<string name="unset_favorite">إزالتها مِن المفضلة</string>

View file

@ -402,6 +402,7 @@
<string name="local_file_not_found_message">Файлът не е намерен в локалната система за файлове</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Няма повече папки.</string>
<string name="lock_file">Заключване на файл</string>
<string name="log_send_mail_subject">%1$s журнали от Android приложението</string>
<string name="log_send_no_mail_app">Няма намерено приложение за изпращане на журнали. Моля, инсталирайте имейл клиент.</string>
<string name="login">Вписване</string>
@ -757,6 +758,7 @@
<string name="trashbin_file_not_restored">Файлът %1$s не може да бъде възстановен!</string>
<string name="trashbin_loading_failed">Зареждането на кошчето е неуспешно!</string>
<string name="trashbin_not_emptied">Файловете не могат да бъдат окончателно изтрити!</string>
<string name="unlock_file">Отключване на файл</string>
<string name="unread_comments">Съществуват непрочетени коментари</string>
<string name="unset_encrypted">Изключи криптиране</string>
<string name="unset_favorite">Премахни от любимите</string>

View file

@ -356,6 +356,7 @@
<string name="local_file_not_found_message">N\'ez eus restr ebet kavet e restroù diabarzh ar sistem</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">N\'ez eus teuliad all ebet</string>
<string name="lock_file">Prennan ar restr</string>
<string name="log_send_mail_subject">%1$s kazetenn meziant Android</string>
<string name="login">kennaskañ</string>
<string name="logs_menu_delete">Lemel ar gazetennoù</string>
@ -664,6 +665,7 @@
<string name="trashbin_file_not_deleted">Ar restr %1$s n\'eo ket evit bezhaãn lamet !</string>
<string name="trashbin_file_not_restored">Ar restr %1$s n\'eo ket evit bezha adlakaet !</string>
<string name="trashbin_not_emptied">N\'eo ket posupl lemel ar restr da virviken !</string>
<string name="unlock_file">Dibrennan ar restr</string>
<string name="unread_comments">Bez eez eus kemenadennom n\'int ket bet lennet</string>
<string name="unset_encrypted">Lemel ar sifrañ</string>
<string name="unset_favorite">Lemel eus ar pennroll</string>

View file

@ -401,6 +401,7 @@
<string name="local_file_not_found_message">No s\'ha trobat el fitxer al sistema de fitxers local</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">No hi ha més carpetes.</string>
<string name="lock_file">Bloca el fitxer</string>
<string name="log_send_mail_subject">%1$s registres de la aplicació d\'Android.</string>
<string name="log_send_no_mail_app">No s\'ha trobat cap aplicació per enviar els registres. Instal·leu un client de correu electrònic.</string>
<string name="login">Inicia la sessió</string>
@ -756,6 +757,7 @@
<string name="trashbin_file_not_restored">El fitxer %1$s no es pot restaurar!</string>
<string name="trashbin_loading_failed">No s\'ha pogut carregar la paperera!</string>
<string name="trashbin_not_emptied">Els fitxers no es poden suprimir permanentment!</string>
<string name="unlock_file">Desbloca el fitxer</string>
<string name="unread_comments">Hi ha comentaris sense llegir</string>
<string name="unset_encrypted">Desactiva el xifrat</string>
<string name="unset_favorite">Suprimeix-ho de preferits</string>

View file

@ -255,6 +255,7 @@
<string name="error_comment_file">Chyba při přidávání komentáře k souboru</string>
<string name="error_crash_title">%1$s zhavarovalo</string>
<string name="error_creating_file_from_template">Chyba při vytváření souboru ze šablony</string>
<string name="error_file_lock">Chyba při změně stavu zámku souboru</string>
<string name="error_report_issue_action">Hlášení</string>
<string name="error_report_issue_text">Nahlásit problém? (vyžaduje GitHub účet)</string>
<string name="error_retrieving_file">Chyba při získávání souboru</string>
@ -401,6 +402,10 @@
<string name="local_file_not_found_message">Soubor nenalezen na souborovém systému</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Nejsou zde žádné další složky.</string>
<string name="lock_expiration_info">Platnost skončí: %1$s</string>
<string name="lock_file">Zamknout soubor</string>
<string name="locked_by">Uzamkl(a) %1$s</string>
<string name="locked_by_app">Uzamčeno aplikací %1$s</string>
<string name="log_send_mail_subject">Záznamy událostí v aplikaci %1$s pro Android</string>
<string name="log_send_no_mail_app">Nebyla nalezena žádná aplikace, přes kterou by bylo možné odeslat záznamy událostí. Nainstalujte si e-mailového klienta.</string>
<string name="login">Přihlásit</string>
@ -777,6 +782,7 @@ Jak vývojová tak produkční verze je k dispozici na F-droid a mohou být nain
<string name="trashbin_file_not_restored">Soubor %1$s se nepodařilo obnovit!</string>
<string name="trashbin_loading_failed">Načtení obsahu koše se nezdařilo!</string>
<string name="trashbin_not_emptied">Soubory se nepodařilo natrvalo smazat!</string>
<string name="unlock_file">Odemknout soubor</string>
<string name="unread_comments">Existuje nepřečtený komentář</string>
<string name="unset_encrypted">Zrušit šifrování</string>
<string name="unset_favorite">Odebrat z oblíbených</string>

View file

@ -401,6 +401,7 @@
<string name="local_file_not_found_message">Fil ikke fundet i lokalt filsystem</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Der er ikke flere mapper.</string>
<string name="lock_file">Lås filen</string>
<string name="log_send_mail_subject">%1$s Android-app - logge</string>
<string name="log_send_no_mail_app">Der er ingen app tilgængelig til at sende log filer. Venligst installér en email klient.</string>
<string name="login">Log ind</string>
@ -758,6 +759,7 @@ Enheds legitimationsoplysninger er sat op
<string name="trashbin_file_not_restored">Fil %1$s kunne ikke genskabes!</string>
<string name="trashbin_loading_failed">Indlæsning af papirkurv mislykkedes!</string>
<string name="trashbin_not_emptied">Filer kunne ikke permanent slettes!</string>
<string name="unlock_file">Lås op filen</string>
<string name="unread_comments">Der er ulæste kommentarer</string>
<string name="unset_encrypted">Slå kryptering fra</string>
<string name="unset_favorite">Fjern fra favoritter</string>

View file

@ -255,6 +255,7 @@
<string name="error_comment_file">Fehler beim Kommentieren der Datei</string>
<string name="error_crash_title">%1$s abgestürzt</string>
<string name="error_creating_file_from_template">Fehler beim Erzeugen einer Datei aus der Vorlage</string>
<string name="error_file_lock">Fehler beim Ändern des Sperr-Status</string>
<string name="error_report_issue_action">Melden</string>
<string name="error_report_issue_text">Problem melden? (benötigt ein GitHub-Konto)</string>
<string name="error_retrieving_file">Fehler beim Abruf der Datei</string>
@ -401,6 +402,10 @@
<string name="local_file_not_found_message">Die Datei wurde im lokalen Dateisystem nicht gefunden</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Hier gibt es keine weiteren Ordner.</string>
<string name="lock_expiration_info">Läuft ab: %1$s</string>
<string name="lock_file">Datei sperren</string>
<string name="locked_by">Gesperrt von %1$s</string>
<string name="locked_by_app">Gesperrt von der App %1$s</string>
<string name="log_send_mail_subject">%1$s Android-App Meldungen</string>
<string name="log_send_no_mail_app">Keine App zum Senden von Protokolldateien gefunden. Bitte installieren Sie einen E-Mail-Client.</string>
<string name="login">Anmelden</string>
@ -757,6 +762,7 @@
<string name="trashbin_file_not_restored">Datei %1$s konnte nicht wiederhergestellt werden!</string>
<string name="trashbin_loading_failed">Laden des Papierkorbs fehlgeschlagen!</string>
<string name="trashbin_not_emptied">Dateien konnten nicht endgültig gelöscht werden!</string>
<string name="unlock_file">Datei entsperren</string>
<string name="unread_comments">Es gibt ungelesene Kommentare</string>
<string name="unset_encrypted">Verschlüsselung aufheben</string>
<string name="unset_favorite">Aus den Favoriten entfernen</string>

View file

@ -401,6 +401,7 @@
<string name="local_file_not_found_message">Το αρχείο δεν βρέθηκε στο τοπικό σύστημα αρχείων</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Δεν υπάρχουν άλλοι φάκελοι.</string>
<string name="lock_file">Κλείδωμα αρχείου</string>
<string name="log_send_mail_subject">%1$s ιστορικό της εφαρμογής Android</string>
<string name="log_send_no_mail_app">Δεν βρέθηκε εφαρμογή για αποστολή αρχείων καταγραφής. Εγκαταστήστε ένα πρόγραμμα -πελάτη ηλεκτρονικής αλληλογραφίας.</string>
<string name="login">Είσοδος</string>
@ -756,6 +757,7 @@
<string name="trashbin_file_not_restored">Το αρχείο %1$s δεν μπορεί να ανακτηθεί!</string>
<string name="trashbin_loading_failed">Αποτυχία φόρτωσης κάδου ανακύκλωσης!</string>
<string name="trashbin_not_emptied">Τα αρχεία δεν θα διαγραφούν μόνιμα!</string>
<string name="unlock_file">Ξεκλείδωμα αρχείου</string>
<string name="unread_comments">Υπάρχουν μη αναγνωσμένα σχόλια</string>
<string name="unset_encrypted">Χρήση κρυπτογράφησης</string>
<string name="unset_favorite">Αφαίρεση από τα αγαπημένα</string>

View file

@ -398,6 +398,7 @@
<string name="local_file_not_found_message">El archivo no se encuentra en el sistema de archivos local</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">No hay mas carpetas</string>
<string name="lock_file">Bloquear archivo</string>
<string name="log_send_mail_subject">Se han encontrado %1$s aplicaciones de registros para Android</string>
<string name="log_send_no_mail_app">No se ha encontrado una app para enviar los registros. Por favor, instale un cliente de correo electrónico.</string>
<string name="login">Iniciar sesión</string>
@ -751,6 +752,7 @@
<string name="trashbin_file_not_restored">No se ha podido restaurar el archivo %1$s.</string>
<string name="trashbin_loading_failed">¡Fallo al cargar la papelera!</string>
<string name="trashbin_not_emptied">No se ha podido eliminar permanentemente el archivo.</string>
<string name="unlock_file">Desbloquear archivo</string>
<string name="unread_comments">Hay comentarios no leídos</string>
<string name="unset_encrypted">Desactivar cifrado</string>
<string name="unset_favorite">Quitar de favoritos</string>

View file

@ -255,6 +255,7 @@
<string name="error_comment_file">Errorea fitxategia iruzkintzean</string>
<string name="error_crash_title">%1$s(e)k huts egin du</string>
<string name="error_creating_file_from_template">Errorea fitxategia plantilatik sortzerakoan</string>
<string name="error_file_lock">Errorea fitxategiaren blokeo egoera aldatzean</string>
<string name="error_report_issue_action">Jakinarazi</string>
<string name="error_report_issue_text">Arazoaren berri eman nahi duzu? (GitHub kontu bat beharrezkoa da)</string>
<string name="error_retrieving_file">Errorea fitxategia berreskuratzean</string>
@ -401,6 +402,10 @@
<string name="local_file_not_found_message">Ez da fitxategia topatu fitxategi-sistema lokalean</string>
<string name="local_folder_friendly_path">%2$s(e)tik %1$s</string>
<string name="local_folder_list_empty">Ez dago karpeta gehiagorik.</string>
<string name="lock_expiration_info">Iraungitze data: %1$s</string>
<string name="lock_file">Blokeatu fitxategia</string>
<string name="locked_by">%1$s-(e)k blokeatuta</string>
<string name="locked_by_app">%1$s aplikazioak blokeatuta</string>
<string name="log_send_mail_subject">%1$s Android aplikazioaren egunkariak</string>
<string name="log_send_no_mail_app">Ez da aurkitu egunkariak bidaltzeko aplikaziorik. Instalatu posta elektroniko bezero bat.</string>
<string name="login">Hasi saioa</string>
@ -756,6 +761,7 @@
<string name="trashbin_file_not_restored"> %1$s fitxategia ezin da berreskuratu!</string>
<string name="trashbin_loading_failed">Zakarrontzia kargatzeak huts egin du!</string>
<string name="trashbin_not_emptied">Fitxategiak ezin dira betirako ezabatu!</string>
<string name="unlock_file">Desblokeatu fitxategia</string>
<string name="unread_comments">Irakurri gabeko iruzkinak daude</string>
<string name="unset_encrypted">Kendu enkriptatzea</string>
<string name="unset_favorite">Gogokoenetatik kendu</string>

View file

@ -372,6 +372,7 @@
<string name="local_file_not_found_message">فایل در فایل‌های سیستمی محلی یافت نشد</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">هیچ پوشه ای وجود ندارد</string>
<string name="lock_file">قفل کردن پرونده</string>
<string name="log_send_mail_subject">%1$s لاگ‌های برنامه های اندروید</string>
<string name="log_send_no_mail_app">برنامه‌ای برای فرستادن گزارش‌ها پیدا نشد. لطفا یک کارخواه رایانامه نصب کنید.</string>
<string name="login">Log in</string>
@ -705,6 +706,7 @@
<string name="trashbin_file_not_restored">فایل %1$s نمی تواند بازیابی شود</string>
<string name="trashbin_loading_failed">بار کردن سطل زباله شکست خورد!</string>
<string name="trashbin_not_emptied">نمی توان پرونده ها را برای همیشه حذف کرد!</string>
<string name="unlock_file">باز کردن قفل پرونده</string>
<string name="unread_comments">نظرات خوانده نشده وجود دارد</string>
<string name="unset_encrypted">به صورت رمزگذاری شده تنظیم نشده است</string>
<string name="unset_favorite">حذف از موارد دلخواه</string>

View file

@ -395,6 +395,7 @@
<string name="local_file_not_found_message">Tiedostoa ei löytynyt paikallisesta tiedostojärjestelmästä</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Kansioita ei ole enempää.</string>
<string name="lock_file">Lukitse tiedosto</string>
<string name="log_send_mail_subject">%1$sin Android-sovelluksen lokit</string>
<string name="log_send_no_mail_app">Sovellusta ei löytynyt lokien lähettämistä varten. Asenna sähköpostisovellus.</string>
<string name="login">Kirjaudu sisään</string>
@ -745,6 +746,7 @@ GNU yleinen lisenssi, versio 2</string>
<string name="trashbin_file_not_restored">Tiedostoa %1$s ei voitu palauttaa!</string>
<string name="trashbin_loading_failed">Roskakorin lataaminen epäonnistui!</string>
<string name="trashbin_not_emptied">Tiedostoja ei voitu poistaa pysyvästi!</string>
<string name="unlock_file">Vapauta tiedoston lukitus</string>
<string name="unread_comments">Lukemattomia kommentteja on olemassa</string>
<string name="unset_encrypted">Poista salaus</string>
<string name="unset_favorite">Poista suosikeista</string>

View file

@ -400,6 +400,7 @@ Attention la suppression est irréversible.</string>
<string name="local_file_not_found_message">Le fichier n\'a pas été trouvé sur le système de fichier local</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Il n\'y a pas d\'autres dossiers.</string>
<string name="lock_file">Verrouiller le fichier</string>
<string name="log_send_mail_subject">Journaux de l\'application Android %1$s</string>
<string name="log_send_no_mail_app">Pas d\'app d\'envoie de logs trouvé. Installer un client e-mail.</string>
<string name="login">Se connecter</string>
@ -753,6 +754,7 @@ Attention la suppression est irréversible.</string>
<string name="trashbin_file_not_restored">Le fichier %1$s n\'a pas pu être restauré !</string>
<string name="trashbin_loading_failed">Le chargement de la corbeille a échoué !</string>
<string name="trashbin_not_emptied">Des fichiers n\'ont pas pu être supprimés de manière définitive !</string>
<string name="unlock_file">Déverrouiller le fichier</string>
<string name="unread_comments">Il y a des commentaire non lus</string>
<string name="unset_encrypted">Désactiver le chiffrement</string>
<string name="unset_favorite">Supprimer des favoris</string>

View file

@ -372,6 +372,7 @@
<string name="local_file_not_found_message">Non se atopou o ficheiro no sistema local de ficheiros</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Xa non hai máis cartafoles</string>
<string name="lock_file">Bloquear ficheiro</string>
<string name="log_send_mail_subject">Atopáronse %1$s aplicacións de rexistros para Android</string>
<string name="login">Acceder</string>
<string name="login_url_helper_text">A ligazón á súa interface web %1$s cando a abre no navegador.</string>
@ -700,6 +701,7 @@
<string name="trashbin_file_not_deleted">Non foi posíbel eliminar o ficheiro %1$s!</string>
<string name="trashbin_file_not_restored">Non foi posíbel restaurar o ficheiro %1$s!</string>
<string name="trashbin_not_emptied">Non foi posíbel eliminar de xeito permanente o ficheiro!</string>
<string name="unlock_file">Desbloquear ficheiro</string>
<string name="unread_comments">Existen comentarios sen ler</string>
<string name="unset_encrypted">Estabelecer como NON cifrado</string>
<string name="unset_favorite">Retirar dos favoritos</string>

View file

@ -401,6 +401,7 @@
<string name="local_file_not_found_message">Datoteka nije pronađena u lokalnom datotečnom sustavu</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Nema drugih mapa.</string>
<string name="lock_file">Zaključaj datoteku</string>
<string name="log_send_mail_subject">Zapisnici aplikacija za Android %1$s</string>
<string name="log_send_no_mail_app">Nije pronađena nijedna aplikacija za slanje zapisa. Instalirajte klijent e-pošte.</string>
<string name="login">Prijava</string>
@ -753,6 +754,7 @@
<string name="trashbin_file_not_restored">Datoteku %1$s nije moguće vratiti!</string>
<string name="trashbin_loading_failed">Učitavanje kante za smeće nije uspjelo!</string>
<string name="trashbin_not_emptied">Datoteke nije moguće trajno izbrisati!</string>
<string name="unlock_file">Otključaj datoteku</string>
<string name="unread_comments">Postoje nepročitani komentari</string>
<string name="unset_encrypted">Ukloni šifriranje</string>
<string name="unset_favorite">Ukloni iz favorita</string>

View file

@ -255,6 +255,7 @@
<string name="error_comment_file">Hiba a fájlhoz hozzászóláskor</string>
<string name="error_crash_title">A(z) %1$s összeomlott</string>
<string name="error_creating_file_from_template">Hiba a fájl sablonból történő létrehozása során</string>
<string name="error_file_lock">Hiba a fájl zárolási állapotának módosítása során</string>
<string name="error_report_issue_action">Jelentés</string>
<string name="error_report_issue_text">Jelenti a hibát a követőbe? (GitHub-fiók szükséges)</string>
<string name="error_retrieving_file">Hiba a fájl lekérésekor</string>
@ -401,6 +402,10 @@
<string name="local_file_not_found_message">A fájl nem található a helyi fájlrendszeren</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Nincs több mappa.</string>
<string name="lock_expiration_info">Lejárat: %1$s</string>
<string name="lock_file">Fájl zárolása</string>
<string name="locked_by">Zárolta: %1$s</string>
<string name="locked_by_app">Zárolta: %1$s alkalmazás</string>
<string name="log_send_mail_subject">%1$s androidos alkalmazás naplói</string>
<string name="log_send_no_mail_app">Nincs alkalmazás a napló küldéshez. Telepítsen egy levelezőprogramot.</string>
<string name="login">Bejelentkezés</string>
@ -774,6 +779,7 @@ A Nextcloud itt érhető el: https://nextcloud.com</string>
<string name="trashbin_file_not_restored">A(z) %1$s fájl nem állítható vissza!</string>
<string name="trashbin_loading_failed">A kuka betöltése sikertelen.</string>
<string name="trashbin_not_emptied">A fájlok nem törölhetők véglegesen!</string>
<string name="unlock_file">Fájl feloldása</string>
<string name="unread_comments">Olvasatlan hozzászólások vannak</string>
<string name="unset_encrypted">Titkosítás kikapcsolása</string>
<string name="unset_favorite">Eltávolítás a kedvencekből</string>

View file

@ -360,6 +360,7 @@
<string name="local_file_not_found_message">Skrá fannst ekki á staðværu skráakerfi</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Það eru engar fleiri möppur.</string>
<string name="lock_file">Læsa skrá</string>
<string name="log_send_mail_subject">%1$s atvikaskrár Android-forrita</string>
<string name="login">Skrá inn</string>
<string name="logs_menu_delete">Eyða atvikaskrám</string>
@ -675,6 +676,7 @@
<string name="trashbin_file_not_deleted">Ekki tókst að eyða %1$s skránni!</string>
<string name="trashbin_file_not_restored">Ekki tókst að endurheimta %1$s skrána!</string>
<string name="trashbin_not_emptied">Ekki tókst að eyða skránum endanlega!</string>
<string name="unlock_file">Aflæsa skrá</string>
<string name="unread_comments">Ólesnar athugasemdir eru fyrirliggjandi</string>
<string name="unset_encrypted">Gera dulritun óvirka</string>
<string name="unset_favorite">Fjarlægja úr eftirlætislista</string>

View file

@ -401,6 +401,7 @@
<string name="local_file_not_found_message">File non trovato nel file system locale</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Non ci sono ulteriori cartelle.</string>
<string name="lock_file">Blocca file</string>
<string name="log_send_mail_subject">Registri applicazione %1$s Android</string>
<string name="log_send_no_mail_app">Non è stata trovata alcuna applicazione per inviare i log. Installa un client di posta elettronica.</string>
<string name="login">Accedi</string>
@ -756,6 +757,7 @@
<string name="trashbin_file_not_restored">Il file %1$s non può essere ripristinato!</string>
<string name="trashbin_loading_failed">Caricamento cestino non riuscito!</string>
<string name="trashbin_not_emptied">I file non possono essere scaricati definitivamente!</string>
<string name="unlock_file">Sblocca file</string>
<string name="unread_comments">Sono presenti commenti non letti</string>
<string name="unset_encrypted">Rimuovi cifratura</string>
<string name="unset_favorite">Rimuovi dai preferiti</string>

View file

@ -354,6 +354,7 @@
<string name="local_file_not_found_message">הקובץ לא נמצא במערכת הקבצים המקומית</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">אין תיקיות נוספות.</string>
<string name="lock_file">נעילת קובץ</string>
<string name="log_send_mail_subject">%1$s לוגים של יישום אנדרואיד</string>
<string name="login">כניסה</string>
<string name="logs_menu_delete">מחיקת יומנים</string>
@ -681,6 +682,7 @@
<string name="trashbin_file_not_deleted">לא ניתן למחוק את הקובץ %1$s!</string>
<string name="trashbin_file_not_restored">לא ניתן לשחזר את הקובץ %1$s!</string>
<string name="trashbin_not_emptied">לא ניתן למחוק קבצים לצמיתות!</string>
<string name="unlock_file">שחרור קובץ</string>
<string name="unread_comments">יש תגובות שלא נקראו</string>
<string name="unset_encrypted">ביטול הגדרת ההצפנה</string>
<string name="unset_favorite">הסרה מהמועדפים</string>

View file

@ -396,6 +396,7 @@
<string name="local_file_not_found_message">ローカルファイルシステムにファイルが見つかりません</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">これ以上フォルダーがありません。</string>
<string name="lock_file">ファイルをロック</string>
<string name="log_send_mail_subject">%1$s アンドロイドアプリログ</string>
<string name="log_send_no_mail_app">ログを送信するためのアプリが見つかりません。メールクライアントをインストールしてください。</string>
<string name="login">ログイン</string>
@ -746,6 +747,7 @@
<string name="trashbin_file_not_restored">ファイル %1$s を復元できませんでした。</string>
<string name="trashbin_loading_failed">ゴミ箱へのロードに失敗しました!</string>
<string name="trashbin_not_emptied">ファイルを完全に削除できませんでした。</string>
<string name="unlock_file">ファイルのロックを解除</string>
<string name="unread_comments">未読のコメントが存在します</string>
<string name="unset_encrypted">暗号化解除</string>
<string name="unset_favorite">お気に入りから削除</string>

View file

@ -394,6 +394,7 @@
<string name="local_file_not_found_message">내부 파일 시스템에서 파일을 찾을 수 없음</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">더 이상의 폴더가 없습니다.</string>
<string name="lock_file">파일 잠금</string>
<string name="log_send_mail_subject">%1$s Android 앱 로그</string>
<string name="log_send_no_mail_app">로그 전송용 앱이 없습니다. 이메일 클라이언트를 설치하십시오.</string>
<string name="login">로그인</string>
@ -765,6 +766,7 @@ Nextcloud를 여기서 확인하십시오: https://nextcloud.com</string>
<string name="trashbin_file_not_restored">%1$s 파일을 복구할 수 없습니다!</string>
<string name="trashbin_loading_failed">휴지통 로딩 실패!</string>
<string name="trashbin_not_emptied">파일을 영구적으로 삭제할 수 없습니다!</string>
<string name="unlock_file">파일 잠금해제</string>
<string name="unread_comments">읽지 않은 댓글 있음</string>
<string name="unset_encrypted">암호화 해제</string>
<string name="unset_favorite">즐겨찾기에서 제거</string>

View file

@ -388,6 +388,7 @@
<string name="local_file_not_found_message">Failas nerastas vietinėje failų sistemoje</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Nėra kitų aplankų</string>
<string name="lock_file">Užrakinti failą</string>
<string name="log_send_mail_subject">%1$s Android žurnalai</string>
<string name="login">Prisijungti</string>
<string name="logs_menu_delete">Ištrinti žurnalus</string>
@ -729,6 +730,7 @@
<string name="trashbin_file_not_restored">Nepavyko atkurti failo %1$s!</string>
<string name="trashbin_loading_failed">Nepavyko įkelti šiukšliadėžės!</string>
<string name="trashbin_not_emptied">Nepavyko ištrinti failų visam laikui!</string>
<string name="unlock_file">Atrakinti failą</string>
<string name="unread_comments">Yra neskaitytų komentarų</string>
<string name="unset_encrypted">Atjungti šifravimą</string>
<string name="unset_favorite">Šalinti iš mėgstamų</string>

View file

@ -355,6 +355,7 @@
<string name="local_file_not_found_message">Датотеката не е пронајдена во локалното складиште</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">.Нема повеќе папки</string>
<string name="lock_file">Заклучи датотека</string>
<string name="log_send_mail_subject">%1$s Апликација на Андроид за логови</string>
<string name="login">Најава</string>
<string name="logs_menu_delete">Избриши записи</string>
@ -665,6 +666,7 @@
<string name="trashbin_file_not_deleted">Датотеката %1$s неможе да се избрише!</string>
<string name="trashbin_file_not_restored">Датотеката %1$s неможе да се врати!</string>
<string name="trashbin_not_emptied">Датотеките неможат да бидат избрижани трајно!</string>
<string name="unlock_file">Отклучи датотека</string>
<string name="unread_comments">Постои непрочитан коментар</string>
<string name="unset_encrypted">Отстрани енкрипција</string>
<string name="unset_favorite">Отстрани од фаворити</string>

View file

@ -398,6 +398,7 @@
<string name="local_file_not_found_message">Het bestand is niet te vinden binnen het lokale bestandssysteem.</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Er zijn niet meer mappen.</string>
<string name="lock_file">Vergrendel bestand</string>
<string name="log_send_mail_subject">%1$s-Android-app-logs</string>
<string name="log_send_no_mail_app">Geen applicatie gevonden voor het versturen van de logs. Installeer een email client.</string>
<string name="login">Inloggen</string>
@ -751,6 +752,7 @@
<string name="trashbin_file_not_restored">Bestand %1$s kon niet worden hersteld!</string>
<string name="trashbin_loading_failed">Laden prullenbak mislukt!</string>
<string name="trashbin_not_emptied">Bestanden konden niet permanent worden verwijderd!</string>
<string name="unlock_file">Ontgrendel bestand</string>
<string name="unread_comments">Er zijn ongelezen reacties</string>
<string name="unset_encrypted">Niet ingestelde versleuteling</string>
<string name="unset_favorite">Verwijderen uit favorieten</string>

View file

@ -401,6 +401,7 @@
<string name="local_file_not_found_message">Plik nie został znaleziony w lokalnym systemie plików</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Nie ma kolejnych katalogów.</string>
<string name="lock_file">Zablokuj plik</string>
<string name="log_send_mail_subject">%1$s logi aplikacji Android</string>
<string name="log_send_no_mail_app">Brak aplikacji do wysyłania logów. Zainstaluj klienta poczty e-mail.</string>
<string name="login">Zaloguj</string>
@ -756,6 +757,7 @@
<string name="trashbin_file_not_restored">Plik %1$s nie może zostać przywrócony!</string>
<string name="trashbin_loading_failed">Wczytywanie kosza nie powiodło się!</string>
<string name="trashbin_not_emptied">Pliki nie mogą być trwale usunięte!</string>
<string name="unlock_file">Odblokuj plik</string>
<string name="unread_comments">Nieprzeczytane komentarze</string>
<string name="unset_encrypted">Wyłącz szyfrowanie</string>
<string name="unset_favorite">Usuń z ulubionych</string>

View file

@ -401,6 +401,7 @@
<string name="local_file_not_found_message">Arquivo não encontrado no sistema de arquivos local</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Não há outras pastas.</string>
<string name="lock_file">Travar arquivo</string>
<string name="log_send_mail_subject">%1$s logs do aplicativo Android</string>
<string name="log_send_no_mail_app">Nenhum aplicativo para envio de registros foi encontrado. Instale um cliente de e-mail.</string>
<string name="login">Entrar</string>
@ -756,6 +757,7 @@
<string name="trashbin_file_not_restored">Arquivo %1$s não pôde ser restaurado!</string>
<string name="trashbin_loading_failed">Falha ao carregar a lixeira! </string>
<string name="trashbin_not_emptied">Os arquivos não puderam ser excluídos permanentemente!</string>
<string name="unlock_file">Destravar arquivo</string>
<string name="unread_comments">Existem comentários não lidos</string>
<string name="unset_encrypted">Definir como não criptografado</string>
<string name="unset_favorite">Remover dos favoritos</string>

View file

@ -387,6 +387,7 @@
<string name="local_file_not_found_message">Ficheiros não encontrados no sistema de ficheiros local</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Não existem mais pastas.</string>
<string name="lock_file">Bloquear ficheiro </string>
<string name="log_send_mail_subject">%1$s registos de aplicação Android</string>
<string name="log_send_no_mail_app">Não encontro aplicação para o envio de logs. Por favor Instale um cliente de email.</string>
<string name="login">Iniciar Sessão</string>
@ -730,6 +731,7 @@ Aproveite o novo e melhorado envio automático.</string>
<string name="trashbin_file_not_restored">Ficheiro %1$s não pôde ser restaurado!</string>
<string name="trashbin_loading_failed">Impossível ler a pasta da reciclagem!</string>
<string name="trashbin_not_emptied">Os ficheiros não puderam ser permanentemente apagados!</string>
<string name="unlock_file">Desbloquear ficheiro</string>
<string name="unread_comments">Existem comentários não lidos</string>
<string name="unset_encrypted">Encriptação não definida</string>
<string name="unset_favorite">Remover dos favoritos</string>

View file

@ -397,6 +397,7 @@
<string name="local_file_not_found_message">Fișierul nu a fost găsit în sistemul local</string>
<string name="local_folder_friendly_path">%1$s/%2$s </string>
<string name="local_folder_list_empty">Nu există dosare.</string>
<string name="lock_file">Blochează fișierul</string>
<string name="log_send_mail_subject">%1$s înregistrările app-ului Android</string>
<string name="log_send_no_mail_app">Nu există nici o aplicație pentru a trimite fisiere log. Vă rugăm instalați un client de email.</string>
<string name="login">Autentificare</string>
@ -749,6 +750,7 @@
<string name="trashbin_file_not_restored">Fișierul %1$s nu a putut fi restaurat!</string>
<string name="trashbin_loading_failed">Încărcarea coșului de gunoi a eșuat!</string>
<string name="trashbin_not_emptied">Fișierele nu au putut fi șterse permanent!</string>
<string name="unlock_file">Deblochează fișierul</string>
<string name="unread_comments">Există comentarii necitite</string>
<string name="unset_encrypted">Dezactivați criptarea</string>
<string name="unset_favorite">Ștergeți din favorite</string>

View file

@ -402,6 +402,7 @@
<string name="local_file_not_found_message">Файл не найден в локальной файловой системе</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Нет других каталогов.</string>
<string name="lock_file">Заблокировать файл</string>
<string name="log_send_mail_subject">Журналы приложения %1$s для Android</string>
<string name="log_send_no_mail_app">Приложение для отправки журналов не найдено. Пожалуйста, установите почтовый клиент.</string>
<string name="login">Войти</string>
@ -757,6 +758,7 @@
<string name="trashbin_file_not_restored">Файл «%1$s» не может быть восстановлен!</string>
<string name="trashbin_loading_failed">Загрузка корзины не удалась!</string>
<string name="trashbin_not_emptied">Файлы не могут быть удалены окончательно!</string>
<string name="unlock_file">Разблокировать файл</string>
<string name="unread_comments">Имеются непрочитанные комментарии</string>
<string name="unset_encrypted">Расшифровать</string>
<string name="unset_favorite">Убрать из избранного</string>

View file

@ -377,6 +377,7 @@
<string name="local_file_not_found_message">Documentu no agatadu in su sistema de documentos locale</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Non ddoe sunt àteras cartellas.</string>
<string name="lock_file">Bloca su documentu</string>
<string name="log_send_mail_subject">%1$s registros de s\'aplicatzione Android</string>
<string name="log_send_no_mail_app">No est è istada agatada peruna aplicatzione pro imbiare is log. Installa unu cliente de posta eletrònica.</string>
<string name="login">Intra</string>
@ -711,6 +712,7 @@
<string name="trashbin_file_not_restored">No at fatu a recuperare s\'archìviu %1$s!</string>
<string name="trashbin_loading_failed">Carrigamentu de s\'àliga faddidu!</string>
<string name="trashbin_not_emptied">No at fatu a cantzellare is archìvios in manera permanente!</string>
<string name="unlock_file">Isbloca su documentu</string>
<string name="unread_comments">Ddoe sunt cummentos non lèghidos</string>
<string name="unset_encrypted">Tzifradura non cunfigurada</string>
<string name="unset_favorite">Boga·nche·ddu dae is preferidos</string>

View file

@ -401,6 +401,7 @@
<string name="local_file_not_found_message">Súbor nebolo možné nájsť na lokálnom úložisku</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Neexistujú ďalšie priečinky.</string>
<string name="lock_file">Zamknúť súbor</string>
<string name="log_send_mail_subject">%1$s Android app logs</string>
<string name="log_send_no_mail_app">Aplikácia na odoslanie logov nenájdená. Prosím nainštalujte emailovú aplikáciu.</string>
<string name="login">Prihlásiť sa</string>
@ -756,6 +757,7 @@
<string name="trashbin_file_not_restored">Súbor %1$s sa nedá obnoviť!</string>
<string name="trashbin_loading_failed">Načítanie obsahu koša sa nepodarilo!</string>
<string name="trashbin_not_emptied">Nepodarilo sa trvalo vymazať súbory!</string>
<string name="unlock_file">Odomknúť súbor</string>
<string name="unread_comments">Existujú neprečítané komentáre</string>
<string name="unset_encrypted">Vypnúť šifrovanie</string>
<string name="unset_favorite">Odstrániť z obľúbených</string>

View file

@ -391,6 +391,7 @@
<string name="local_file_not_found_message">Na krajevnem sistemu datoteke ni mogoče najti</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Ni drugih map.</string>
<string name="lock_file">Zakleni datoteko</string>
<string name="log_send_mail_subject">%1$s dnevniki programa</string>
<string name="log_send_no_mail_app">Ni nameščenega programa za pošiljanje dnevnikov. Namestiti je treba program za elektronsko pošto.</string>
<string name="login">Prijava</string>
@ -737,6 +738,7 @@
<string name="trashbin_file_not_restored">Datoteke %1$s ni mogoče obnoviti!</string>
<string name="trashbin_loading_failed">Nalaganje smeti je spodletelo!</string>
<string name="trashbin_not_emptied">Datotek ni mogoče trajno izbrisati!</string>
<string name="unlock_file">Odkleni datoteko</string>
<string name="unread_comments">Obstajajo so neprebrane opombe</string>
<string name="unset_encrypted">Odstrani šifriranje</string>
<string name="unset_favorite">Odstrani iz priljubljenih</string>

View file

@ -357,6 +357,7 @@
<string name="local_file_not_found_message">Фајл није нађен у локалном фајл-систему</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Нема више фасцикли</string>
<string name="lock_file">Закључај фајл</string>
<string name="log_send_mail_subject">Записници %1$s Андроид апликације</string>
<string name="login">Пријава</string>
<string name="logs_menu_delete">Обриши записнике</string>
@ -671,6 +672,7 @@
<string name="trashbin_file_not_deleted">Фајл %1$s не може да се обрише!</string>
<string name="trashbin_file_not_restored">Фајл %1$s не може да се поврати!</string>
<string name="trashbin_not_emptied">Фајл не може да се обрише неповратно!</string>
<string name="unlock_file">Откључај фајл</string>
<string name="unread_comments">Постоје непрочитани коментари</string>
<string name="unset_encrypted">Искључи шифровање</string>
<string name="unset_favorite">Уклони из омиљених</string>

View file

@ -401,6 +401,7 @@
<string name="local_file_not_found_message">Filen hittades inte i det lokala filsystemet</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Det finna inga fler mappar.</string>
<string name="lock_file">Lås fil</string>
<string name="log_send_mail_subject">%1$s Android-apploggar</string>
<string name="log_send_no_mail_app">Ingen app för att skicka loggar hittades. Vänligen installera en e-postklient.</string>
<string name="login">Logga in</string>
@ -757,6 +758,7 @@
<string name="trashbin_file_not_restored">Fil %1$s kunde inte återställas!</string>
<string name="trashbin_loading_failed">Laddning av papperskorg misslyckades!</string>
<string name="trashbin_not_emptied">Filer kunde inte tas bort permanent!</string>
<string name="unlock_file">Lås upp fil</string>
<string name="unread_comments">Olästa kommentarer finns</string>
<string name="unset_encrypted">Ta bort som krypterad</string>
<string name="unset_favorite">Ta bort från favoriter</string>

View file

@ -401,6 +401,7 @@
<string name="local_file_not_found_message">Dosya yerel dosya sisteminde bulunamadı</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Bu klasörde başka bir klasör yok.</string>
<string name="lock_file">Dosyayı kilitle</string>
<string name="log_send_mail_subject">%1$s Android uygulaması günlükleri</string>
<string name="log_send_no_mail_app">Günlüklerin gönderilebileceği bir uygulama bulunamadı. Lütfen bir e-posta uygulaması kurun.</string>
<string name="login">Oturum aç</string>
@ -756,6 +757,7 @@
<string name="trashbin_file_not_restored">%1$s dosyası geri yüklenemedi!</string>
<string name="trashbin_loading_failed">Çöp kutusu yüklenemedi!</string>
<string name="trashbin_not_emptied">Dosyalar kalıcı olarak silinemedi!</string>
<string name="unlock_file">Dosya kilidini aç</string>
<string name="unread_comments">Okunmamış yorumlar var</string>
<string name="unset_encrypted">Şifrelemeyi kaldır</string>
<string name="unset_favorite">Sık kullanılanlardan kaldır</string>

View file

@ -382,6 +382,7 @@
<string name="local_file_not_found_message">Файл не знайдено у локальній файловій системі</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Тут відсутні інші каталоги.</string>
<string name="lock_file">Заблокувати файл</string>
<string name="log_send_mail_subject">%1$s Android лог додатку</string>
<string name="log_send_no_mail_app">Не знайдено застосунку для надсилання журналу. Будь ласка, встановіть поштовий клієнт.</string>
<string name="login">Увійти</string>
@ -717,6 +718,7 @@
<string name="trashbin_file_not_restored">Файл %1$s неможливо відновити!</string>
<string name="trashbin_loading_failed">Неможливо відкрити кошик!</string>
<string name="trashbin_not_emptied">Неможливо вилучити файли!</string>
<string name="unlock_file">Розблокувати файл</string>
<string name="unread_comments">Доступні непрочитані коментарі</string>
<string name="unset_encrypted">Зняти шифрування</string>
<string name="unset_favorite">Вилучити з улюблених</string>

View file

@ -397,6 +397,7 @@
<string name="local_file_not_found_message">本地文件系统无法找到此文件</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">没有其他文件夹。</string>
<string name="lock_file">锁定文件</string>
<string name="log_send_mail_subject">%1$s 安卓应用日志</string>
<string name="log_send_no_mail_app">没有找到发送日志文件的应用程序。请安装电子邮件客户端。</string>
<string name="login">登录</string>
@ -752,6 +753,7 @@
<string name="trashbin_file_not_restored">文件 %1$s 无法被恢复!</string>
<string name="trashbin_loading_failed">加载垃圾箱失败!</string>
<string name="trashbin_not_emptied">文件无法永久删除!</string>
<string name="unlock_file">解锁文件</string>
<string name="unread_comments">有未读评论</string>
<string name="unset_encrypted">不设置加密</string>
<string name="unset_favorite">从收藏列表移除</string>

View file

@ -255,6 +255,7 @@
<string name="error_comment_file">留言錯誤</string>
<string name="error_crash_title">%1$s失敗</string>
<string name="error_creating_file_from_template">從模板創建檔案時出錯</string>
<string name="error_file_lock">更改檔案上鎖狀態時出錯</string>
<string name="error_report_issue_action">舉報</string>
<string name="error_report_issue_text">想舉報問題需要GitHub賬號</string>
<string name="error_retrieving_file">檢索檔案時發生錯誤</string>
@ -401,6 +402,10 @@
<string name="local_file_not_found_message">近端檔案系統中找不到檔案</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">沒有其他資料夾了</string>
<string name="lock_expiration_info">過期:%1$s</string>
<string name="lock_file">上鎖檔案</string>
<string name="locked_by">被 %1$s 鎖上</string>
<string name="locked_by_app">被 %1$s 應用程式鎖上</string>
<string name="log_send_mail_subject">%1$s Android 應用程式記錄</string>
<string name="log_send_no_mail_app">找不到用於發送記錄的應用程式。請安裝電子郵件客戶端。</string>
<string name="login">登入</string>
@ -756,6 +761,7 @@
<string name="trashbin_file_not_restored">檔案 %1$s 不能被還原!</string>
<string name="trashbin_loading_failed">載入回收桶失敗!</string>
<string name="trashbin_not_emptied">無法永久刪除文件!</string>
<string name="unlock_file">解鎖檔案</string>
<string name="unread_comments">有未讀留言</string>
<string name="unset_encrypted">取消加密</string>
<string name="unset_favorite">從最愛中移除</string>

View file

@ -255,6 +255,7 @@
<string name="error_comment_file">留言錯誤</string>
<string name="error_crash_title">%1$s失敗</string>
<string name="error_creating_file_from_template">從範本建立檔案時發生錯誤</string>
<string name="error_file_lock">變更檔案鎖定狀態時發生錯誤</string>
<string name="error_report_issue_action">回報</string>
<string name="error_report_issue_text">要回報議題至追蹤程式?(需要 GitHub 帳號)</string>
<string name="error_retrieving_file">擷取檔案時發生錯誤</string>
@ -401,6 +402,10 @@
<string name="local_file_not_found_message">本機檔案系統中找不到檔案</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">沒有其他資料夾了</string>
<string name="lock_expiration_info">過期:%1$s</string>
<string name="lock_file">上鎖檔案</string>
<string name="locked_by">被 %1$s 鎖定</string>
<string name="locked_by_app">被 %1$s 應用程式鎖定</string>
<string name="log_send_mail_subject">%1$s Android 應用程式記錄</string>
<string name="log_send_no_mail_app">無應用程式送出的紀錄。請安裝電子郵件客戶端。</string>
<string name="login">登入</string>
@ -756,6 +761,7 @@
<string name="trashbin_file_not_restored">檔案 %1$s 不能被還原!</string>
<string name="trashbin_loading_failed">載入回收桶失敗!</string>
<string name="trashbin_not_emptied">無法永久刪除檔案!</string>
<string name="unlock_file">解鎖檔案</string>
<string name="unread_comments">有未讀留言</string>
<string name="unset_encrypted">取消加密</string>
<string name="unset_favorite">從最愛中移除</string>

View file

@ -1016,4 +1016,10 @@
<string name="select_media_folder">Set media folder</string>
<string name="choose_location">Choose location</string>
<string name="common_select">Select</string>
<string name="lock_file">Lock file</string>
<string name="unlock_file">Unlock file</string>
<string name="error_file_lock">Error changing file lock status</string>
<string name="locked_by">Locked by %1$s</string>
<string name="locked_by_app">Locked by %1$s app</string>
<string name="lock_expiration_info">Expires: %1$s</string>
</resources>

View file

@ -386,6 +386,14 @@
<item name="android:textColorPrimary">@color/text_color</item>
</style>
<style name="Nextcloud.Widget.PopupMenu" parent="@style/Widget.AppCompat.PopupMenu">
<item name="android:textColor">@color/menu_item_text_color</item>
</style>
<style name="Theme.ToolbarWithDisabled" parent="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar">
<item name="android:textColor">@color/menu_item_text_color</item>
</style>
<style name="MaterialListItemSingleLine">
<item name="android:clickable">true</item>
<item name="android:background">?android:selectableItemBackground</item>

View file

@ -0,0 +1,74 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.android.files
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.resources.files.model.FileLockType
import org.junit.Assert
import org.junit.Test
class FileLockingHelperTest {
@Test
fun fileNotLocked_cannotUnlock() {
val file = OCFile("/foo.md").apply {
isLocked = false
lockOwnerId = USER_NAME
lockType = FileLockType.MANUAL
}
Assert.assertFalse(FileLockingHelper.canUserUnlockFile(USER_NAME, file))
}
@Test
fun ownerNotUser_cannotUnlock() {
val file = OCFile("/foo.md").apply {
isLocked = true
lockOwnerId = "bloop"
lockType = FileLockType.MANUAL
}
Assert.assertFalse(FileLockingHelper.canUserUnlockFile(USER_NAME, file))
}
@Test
fun typeNotManual_cannotUnlock() {
val file = OCFile("/foo.md").apply {
isLocked = true
lockOwnerId = USER_NAME
lockType = FileLockType.COLLABORATIVE
}
Assert.assertFalse(FileLockingHelper.canUserUnlockFile(USER_NAME, file))
}
@Test
fun canUnlock() {
val file = OCFile("/foo.md").apply {
isLocked = true
lockOwnerId = USER_NAME
lockType = FileLockType.MANUAL
}
Assert.assertTrue(FileLockingHelper.canUserUnlockFile(USER_NAME, file))
}
companion object {
private const val USER_NAME = "user"
}
}

View file

@ -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"
@ -26,17 +26,15 @@ buildscript {
subprojects {
buildscript {
repositories {
gradlePluginPortal()
google()
maven {
url 'https://plugins.gradle.org/m2/'
}
mavenCentral()
}
}
repositories {
google()
maven { url "https://jitpack.io" }
mavenCentral()
maven { url "https://jitpack.io" }
}
}

View file

@ -1,2 +1,2 @@
DO NOT TOUCH; GENERATED BY DRONE
<span class="mdl-layout-title">Lint Report: 88 warnings</span>
<span class="mdl-layout-title">Lint Report: 87 warnings</span>