Merge remote-tracking branch 'origin/master' into dev

This commit is contained in:
Tobias Kaminsky 2024-11-26 02:36:25 +01:00
commit 1ce24b8a8f
35 changed files with 377 additions and 147 deletions

View file

@ -269,7 +269,7 @@ dependencies {
}
// Jetpack Compose
implementation(platform("androidx.compose:compose-bom:2024.10.01"))
implementation(platform("androidx.compose:compose-bom:2024.11.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.material3:material3")
@ -295,7 +295,7 @@ dependencies {
implementation "androidx.fragment:fragment-ktx:1.8.5"
implementation 'com.github.albfernandez:juniversalchardet:2.0.3' // need this version for Android <7
compileOnly 'com.google.code.findbugs:annotations:3.0.1u2'
implementation 'commons-io:commons-io:2.17.0'
implementation 'commons-io:commons-io:2.18.0'
implementation 'org.greenrobot:eventbus:3.3.1'
implementation 'com.googlecode.ez-vcard:ez-vcard:0.12.1'
implementation 'org.lukhnos:nnio:0.3.1'

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -7,8 +7,8 @@
*/
package com.nextcloud.client;
import android.app.Activity;
import android.content.Intent;
import android.os.Looper;
import com.nextcloud.client.preferences.SubFolderRule;
import com.owncloud.android.AbstractIT;
@ -25,6 +25,7 @@ import org.junit.Test;
import java.util.Objects;
import androidx.appcompat.app.AlertDialog;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@ -78,4 +79,24 @@ public class SyncedFoldersActivityIT extends AbstractIT {
screenshot(Objects.requireNonNull(sut.requireDialog().getWindow()).getDecorView());
}
@Test
@ScreenshotTest
public void showPowerCheckDialog() {
if (Looper.myLooper() == null) {
Looper.prepare();
}
Intent intent = new Intent(targetContext, SyncedFoldersActivity.class);
SyncedFoldersActivity activity = activityRule.launchActivity(intent);
AlertDialog sut = activity.buildPowerCheckDialog();
activity.runOnUiThread(sut::show);
getInstrumentation().waitForIdleSync();
shortSleep();
screenshot(Objects.requireNonNull(sut.getWindow()).getDecorView());
}
}

View file

@ -300,6 +300,14 @@ class OCFileListFragmentStaticServerIT : AbstractIT() {
sut.storageManager.saveFile(this)
}
OCFile("/offlineOperation/").apply {
mimeType = MimeType.DIRECTORY
decryptedRemotePath = "/offlineOperation/"
modificationTimestamp = System.currentTimeMillis()
parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
sut.storageManager.saveFile(this)
}
sut.addFragment(fragment)
shortSleep()

View file

@ -12,18 +12,15 @@ import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.After
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Test
class DrawableUtilTests {
private var sut: DrawableUtil? = null
private var context: Context? = null
@Before
fun setUp() {
sut = DrawableUtil()
context = InstrumentationRegistry.getInstrumentation().context
}
@ -32,18 +29,13 @@ class DrawableUtilTests {
val bitmap: Bitmap = Bitmap.createBitmap(2, 2, Bitmap.Config.ARGB_8888)
val drawable = BitmapDrawable(context?.resources, bitmap)
val layerDrawable = sut?.addDrawableAsOverlay(drawable, drawable)
val layerDrawable = DrawableUtil.addDrawableAsOverlay(drawable, drawable)
if (layerDrawable == null) {
fail("Layer drawable expected to be not null")
}
assert(layerDrawable?.numberOfLayers == 2)
assert(layerDrawable.numberOfLayers == 2)
}
@After
fun destroy() {
sut = null
context = null
}
}

View file

@ -62,7 +62,7 @@ class ShortcutUtil @Inject constructor(private val mContext: Context) {
val isDarkModeActive = syncedFolderProvider.preferences.isDarkModeEnabled
val overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder)
val drawable = MimeTypeUtil.getFileIcon(isDarkModeActive, overlayIconId, mContext, viewThemeUtils)
val drawable = MimeTypeUtil.getFolderIcon(isDarkModeActive, overlayIconId, mContext, viewThemeUtils)
val bitmapIcon = drawable.toBitmap()
icon = IconCompat.createWithBitmap(bitmapIcon)
} else {

View file

@ -18,6 +18,15 @@ fun View?.setVisibleIf(condition: Boolean) {
visibility = if (condition) View.VISIBLE else View.GONE
}
fun View?.makeRounded(context: Context, cornerRadius: Float) {
this?.let {
it.apply {
outlineProvider = createRoundedOutline(context, cornerRadius)
clipToOutline = true
}
}
}
fun createRoundedOutline(context: Context, cornerRadiusValue: Float): ViewOutlineProvider {
return object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {

View file

@ -119,6 +119,9 @@ object MDMConfig {
fun getPort(context: Context): Int =
context.getRestriction(AppConfigKeys.ProxyPort, context.resources.getInteger(R.integer.proxy_port))
fun enforceProtection(context: Context): Boolean =
context.getRestriction(AppConfigKeys.EnforceProtection, context.resources.getBoolean(R.bool.enforce_protection))
@Suppress("UNCHECKED_CAST")
private fun <T : Any> Context.getRestriction(appConfigKey: AppConfigKeys, defaultValue: T): T {
val restrictionsManager = getSystemService(Context.RESTRICTIONS_SERVICE) as? RestrictionsManager

View file

@ -17,11 +17,13 @@ import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.nextcloud.client.preferences.AppPreferences
import com.nextcloud.utils.mdm.MDMConfig
import com.owncloud.android.R
import com.owncloud.android.authentication.AuthenticatorActivity
import com.owncloud.android.databinding.ActivitySplashBinding
import com.owncloud.android.ui.activity.BaseActivity
import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.ui.activity.SettingsActivity
import javax.inject.Inject
class LauncherActivity : BaseActivity() {
@ -65,7 +67,11 @@ class LauncherActivity : BaseActivity() {
private fun scheduleSplashScreen() {
Handler(Looper.getMainLooper()).postDelayed({
if (user.isPresent) {
startActivity(Intent(this, FileDisplayActivity::class.java))
if (MDMConfig.enforceProtection(this) && appPreferences.lockPreference == SettingsActivity.LOCK_NONE) {
startActivity(Intent(this, SettingsActivity::class.java))
} else {
startActivity(Intent(this, FileDisplayActivity::class.java))
}
} else {
startActivity(Intent(this, AuthenticatorActivity::class.java))
}

View file

@ -105,6 +105,7 @@ import com.owncloud.android.services.OperationsService;
import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
import com.owncloud.android.ui.NextcloudWebViewClient;
import com.owncloud.android.ui.activity.FileDisplayActivity;
import com.owncloud.android.ui.activity.SettingsActivity;
import com.owncloud.android.ui.dialog.IndeterminateProgressDialog;
import com.owncloud.android.ui.dialog.SslUntrustedCertDialog;
import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener;
@ -123,6 +124,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@ -1361,11 +1363,17 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
private void endSuccess() {
if (!onlyAdd) {
Intent i = new Intent(this, FileDisplayActivity.class);
i.setAction(FileDisplayActivity.RESTART);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
if (MDMConfig.INSTANCE.enforceProtection(this) && Objects.equals(preferences.getLockPreference(), SettingsActivity.LOCK_NONE)) {
Intent i = new Intent(this, SettingsActivity.class);
startActivity(i);
} else {
Intent i = new Intent(this, FileDisplayActivity.class);
i.setAction(FileDisplayActivity.RESTART);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
}
}
finish();
}

View file

@ -0,0 +1,46 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui
import android.app.AlertDialog
import android.app.Dialog
import android.content.Context
import android.preference.ListPreference
import android.util.AttributeSet
import com.nextcloud.utils.extensions.setVisibleIf
@Suppress("DEPRECATION")
class ListPreferenceDialog(context: Context?, attrs: AttributeSet?) : ListPreference(context, attrs) {
fun showDialog() {
if (!isDialogCreated()) {
onClick()
}
}
fun dismissible(value: Boolean) {
if (isDialogCreated()) {
dialog.setCancelable(value)
dialog.setCanceledOnTouchOutside(value)
}
}
fun enableCancelButton(value: Boolean) {
if (isDialogCreated()) {
(dialog as? AlertDialog)?.let {
val cancelButton = it.getButton(Dialog.BUTTON_NEGATIVE)
cancelButton?.setVisibleIf(value)
cancelButton?.isEnabled = value
}
}
}
private fun isDialogCreated(): Boolean {
return dialog != null
}
}

View file

@ -250,7 +250,7 @@ public abstract class EditorWebView extends ExternalSiteWebView {
boolean isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, user);
Integer overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder);
LayerDrawable drawable = MimeTypeUtil.getFileIcon(preferences.isDarkModeEnabled(), overlayIconId, this, viewThemeUtils);
LayerDrawable drawable = MimeTypeUtil.getFolderIcon(preferences.isDarkModeEnabled(), overlayIconId, this, viewThemeUtils);
binding.thumbnail.setImageDrawable(drawable);
} else {
if ((MimeTypeUtil.isImage(file) || MimeTypeUtil.isVideo(file)) && file.getRemoteId() != null) {

View file

@ -61,6 +61,7 @@ import com.owncloud.android.lib.common.ExternalLink;
import com.owncloud.android.lib.common.ExternalLinkType;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.providers.DocumentsStorageProvider;
import com.owncloud.android.ui.ListPreferenceDialog;
import com.owncloud.android.ui.ThemeableSwitchPreference;
import com.owncloud.android.ui.asynctasks.LoadingVersionNumberTask;
import com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment;
@ -75,6 +76,7 @@ import com.owncloud.android.utils.theme.ViewThemeUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.inject.Inject;
@ -122,7 +124,7 @@ public class SettingsActivity extends PreferenceActivity
private Uri serverBaseUri;
private ListPreference lock;
private ListPreferenceDialog lock;
private ThemeableSwitchPreference showHiddenFiles;
private ThemeableSwitchPreference showEcosystemApps;
private AppCompatDelegate delegate;
@ -139,7 +141,6 @@ public class SettingsActivity extends PreferenceActivity
@Inject ViewThemeUtils viewThemeUtils;
@Inject ConnectivityService connectivityService;
@SuppressWarnings("deprecation")
@Override
public void onCreate(Bundle savedInstanceState) {
@ -185,6 +186,15 @@ public class SettingsActivity extends PreferenceActivity
// workaround for mismatched color when app dark mode and system dark mode don't agree
setListBackground();
showPasscodeDialogIfEnforceAppProtection();
}
private void showPasscodeDialogIfEnforceAppProtection() {
if (MDMConfig.INSTANCE.enforceProtection(this) && Objects.equals(preferences.getLockPreference(), SettingsActivity.LOCK_NONE) && lock != null) {
lock.showDialog();
lock.dismissible(false);
lock.enableCancelButton(false);
}
}
private void setupDevCategory(PreferenceScreen preferenceScreen) {
@ -678,26 +688,35 @@ public class SettingsActivity extends PreferenceActivity
private void setupLockPreference(PreferenceCategory preferenceCategoryDetails,
boolean passCodeEnabled,
boolean deviceCredentialsEnabled) {
lock = (ListPreference) findPreference(PREFERENCE_LOCK);
boolean enforceProtection = MDMConfig.INSTANCE.enforceProtection(this);
lock = (ListPreferenceDialog) findPreference(PREFERENCE_LOCK);
int optionSize = 3;
if (enforceProtection) {
optionSize = 2;
}
if (lock != null && (passCodeEnabled || deviceCredentialsEnabled)) {
ArrayList<String> lockEntries = new ArrayList<>(3);
lockEntries.add(getString(R.string.prefs_lock_none));
ArrayList<String> lockEntries = new ArrayList<>(optionSize);
lockEntries.add(getString(R.string.prefs_lock_using_passcode));
lockEntries.add(getString(R.string.prefs_lock_using_device_credentials));
ArrayList<String> lockValues = new ArrayList<>(3);
lockValues.add(LOCK_NONE);
ArrayList<String> lockValues = new ArrayList<>(optionSize);
lockValues.add(LOCK_PASSCODE);
lockValues.add(LOCK_DEVICE_CREDENTIALS);
if (!passCodeEnabled) {
lockEntries.remove(1);
lockValues.remove(1);
} else if (!deviceCredentialsEnabled ||
!DeviceCredentialUtils.areCredentialsAvailable(getApplicationContext())) {
lockEntries.remove(2);
lockValues.remove(2);
if (!enforceProtection) {
lockEntries.add(getString(R.string.prefs_lock_none));
lockValues.add(LOCK_NONE);
}
if (!passCodeEnabled) {
lockEntries.remove(getString(R.string.prefs_lock_using_passcode));
lockValues.remove(LOCK_PASSCODE);
} else if (!deviceCredentialsEnabled || !DeviceCredentialUtils.areCredentialsAvailable(getApplicationContext())) {
lockEntries.remove(getString(R.string.prefs_lock_using_device_credentials));
lockValues.remove(LOCK_DEVICE_CREDENTIALS);
}
String[] lockEntriesArr = new String[lockEntries.size()];
lockEntriesArr = lockEntries.toArray(lockEntriesArr);
String[] lockValuesArr = new String[lockValues.size()];
@ -706,6 +725,7 @@ public class SettingsActivity extends PreferenceActivity
lock.setEntries(lockEntriesArr);
lock.setEntryValues(lockValuesArr);
lock.setSummary(lock.getEntry());
lock.setOnPreferenceChangeListener((preference, o) -> {
pendingLock = LOCK_NONE;
String oldValue = ((ListPreference) preference).getValue();
@ -799,6 +819,8 @@ public class SettingsActivity extends PreferenceActivity
final PreferenceCategory preferenceCategoryGeneral = (PreferenceCategory) findPreference("general");
viewThemeUtils.files.themePreferenceCategory(preferenceCategoryGeneral);
readStoragePath();
prefDataLoc = findPreference(AppPreferencesImpl.DATA_STORAGE_LOCATION);
if (prefDataLoc != null) {
prefDataLoc.setOnPreferenceClickListener(p -> {
@ -935,7 +957,9 @@ public class SettingsActivity extends PreferenceActivity
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == ACTION_REQUEST_PASSCODE && resultCode == RESULT_OK) {
if (requestCode == ACTION_REQUEST_PASSCODE && resultCode == RESULT_CANCELED) {
showPasscodeDialogIfEnforceAppProtection();
} else if (requestCode == ACTION_REQUEST_PASSCODE && resultCode == RESULT_OK) {
String passcode = data.getStringExtra(PassCodeActivity.KEY_PASSCODE);
if (passcode != null && passcode.length() == 4) {
SharedPreferences.Editor appPrefs = PreferenceManager
@ -978,7 +1002,7 @@ public class SettingsActivity extends PreferenceActivity
} else if (requestCode == ACTION_SET_STORAGE_LOCATION && data != null) {
String newPath = data.getStringExtra(ChooseStorageLocationActivity.KEY_RESULT_STORAGE_LOCATION);
if (!storagePath.equals(newPath)) {
if (storagePath != null && !storagePath.equals(newPath)) {
StorageMigration storageMigration = new StorageMigration(this, user, storagePath, newPath, viewThemeUtils);
storageMigration.setStorageMigrationProgressListener(this);
storageMigration.migrate();
@ -1128,6 +1152,12 @@ public class SettingsActivity extends PreferenceActivity
editor.apply();
}
private void readStoragePath() {
SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
// Load storage path from shared preferences. Use private internal storage by default.
storagePath = appPrefs.getString(AppPreferencesImpl.STORAGE_PATH, getApplicationContext().getFilesDir().getAbsolutePath());
}
@Override
public void onStorageMigrationFinished(String storagePath, boolean succeed) {
if (succeed) {

View file

@ -73,7 +73,7 @@ public class ShareActivity extends FileActivity {
boolean isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, optionalUser.get());
Integer overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder);
LayerDrawable drawable = MimeTypeUtil.getFileIcon(preferences.isDarkModeEnabled(), overlayIconId, this, viewThemeUtils);
LayerDrawable drawable = MimeTypeUtil.getFolderIcon(preferences.isDarkModeEnabled(), overlayIconId, this, viewThemeUtils);
binding.shareFileIcon.setImageDrawable(drawable);
} else {
binding.shareFileIcon.setImageDrawable(MimeTypeUtil.getFileTypeIcon(file.getMimeType(),

View file

@ -20,6 +20,7 @@ import android.text.TextUtils
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.Lifecycle
@ -31,7 +32,6 @@ import com.nextcloud.client.di.Injectable
import com.nextcloud.client.jobs.MediaFoldersDetectionWork
import com.nextcloud.client.jobs.NotificationWork
import com.nextcloud.client.jobs.upload.FileUploadWorker
import com.nextcloud.client.preferences.AppPreferences
import com.nextcloud.client.preferences.SubFolderRule
import com.nextcloud.utils.extensions.getParcelableArgument
import com.nextcloud.utils.extensions.isDialogFragmentReady
@ -56,7 +56,6 @@ import com.owncloud.android.ui.dialog.SyncedFolderPreferencesDialogFragment.OnSy
import com.owncloud.android.ui.dialog.parcel.SyncedFolderParcelable
import com.owncloud.android.utils.PermissionUtil
import com.owncloud.android.utils.SyncedFolderUtils
import com.owncloud.android.utils.theme.ViewThemeUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -135,18 +134,12 @@ class SyncedFoldersActivity :
}
}
@Inject
lateinit var preferences: AppPreferences
@Inject
lateinit var powerManagementService: PowerManagementService
@Inject
lateinit var clock: Clock
@Inject
lateinit var viewThemeUtils: ViewThemeUtils
@Inject
lateinit var syncedFolderProvider: SyncedFolderProvider
@ -221,16 +214,20 @@ class SyncedFoldersActivity :
return true
}
private fun showPowerCheckDialog() {
fun buildPowerCheckDialog(): AlertDialog {
val builder = MaterialAlertDialogBuilder(this)
.setView(R.id.root_layout)
.setPositiveButton(R.string.common_ok) { dialog, _ -> dialog.dismiss() }
.setTitle(R.string.autoupload_disable_power_save_check)
.setMessage(getString(R.string.power_save_check_dialog_message))
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(this, builder)
builder.create().show()
return builder.create()
}
@VisibleForTesting
fun showPowerCheckDialog() {
buildPowerCheckDialog().show()
}
/**
@ -636,6 +633,7 @@ class SyncedFoldersActivity :
binding.emptyList.emptyListIcon.visibility = View.VISIBLE
}
@Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == SyncedFolderPreferencesDialogFragment.REQUEST_CODE__SELECT_REMOTE_FOLDER &&
resultCode == RESULT_OK && dialogFragment != null

View file

@ -35,7 +35,7 @@ class GroupfolderListAdapter(
private fun getFolderIcon(): LayerDrawable? {
val overlayDrawableId = R.drawable.ic_folder_overlay_account_group
return MimeTypeUtil.getFileIcon(false, overlayDrawableId, context, viewThemeUtils)
return MimeTypeUtil.getFolderIcon(false, overlayDrawableId, context, viewThemeUtils)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {

View file

@ -18,7 +18,6 @@ import android.content.ContentValues;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
@ -97,6 +96,7 @@ import java.util.stream.Collectors;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import me.zhanghai.android.fastscroll.PopupTextProvider;
@ -141,6 +141,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
private final long footerId = UUID.randomUUID().getLeastSignificantBits();
private final long headerId = UUID.randomUUID().getLeastSignificantBits();
private final SyncedFolderProvider syncedFolderProvider;
public OCFileListAdapter(
Activity activity,
@ -172,9 +173,8 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
.get(activity)
.getUserData(this.user.toPlatformAccount(),
AccountUtils.Constants.KEY_USER_ID);
this.syncedFolderProvider = syncedFolderProvider;
this.viewThemeUtils = viewThemeUtils;
ocFileListDelegate = new OCFileListDelegate(FileUploadHelper.Companion.instance(),
activity,
ocFileListFragmentInterface,
@ -536,12 +536,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
}
}
ViewExtensionsKt.setVisibleIf(holder.getShared(), !file.isOfflineOperation());
if (file.isFolder()) {
setColorFilterForOfflineCreateFolderOperations(holder, file);
} else {
setColorFilterForOfflineCreateFileOperations(holder, file);
}
configureThumbnail(holder, file);
}
private void bindListItemViewHolder(ListItemViewHolder holder, OCFile file) {
@ -651,75 +646,68 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
holder.getOverflowMenu().setImageResource(R.drawable.ic_dots_vertical);
}
applyVisualsForOfflineOperations(holder, file);
configureThumbnail(holder, file);
}
private void prepareFileSize(ListItemViewHolder holder, OCFile file, long size) {
holder.getFileSize().setVisibility(View.VISIBLE);
ViewExtensionsKt.setVisibleIf(holder.getFileSizeSeparator(), !file.isOfflineOperation());
String fileSizeText = getFileSizeText(file, size);
holder.getFileSize().setText(fileSizeText);
}
private String getFileSizeText(OCFile file, long size) {
OfflineOperationEntity entity = mStorageManager.getOfflineEntityFromOCFile(file);
boolean isRemoveOperation = entity != null && entity.getType() instanceof OfflineOperationType.RemoveFile;
if (!file.isOfflineOperation()) {
return DisplayUtils.bytesToHumanReadable(size);
}
OfflineOperationEntity entity = mStorageManager.getOfflineEntityFromOCFile(file);
boolean isRemoveOperation = (entity != null && entity.getType() instanceof OfflineOperationType.RemoveFile);
if (isRemoveOperation) {
return activity.getString(R.string.oc_file_list_adapter_offline_operation_remove_description_text);
}
if (file.isOfflineOperation()) {
return activity.getString(R.string.oc_file_list_adapter_offline_operation_description_text);
}
return DisplayUtils.bytesToHumanReadable(size);
}
private void applyVisualsForOfflineOperations(ListItemViewHolder holder, OCFile file) {
ViewExtensionsKt.setVisibleIf(holder.getShared(), !file.isOfflineOperation());
if (file.isFolder()) {
setColorFilterForOfflineCreateFolderOperations(holder, file);
} else {
setColorFilterForOfflineCreateFileOperations(holder, file);
}
return activity.getString(R.string.oc_file_list_adapter_offline_operation_description_text);
}
private final ExecutorService executorService = Executors.newCachedThreadPool();
private final Handler mainHandler = new Handler(Looper.getMainLooper());
private void setColorFilterForOfflineCreateFileOperations(ListViewHolder holder, OCFile file) {
if (!file.isOfflineOperation()) {
return;
}
private void configureThumbnail(ListViewHolder holder, OCFile file) {
final var context = MainApp.getAppContext();
executorService.execute(() -> {
OfflineOperationEntity entity = mStorageManager.offlineOperationDao.getByPath(file.getDecryptedRemotePath());
if (file.isOfflineOperation()) {
if (file.isFolder()) {
Drawable icon = ContextCompat.getDrawable(context, R.drawable.ic_folder_offline);
holder.getThumbnail().setImageDrawable(icon);
} else {
executorService.execute(() -> {
OfflineOperationEntity entity = mStorageManager.offlineOperationDao.getByPath(file.getDecryptedRemotePath());
if (entity != null && entity.getType() != null && entity.getType() instanceof OfflineOperationType.CreateFile createFileOperation) {
Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(createFileOperation.getLocalPath(), holder.getThumbnail().getWidth(), holder.getThumbnail().getHeight());
if (bitmap == null) return;
if (entity != null && entity.getType() != null && entity.getType() instanceof OfflineOperationType.CreateFile createFileOperation) {
Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(createFileOperation.getLocalPath(), holder.getThumbnail().getWidth(), holder.getThumbnail().getHeight());
if (bitmap == null) return;
Bitmap thumbnail = BitmapUtils.addColorFilter(bitmap, Color.GRAY,100);
mainHandler.post(() -> holder.getThumbnail().setImageBitmap(thumbnail));
Bitmap thumbnail = BitmapUtils.addColorFilter(bitmap, Color.GRAY,100);
mainHandler.post(() -> holder.getThumbnail().setImageBitmap(thumbnail));
}
});
}
});
} else {
boolean isAutoUpload = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, user);
boolean isDarkModeActive = preferences.isDarkModeEnabled();
Drawable icon = MimeTypeUtil.getOCFileIcon(file, context, viewThemeUtils, isAutoUpload, isDarkModeActive);
holder.getThumbnail().setImageDrawable(icon);
if (!file.isFolder()) {
ViewExtensionsKt.makeRounded(holder.getThumbnail(), context, 4);
}
}
}
public void onDestroy() {
executorService.shutdown();
}
private void setColorFilterForOfflineCreateFolderOperations(ListViewHolder holder, OCFile file) {
if (file.isOfflineOperation()) {
holder.getThumbnail().setColorFilter(Color.GRAY, PorterDuff.Mode.SRC_IN);
} else {
Drawable drawable = viewThemeUtils.platform.tintDrawable(MainApp.getAppContext(), holder.getThumbnail().getDrawable(), ColorRole.PRIMARY);
holder.getThumbnail().setImageDrawable(drawable);
}
}
@Override
public void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) {
if (holder instanceof ListViewHolder) {

View file

@ -21,7 +21,7 @@ import com.nextcloud.client.account.User
import com.nextcloud.client.jobs.download.FileDownloadHelper
import com.nextcloud.client.jobs.upload.FileUploadHelper
import com.nextcloud.client.preferences.AppPreferences
import com.nextcloud.utils.extensions.createRoundedOutline
import com.nextcloud.utils.extensions.makeRounded
import com.nextcloud.utils.mdm.MDMConfig
import com.owncloud.android.R
import com.owncloud.android.datamodel.FileDataStorageManager
@ -250,7 +250,7 @@ class OCFileListDelegate(
if (shouldHideShare) {
gridViewHolder.shared.visibility = View.GONE
} else {
showShareIcon(gridViewHolder, file)
configureSharedIconView(gridViewHolder, file)
}
}
@ -307,9 +307,8 @@ class OCFileListDelegate(
R.color.bg_default
}
gridViewHolder.itemLayout.apply {
outlineProvider = createRoundedOutline(context, cornerRadius)
clipToOutline = true
gridViewHolder.itemLayout.run {
makeRounded(context, cornerRadius)
setBackgroundColor(ContextCompat.getColor(context, itemLayoutBackgroundColorId))
}
}
@ -367,34 +366,38 @@ class OCFileListDelegate(
}
}
private fun showShareIcon(gridViewHolder: ListViewHolder, file: OCFile) {
val sharedIconView = gridViewHolder.shared
private fun configureSharedIconView(gridViewHolder: ListViewHolder, file: OCFile) {
val result = getShareIconIdAndContentDescriptionId(gridViewHolder, file)
gridViewHolder.shared.run {
if (result == null) {
visibility = View.GONE
return
}
setImageResource(result.first)
contentDescription = context.getString(result.second)
visibility = View.VISIBLE
setOnClickListener { ocFileListFragmentInterface.onShareIconClick(file) }
}
}
@Suppress("ReturnCount")
private fun getShareIconIdAndContentDescriptionId(holder: ListViewHolder, file: OCFile): Pair<Int, Int>? {
if (!MDMConfig.sharingSupport(context)) {
sharedIconView.visibility = View.GONE
return
return null
}
if (gridViewHolder is OCFileListItemViewHolder || file.unreadCommentsCount == 0) {
sharedIconView.visibility = View.VISIBLE
if (file.isSharedWithSharee || file.isSharedWithMe) {
if (showShareAvatar) {
sharedIconView.visibility = View.GONE
} else {
sharedIconView.visibility = View.VISIBLE
sharedIconView.setImageResource(R.drawable.shared_via_users)
sharedIconView.contentDescription = context.getString(R.string.shared_icon_shared)
}
} else if (file.isSharedViaLink) {
sharedIconView.setImageResource(R.drawable.shared_via_link)
sharedIconView.contentDescription = context.getString(R.string.shared_icon_shared_via_link)
} else {
sharedIconView.setImageResource(R.drawable.ic_unshared)
sharedIconView.contentDescription = context.getString(R.string.shared_icon_share)
if (file.isOfflineOperation) return null
if (holder !is OCFileListItemViewHolder && file.unreadCommentsCount != 0) return null
return when {
file.isSharedWithSharee || file.isSharedWithMe -> {
if (showShareAvatar) null else R.drawable.shared_via_users to R.string.shared_icon_shared
}
sharedIconView.setOnClickListener { ocFileListFragmentInterface.onShareIconClick(file) }
} else {
sharedIconView.visibility = View.GONE
file.isSharedViaLink -> R.drawable.shared_via_link to R.string.shared_icon_shared_via_link
else -> R.drawable.ic_unshared to R.string.shared_icon_share
}
}

View file

@ -115,7 +115,7 @@ class ReceiveExternalFilesAdapter(
val isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, user)
val isDarkModeActive = syncedFolderProvider.preferences.isDarkModeEnabled
val overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder)
val icon = MimeTypeUtil.getFileIcon(isDarkModeActive, overlayIconId, context, viewThemeUtils)
val icon = MimeTypeUtil.getFolderIcon(isDarkModeActive, overlayIconId, context, viewThemeUtils)
thumbnailImageView.setImageDrawable(icon)
}

View file

@ -37,6 +37,7 @@ import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.KeyboardUtils
import com.owncloud.android.utils.theme.ViewThemeUtils
import java.io.File
import javax.inject.Inject
/**
@ -178,9 +179,13 @@ class RenameFileDialogFragment : DialogFragment(), DialogInterface.OnClickListen
if (isFileHidden(newFileName)) {
binding.userInputContainer.error = getText(R.string.hidden_file_name_warning)
positiveButton?.isEnabled = true
} else if (errorMessage != null) {
binding.userInputContainer.error = errorMessage
positiveButton?.isEnabled = false
} else if (checkExtensionRenamed(newFileName)) {
binding.userInputContainer.error = getText(R.string.warn_rename_extension)
positiveButton?.isEnabled = true
} else if (binding.userInputContainer.error != null) {
binding.userInputContainer.error = null
// Called to remove extra padding
@ -191,6 +196,17 @@ class RenameFileDialogFragment : DialogFragment(), DialogInterface.OnClickListen
override fun afterTextChanged(s: Editable) = Unit
private fun checkExtensionRenamed(newFileName: String): Boolean {
mTargetFile?.fileName?.let { previousFileName ->
val previousExtension = File(previousFileName).extension
val newExtension = File(newFileName).extension
return previousExtension != newExtension
}
return false
}
companion object {
private const val ARG_TARGET_FILE = "TARGET_FILE"
private const val ARG_PARENT_FOLDER = "PARENT_FOLDER"

View file

@ -868,7 +868,7 @@ public final class DisplayUtils {
boolean isDarkModeActive = preferences.isDarkModeEnabled();
Integer overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder);
LayerDrawable fileIcon = MimeTypeUtil.getFileIcon(isDarkModeActive, overlayIconId, context, viewThemeUtils);
LayerDrawable fileIcon = MimeTypeUtil.getFolderIcon(isDarkModeActive, overlayIconId, context, viewThemeUtils);
thumbnailView.setImageDrawable(fileIcon);
} else {
if (file.getRemoteId() != null && file.isPreviewAvailable()) {

View file

@ -7,12 +7,12 @@
*/
package com.owncloud.android.utils
import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import androidx.core.graphics.drawable.DrawableCompat
class DrawableUtil {
object DrawableUtil {
fun changeColor(source: Drawable, color: Int): Drawable {
val drawable = DrawableCompat.wrap(source)
@ -21,13 +21,27 @@ class DrawableUtil {
}
fun addDrawableAsOverlay(backgroundDrawable: Drawable, overlayDrawable: Drawable): LayerDrawable {
val overlayBounds = Rect()
val overlayIconSize = backgroundDrawable.intrinsicWidth / 2
val topMargin = overlayIconSize.div(2)
overlayBounds.set(overlayIconSize, overlayIconSize + topMargin, overlayIconSize, overlayIconSize)
val containerDrawable = LayerDrawable(arrayOf(backgroundDrawable, overlayDrawable))
val layerDrawable = LayerDrawable(arrayOf(backgroundDrawable, overlayDrawable))
layerDrawable.setLayerInset(1, overlayBounds.left, overlayBounds.top, overlayBounds.right, overlayBounds.bottom)
return layerDrawable
val overlayWidth = overlayDrawable.intrinsicWidth
val overlayHeight = overlayDrawable.intrinsicHeight
val backgroundWidth = backgroundDrawable.intrinsicWidth
val backgroundHeight = backgroundDrawable.intrinsicHeight
val scaleFactor = 2f / maxOf(overlayWidth, overlayHeight)
val scaledOverlayWidth = (overlayWidth * scaleFactor).toInt()
val scaledOverlayHeight = (overlayHeight * scaleFactor).toInt()
val left = (backgroundWidth - scaledOverlayWidth) / 2
val top = (backgroundHeight - scaledOverlayHeight) / 2
// Icons are centered on the folder icon. However, some icons take up more vertical space,
// so adding a top margin to all icons helps center the overlay icon better.
val topMargin = 2
containerDrawable.setLayerInset(1, left, top + topMargin, left, top)
(overlayDrawable as? BitmapDrawable)?.setBounds(0, 0, scaledOverlayWidth, scaledOverlayHeight)
return containerDrawable
}
}

View file

@ -11,6 +11,8 @@
package com.owncloud.android.utils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
@ -19,6 +21,7 @@ import android.webkit.MimeTypeMap;
import com.nextcloud.android.common.ui.theme.utils.ColorRole;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
import com.owncloud.android.lib.resources.files.model.ServerFileInterface;
import com.owncloud.android.utils.theme.ViewThemeUtils;
@ -95,9 +98,12 @@ public final class MimeTypeUtil {
if (context != null) {
int iconId = MimeTypeUtil.getFileTypeIconId(mimetype, filename);
Drawable icon = ContextCompat.getDrawable(context, iconId);
if (icon == null) {
return null;
}
if (R.drawable.file_zip == iconId) {
viewThemeUtils.platform.tintPrimaryDrawable(context, icon);
viewThemeUtils.platform.tintDrawable(context, icon, ColorRole.PRIMARY);
}
return icon;
@ -141,6 +147,45 @@ public final class MimeTypeUtil {
return determineIconIdByMimeTypeList(possibleMimeTypes);
}
/**
* Returns a drawable representing a file or folder.
* <p>
*
* - For folders: Returns a folder icon. If an overlay is needed, it includes an overlay icon on the folder.
*
* <p>
* - For files: Returns the file's thumbnail if it exists. Otherwise, it provides a thumbnail based on the file's MIME type.
*
* @return A drawable for the file or folder.
*/
public static Drawable getOCFileIcon(OCFile file, Context context, ViewThemeUtils viewThemeUtils, boolean isAutoUpload, boolean isDarkModeActive) {
if (file.isFolder()) {
Integer overlayIconId = file.getFileOverlayIconId(isAutoUpload);
return MimeTypeUtil.getFolderIcon(isDarkModeActive, overlayIconId, context, viewThemeUtils);
}
if (!(MimeTypeUtil.isImage(file) || MimeTypeUtil.isVideo(file)) || file.getRemoteId() == null) {
return MimeTypeUtil.getFileTypeIcon(file.getMimeType(), file.getFileName(), context, viewThemeUtils);
}
Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.getRemoteId());
if (thumbnail == null || file.isUpdateThumbnailNeeded()) {
return MimeTypeUtil.getFileTypeIcon(file.getMimeType(), file.getFileName(), context, viewThemeUtils);
}
Drawable background = new BitmapDrawable(context.getResources(), thumbnail);
if (!MimeTypeUtil.isVideo(file)) {
return background;
}
Drawable videoOverlay = ContextCompat.getDrawable(context, R.drawable.video_white);
if (videoOverlay == null) {
return background;
}
return DrawableUtil.INSTANCE.addDrawableAsOverlay(background, videoOverlay);
}
public static Drawable getDefaultFolderIcon(Context context, ViewThemeUtils viewThemeUtils) {
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.folder);
assert(drawable != null);
@ -149,7 +194,7 @@ public final class MimeTypeUtil {
return drawable;
}
public static LayerDrawable getFileIcon(Boolean isDarkModeActive, Integer overlayIconId, Context context, ViewThemeUtils viewThemeUtils) {
public static LayerDrawable getFolderIcon(boolean isDarkModeActive, Integer overlayIconId, Context context, ViewThemeUtils viewThemeUtils) {
Drawable folderDrawable = getDefaultFolderIcon(context, viewThemeUtils);
assert(folderDrawable != null);
@ -159,16 +204,14 @@ public final class MimeTypeUtil {
return folderLayerDrawable;
}
DrawableUtil drawableUtil = new DrawableUtil();
Drawable overlayDrawable = ContextCompat.getDrawable(context, overlayIconId);
assert(overlayDrawable != null);
if (isDarkModeActive) {
overlayDrawable = drawableUtil.changeColor(overlayDrawable, R.color.dark);
overlayDrawable = DrawableUtil.INSTANCE.changeColor(overlayDrawable, R.color.dark);
}
return drawableUtil.addDrawableAsOverlay(folderDrawable, overlayDrawable);
return DrawableUtil.INSTANCE.addDrawableAsOverlay(folderDrawable, overlayDrawable);
}
/**

View file

@ -19,5 +19,6 @@ enum class AppConfigKeys(val key: String) {
DisableClipboard("disable_clipboard"),
DisableMoreExternalSite("disable_more_external_site"),
DisableIntro("disable_intro"),
DisableLog("disable_log")
DisableLog("disable_log"),
EnforceProtection("enforce_protection")
}

View file

@ -0,0 +1,16 @@
<!--
~ Nextcloud - Android Client
~
~ SPDX-FileCopyrightText: 2018-2024 Google LLC
~ SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/grey_600"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/grey_600"
android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z" />
</vector>

View file

@ -35,6 +35,13 @@
<string name="advanced_settings">Paramètres avancés</string>
<string name="allow_resharing">Autoriser le repartage</string>
<string name="app_config_base_url_title">URL de base</string>
<string name="app_config_disable_clipboard_title">Désactiver le Presse-papiers</string>
<string name="app_config_disable_intro_title">Désactiver l\'introduction</string>
<string name="app_config_disable_log_title">Désactiver les logs</string>
<string name="app_config_disable_more_external_site_title">Désactiver les sites externes</string>
<string name="app_config_disable_multiaccount_title">Désactiver le Multi-comptes</string>
<string name="app_config_disable_sharing_title">Désactiver le partage</string>
<string name="app_config_proxy_host_title">Nom d\'hôte du proxy</string>
<string name="app_config_proxy_port_title">Port Proxy</string>
<string name="app_widget_description">Affiche un widget du tableau de bord</string>
<string name="appbar_search_in">Recherche dans %s</string>
@ -55,6 +62,7 @@
<string name="assistant_screen_task_delete_success_message">Tâche supprimée avec succès</string>
<string name="assistant_screen_task_list_error_state_message">Impossible de récupérer la liste des tâches, veuillez vérifier votre connexion Internet.</string>
<string name="assistant_screen_task_more_actions_bottom_sheet_delete_action">Supprimer la tâche</string>
<string name="assistant_screen_task_output_empty_text">Le résultat de la tâche n\'est pas encore prêt.</string>
<string name="assistant_screen_task_types_error_state_message">Impossible de récupérer les types des tâches, veuillez vérifier votre connexion Internet.</string>
<string name="assistant_screen_top_bar_title">Assistant</string>
<string name="assistant_screen_unknown_task_status_text">Inconnu</string>
@ -360,6 +368,7 @@
<string name="file_already_exists">Ce nom de fichier existe déjà</string>
<string name="file_delete">Supprimer</string>
<string name="file_detail_activity_error">Erreur lors de la récupération de l\'activité du fichier</string>
<string name="file_detail_sharing_fragment_no_contact_app_message">Aucune application disponible pour sélectionner des contacts</string>
<string name="file_details_no_content">Impossible de charger les détails</string>
<string name="file_icon">Fichier</string>
<string name="file_keep">Conserver</string>
@ -387,6 +396,7 @@
<string name="file_management_permission_optional">Autorisations d\'accès au stockage</string>
<string name="file_management_permission_optional_text">%1$s fonctionne mieux avec les autorisations d\'accès au stockage. Vous pouvez choisir un accès complet à tous les fichiers ou un accès en lecture seule aux photos et vidéos.</string>
<string name="file_management_permission_text">%1$s a besoin d\'autorisations concernant la gestion des fichiers afin de téléverser des fichiers. Vous pouvez choisir un accès complet à tous les fichiers ou un accès en lecture seule aux photos et vidéos.</string>
<string name="file_migration_allow_media_indexing">Autoriser l\'accès à partir d\'autres applications</string>
<string name="file_migration_checking_destination">Vérification de la destination…</string>
<string name="file_migration_cleaning">Nettoyage…</string>
<string name="file_migration_dialog_title">Mise à jour du dossier de stockage des données</string>
@ -397,6 +407,7 @@
<string name="file_migration_failed_not_writable">Le fichier de destination n\'est pas modifiable</string>
<string name="file_migration_failed_while_coping">Échec lors de la migration</string>
<string name="file_migration_failed_while_updating_index">Échec lors de la mise à jour de l\'index</string>
<string name="file_migration_free_space">%1$s\n(%2$s / %3$s)</string>
<string name="file_migration_migrating">Déplacement des données…</string>
<string name="file_migration_ok_finished">Terminé</string>
<string name="file_migration_override_data_folder">Remplacer</string>
@ -592,6 +603,7 @@
<string name="notifications_no_results_headline">Aucune notification</string>
<string name="notifications_no_results_message">Veuillez revenir plus tard.</string>
<string name="oc_file_list_adapter_offline_operation_description_text">Opération en attente</string>
<string name="oc_file_list_adapter_offline_operation_remove_description_text">Opération de suppression en attente</string>
<string name="offline_mode">Aucune connexion Internet</string>
<string name="offline_mode_info_description">Même sans connexion Internet, vous pouvez organiser vos dossiers, créer des fichiers. Dès que vous êtes de nouveau en ligne, vos actions en cours sont automatiquement synchronisées.</string>
<string name="offline_mode_info_title">Vous êtes hors ligne, mais le travail continue</string>
@ -654,6 +666,8 @@
<string name="prefs_category_sync">Synchroniser</string>
<string name="prefs_daily_backup_summary">Sauvegarde quotidienne de votre agenda et des contacts</string>
<string name="prefs_daily_contact_backup_summary">Sauvegarde quotidienne de vos contacts</string>
<string name="prefs_data_storage_location">Emplacement de stockage des données</string>
<string name="prefs_data_storage_location_summary">Gérer l\'emplacement de stockage des données</string>
<string name="prefs_davx5_setup_error">Erreur inattendue lors de la configuration de DAVx5 (anciennement connu sous le nom de DAVdroid)</string>
<string name="prefs_e2e_active">Le chiffrement de bout en bout est configuré !</string>
<string name="prefs_e2e_mnemonic">Phrase secrète E2E</string>
@ -760,6 +774,7 @@
<string name="screenshot_06_davdroid_subline">Synchroniser avec DAVx5</string>
<string name="search_error">Erreur lors de l\'obtention des résultats de recherche</string>
<string name="secure_share_not_set_up">Le partage sécurité n\'est pas configuré pour cet utilisateur</string>
<string name="secure_share_search">Partage sécurisé…</string>
<string name="select_all">Tout sélectionner</string>
<string name="select_media_folder">Paramétrer le dossier média</string>
<string name="select_one_template">Veuillez sélectionner un modèle</string>
@ -803,6 +818,7 @@
<string name="share_permissions">Autorisations pour le partage</string>
<string name="share_remote_clarification">%1$s (distant)</string>
<string name="share_room_clarification">%1$s (conversation)</string>
<string name="share_search">Nom, identifiant de Cloud fédéré ou adresse e-mail…</string>
<string name="share_send_new_email">Envoyer un nouvel e-mail</string>
<string name="share_send_note">Note au destinataire</string>
<string name="share_settings">Paramètres</string>

View file

@ -42,6 +42,7 @@
<bool name="disable_sharing">false</bool>
<bool name="disable_clipboard">false</bool>
<bool name="disable_log">false</bool>
<bool name="enforce_protection">false</bool>
<!-- Flags to enable/disable some features -->
<string name="send_files_to_other_apps">on</string>

View file

@ -35,6 +35,7 @@
<string name="app_config_base_url_title">Base URL</string>
<string name="app_config_proxy_host_title">Proxy Hostname</string>
<string name="app_config_proxy_port_title">Proxy Port</string>
<string name="app_config_enforce_protection_title">Enforce Protection</string>
<string name="offline_operations_file_does_not_exists_yet">File does not exists, yet. Please upload the file first.</string>
<string name="offline_operations_worker_notification_delete_offline_folder">Delete Offline Folder</string>
<string name="offline_operations_worker_notification_conflict_text">Conflicted Folder: %s</string>
@ -1268,11 +1269,10 @@
<string name="please_select_a_server">Please select a server…</string>
<string name="notification_icon_description">Unread notifications exist</string>
<string name="unset_internal_two_way_sync_description">Remove folder from internal two way sync</string>
<string name="two_way_sync_activity_empty_list_title">Two way sync not set up</string>
<string name="two_way_sync_activity_empty_list_desc">To set up a two way sync folder, please enable it in the details tab of the folder in question.</string>
<string name="two_way_sync_activity_title">Internal two way sync</string>
<string name="two_way_sync_activity_disable_all_button_title">Disable for all folders</string>
<string name="oc_file_list_adapter_offline_operation_remove_description_text">Pending Remove Operation</string>
<string name="warn_rename_extension">Changing the extension might cause this file to open in a different application</string>
</resources>

View file

@ -61,4 +61,10 @@
android:restrictionType="bool"
android:title="@string/app_config_disable_log_title" />
<restriction
android:key="enforce_protection"
android:defaultValue="false"
android:restrictionType="bool"
android:title="@string/app_config_enforce_protection_title" />
</restrictions>

View file

@ -35,7 +35,7 @@
</PreferenceCategory>
<PreferenceCategory android:title="@string/prefs_category_details" android:key="details">
<ListPreference
<com.owncloud.android.ui.ListPreferenceDialog
android:title="@string/prefs_lock"
android:key="lock"
android:dialogTitle="@string/prefs_lock_title"

View file

@ -1028,6 +1028,11 @@
<sha256 value="e4b122da072402de71c8a20b85289d68e4d4446d7b1bee6dee4fef9d4677130b" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="androidx.compose" name="compose-bom" version="2024.11.00">
<artifact name="compose-bom-2024.11.00.pom">
<sha256 value="13ddc489c6c373e6e5e70f0d9f3b005c5f1f201c3e906245dce1cd58d15d73b9" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="androidx.compose.animation" name="animation" version="1.7.5">
<artifact name="animation-1.7.5.module">
<sha256 value="7c742bb407497fb4c322bd3d4abef83741ea6107a12168f579438303433497ff" origin="Generated by Gradle"/>