Merge remote-tracking branch 'origin/master' into dev
|
@ -269,7 +269,7 @@ dependencies {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jetpack Compose
|
// 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")
|
||||||
implementation("androidx.compose.ui:ui-graphics")
|
implementation("androidx.compose.ui:ui-graphics")
|
||||||
implementation("androidx.compose.material3:material3")
|
implementation("androidx.compose.material3:material3")
|
||||||
|
@ -295,7 +295,7 @@ dependencies {
|
||||||
implementation "androidx.fragment:fragment-ktx:1.8.5"
|
implementation "androidx.fragment:fragment-ktx:1.8.5"
|
||||||
implementation 'com.github.albfernandez:juniversalchardet:2.0.3' // need this version for Android <7
|
implementation 'com.github.albfernandez:juniversalchardet:2.0.3' // need this version for Android <7
|
||||||
compileOnly 'com.google.code.findbugs:annotations:3.0.1u2'
|
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 'org.greenrobot:eventbus:3.3.1'
|
||||||
implementation 'com.googlecode.ez-vcard:ez-vcard:0.12.1'
|
implementation 'com.googlecode.ez-vcard:ez-vcard:0.12.1'
|
||||||
implementation 'org.lukhnos:nnio:0.3.1'
|
implementation 'org.lukhnos:nnio:0.3.1'
|
||||||
|
|
After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 20 KiB |
|
@ -7,8 +7,8 @@
|
||||||
*/
|
*/
|
||||||
package com.nextcloud.client;
|
package com.nextcloud.client;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
import com.nextcloud.client.preferences.SubFolderRule;
|
import com.nextcloud.client.preferences.SubFolderRule;
|
||||||
import com.owncloud.android.AbstractIT;
|
import com.owncloud.android.AbstractIT;
|
||||||
|
@ -25,6 +25,7 @@ import org.junit.Test;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.test.espresso.intent.rule.IntentsTestRule;
|
import androidx.test.espresso.intent.rule.IntentsTestRule;
|
||||||
|
|
||||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||||
|
@ -78,4 +79,24 @@ public class SyncedFoldersActivityIT extends AbstractIT {
|
||||||
|
|
||||||
screenshot(Objects.requireNonNull(sut.requireDialog().getWindow()).getDecorView());
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -300,6 +300,14 @@ class OCFileListFragmentStaticServerIT : AbstractIT() {
|
||||||
sut.storageManager.saveFile(this)
|
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)
|
sut.addFragment(fragment)
|
||||||
|
|
||||||
shortSleep()
|
shortSleep()
|
||||||
|
|
|
@ -12,18 +12,15 @@ import android.graphics.Bitmap
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Assert.fail
|
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class DrawableUtilTests {
|
class DrawableUtilTests {
|
||||||
|
|
||||||
private var sut: DrawableUtil? = null
|
|
||||||
private var context: Context? = null
|
private var context: Context? = null
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
sut = DrawableUtil()
|
|
||||||
context = InstrumentationRegistry.getInstrumentation().context
|
context = InstrumentationRegistry.getInstrumentation().context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,18 +29,13 @@ class DrawableUtilTests {
|
||||||
val bitmap: Bitmap = Bitmap.createBitmap(2, 2, Bitmap.Config.ARGB_8888)
|
val bitmap: Bitmap = Bitmap.createBitmap(2, 2, Bitmap.Config.ARGB_8888)
|
||||||
val drawable = BitmapDrawable(context?.resources, bitmap)
|
val drawable = BitmapDrawable(context?.resources, bitmap)
|
||||||
|
|
||||||
val layerDrawable = sut?.addDrawableAsOverlay(drawable, drawable)
|
val layerDrawable = DrawableUtil.addDrawableAsOverlay(drawable, drawable)
|
||||||
|
|
||||||
if (layerDrawable == null) {
|
assert(layerDrawable.numberOfLayers == 2)
|
||||||
fail("Layer drawable expected to be not null")
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(layerDrawable?.numberOfLayers == 2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
fun destroy() {
|
fun destroy() {
|
||||||
sut = null
|
|
||||||
context = null
|
context = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ class ShortcutUtil @Inject constructor(private val mContext: Context) {
|
||||||
val isDarkModeActive = syncedFolderProvider.preferences.isDarkModeEnabled
|
val isDarkModeActive = syncedFolderProvider.preferences.isDarkModeEnabled
|
||||||
|
|
||||||
val overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder)
|
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()
|
val bitmapIcon = drawable.toBitmap()
|
||||||
icon = IconCompat.createWithBitmap(bitmapIcon)
|
icon = IconCompat.createWithBitmap(bitmapIcon)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -18,6 +18,15 @@ fun View?.setVisibleIf(condition: Boolean) {
|
||||||
visibility = if (condition) View.VISIBLE else View.GONE
|
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 {
|
fun createRoundedOutline(context: Context, cornerRadiusValue: Float): ViewOutlineProvider {
|
||||||
return object : ViewOutlineProvider() {
|
return object : ViewOutlineProvider() {
|
||||||
override fun getOutline(view: View, outline: Outline) {
|
override fun getOutline(view: View, outline: Outline) {
|
||||||
|
|
|
@ -119,6 +119,9 @@ object MDMConfig {
|
||||||
fun getPort(context: Context): Int =
|
fun getPort(context: Context): Int =
|
||||||
context.getRestriction(AppConfigKeys.ProxyPort, context.resources.getInteger(R.integer.proxy_port))
|
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")
|
@Suppress("UNCHECKED_CAST")
|
||||||
private fun <T : Any> Context.getRestriction(appConfigKey: AppConfigKeys, defaultValue: T): T {
|
private fun <T : Any> Context.getRestriction(appConfigKey: AppConfigKeys, defaultValue: T): T {
|
||||||
val restrictionsManager = getSystemService(Context.RESTRICTIONS_SERVICE) as? RestrictionsManager
|
val restrictionsManager = getSystemService(Context.RESTRICTIONS_SERVICE) as? RestrictionsManager
|
||||||
|
|
|
@ -17,11 +17,13 @@ import android.view.View
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import com.nextcloud.client.preferences.AppPreferences
|
import com.nextcloud.client.preferences.AppPreferences
|
||||||
|
import com.nextcloud.utils.mdm.MDMConfig
|
||||||
import com.owncloud.android.R
|
import com.owncloud.android.R
|
||||||
import com.owncloud.android.authentication.AuthenticatorActivity
|
import com.owncloud.android.authentication.AuthenticatorActivity
|
||||||
import com.owncloud.android.databinding.ActivitySplashBinding
|
import com.owncloud.android.databinding.ActivitySplashBinding
|
||||||
import com.owncloud.android.ui.activity.BaseActivity
|
import com.owncloud.android.ui.activity.BaseActivity
|
||||||
import com.owncloud.android.ui.activity.FileDisplayActivity
|
import com.owncloud.android.ui.activity.FileDisplayActivity
|
||||||
|
import com.owncloud.android.ui.activity.SettingsActivity
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class LauncherActivity : BaseActivity() {
|
class LauncherActivity : BaseActivity() {
|
||||||
|
@ -65,7 +67,11 @@ class LauncherActivity : BaseActivity() {
|
||||||
private fun scheduleSplashScreen() {
|
private fun scheduleSplashScreen() {
|
||||||
Handler(Looper.getMainLooper()).postDelayed({
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
if (user.isPresent) {
|
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 {
|
} else {
|
||||||
startActivity(Intent(this, AuthenticatorActivity::class.java))
|
startActivity(Intent(this, AuthenticatorActivity::class.java))
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,6 +105,7 @@ import com.owncloud.android.services.OperationsService;
|
||||||
import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
|
import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
|
||||||
import com.owncloud.android.ui.NextcloudWebViewClient;
|
import com.owncloud.android.ui.NextcloudWebViewClient;
|
||||||
import com.owncloud.android.ui.activity.FileDisplayActivity;
|
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.IndeterminateProgressDialog;
|
||||||
import com.owncloud.android.ui.dialog.SslUntrustedCertDialog;
|
import com.owncloud.android.ui.dialog.SslUntrustedCertDialog;
|
||||||
import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener;
|
import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener;
|
||||||
|
@ -123,6 +124,7 @@ import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
@ -1361,11 +1363,17 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
|
||||||
|
|
||||||
private void endSuccess() {
|
private void endSuccess() {
|
||||||
if (!onlyAdd) {
|
if (!onlyAdd) {
|
||||||
Intent i = new Intent(this, FileDisplayActivity.class);
|
if (MDMConfig.INSTANCE.enforceProtection(this) && Objects.equals(preferences.getLockPreference(), SettingsActivity.LOCK_NONE)) {
|
||||||
i.setAction(FileDisplayActivity.RESTART);
|
Intent i = new Intent(this, SettingsActivity.class);
|
||||||
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
startActivity(i);
|
||||||
startActivity(i);
|
} else {
|
||||||
|
Intent i = new Intent(this, FileDisplayActivity.class);
|
||||||
|
i.setAction(FileDisplayActivity.RESTART);
|
||||||
|
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
startActivity(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -250,7 +250,7 @@ public abstract class EditorWebView extends ExternalSiteWebView {
|
||||||
boolean isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, user);
|
boolean isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, user);
|
||||||
|
|
||||||
Integer overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder);
|
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);
|
binding.thumbnail.setImageDrawable(drawable);
|
||||||
} else {
|
} else {
|
||||||
if ((MimeTypeUtil.isImage(file) || MimeTypeUtil.isVideo(file)) && file.getRemoteId() != null) {
|
if ((MimeTypeUtil.isImage(file) || MimeTypeUtil.isVideo(file)) && file.getRemoteId() != null) {
|
||||||
|
|
|
@ -61,6 +61,7 @@ import com.owncloud.android.lib.common.ExternalLink;
|
||||||
import com.owncloud.android.lib.common.ExternalLinkType;
|
import com.owncloud.android.lib.common.ExternalLinkType;
|
||||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||||
import com.owncloud.android.providers.DocumentsStorageProvider;
|
import com.owncloud.android.providers.DocumentsStorageProvider;
|
||||||
|
import com.owncloud.android.ui.ListPreferenceDialog;
|
||||||
import com.owncloud.android.ui.ThemeableSwitchPreference;
|
import com.owncloud.android.ui.ThemeableSwitchPreference;
|
||||||
import com.owncloud.android.ui.asynctasks.LoadingVersionNumberTask;
|
import com.owncloud.android.ui.asynctasks.LoadingVersionNumberTask;
|
||||||
import com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment;
|
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.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@ -122,7 +124,7 @@ public class SettingsActivity extends PreferenceActivity
|
||||||
|
|
||||||
private Uri serverBaseUri;
|
private Uri serverBaseUri;
|
||||||
|
|
||||||
private ListPreference lock;
|
private ListPreferenceDialog lock;
|
||||||
private ThemeableSwitchPreference showHiddenFiles;
|
private ThemeableSwitchPreference showHiddenFiles;
|
||||||
private ThemeableSwitchPreference showEcosystemApps;
|
private ThemeableSwitchPreference showEcosystemApps;
|
||||||
private AppCompatDelegate delegate;
|
private AppCompatDelegate delegate;
|
||||||
|
@ -139,7 +141,6 @@ public class SettingsActivity extends PreferenceActivity
|
||||||
@Inject ViewThemeUtils viewThemeUtils;
|
@Inject ViewThemeUtils viewThemeUtils;
|
||||||
@Inject ConnectivityService connectivityService;
|
@Inject ConnectivityService connectivityService;
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
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
|
// workaround for mismatched color when app dark mode and system dark mode don't agree
|
||||||
setListBackground();
|
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) {
|
private void setupDevCategory(PreferenceScreen preferenceScreen) {
|
||||||
|
@ -678,26 +688,35 @@ public class SettingsActivity extends PreferenceActivity
|
||||||
private void setupLockPreference(PreferenceCategory preferenceCategoryDetails,
|
private void setupLockPreference(PreferenceCategory preferenceCategoryDetails,
|
||||||
boolean passCodeEnabled,
|
boolean passCodeEnabled,
|
||||||
boolean deviceCredentialsEnabled) {
|
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)) {
|
if (lock != null && (passCodeEnabled || deviceCredentialsEnabled)) {
|
||||||
ArrayList<String> lockEntries = new ArrayList<>(3);
|
ArrayList<String> lockEntries = new ArrayList<>(optionSize);
|
||||||
lockEntries.add(getString(R.string.prefs_lock_none));
|
|
||||||
lockEntries.add(getString(R.string.prefs_lock_using_passcode));
|
lockEntries.add(getString(R.string.prefs_lock_using_passcode));
|
||||||
lockEntries.add(getString(R.string.prefs_lock_using_device_credentials));
|
lockEntries.add(getString(R.string.prefs_lock_using_device_credentials));
|
||||||
|
|
||||||
ArrayList<String> lockValues = new ArrayList<>(3);
|
ArrayList<String> lockValues = new ArrayList<>(optionSize);
|
||||||
lockValues.add(LOCK_NONE);
|
|
||||||
lockValues.add(LOCK_PASSCODE);
|
lockValues.add(LOCK_PASSCODE);
|
||||||
lockValues.add(LOCK_DEVICE_CREDENTIALS);
|
lockValues.add(LOCK_DEVICE_CREDENTIALS);
|
||||||
|
|
||||||
if (!passCodeEnabled) {
|
if (!enforceProtection) {
|
||||||
lockEntries.remove(1);
|
lockEntries.add(getString(R.string.prefs_lock_none));
|
||||||
lockValues.remove(1);
|
lockValues.add(LOCK_NONE);
|
||||||
} else if (!deviceCredentialsEnabled ||
|
|
||||||
!DeviceCredentialUtils.areCredentialsAvailable(getApplicationContext())) {
|
|
||||||
lockEntries.remove(2);
|
|
||||||
lockValues.remove(2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()];
|
String[] lockEntriesArr = new String[lockEntries.size()];
|
||||||
lockEntriesArr = lockEntries.toArray(lockEntriesArr);
|
lockEntriesArr = lockEntries.toArray(lockEntriesArr);
|
||||||
String[] lockValuesArr = new String[lockValues.size()];
|
String[] lockValuesArr = new String[lockValues.size()];
|
||||||
|
@ -706,6 +725,7 @@ public class SettingsActivity extends PreferenceActivity
|
||||||
lock.setEntries(lockEntriesArr);
|
lock.setEntries(lockEntriesArr);
|
||||||
lock.setEntryValues(lockValuesArr);
|
lock.setEntryValues(lockValuesArr);
|
||||||
lock.setSummary(lock.getEntry());
|
lock.setSummary(lock.getEntry());
|
||||||
|
|
||||||
lock.setOnPreferenceChangeListener((preference, o) -> {
|
lock.setOnPreferenceChangeListener((preference, o) -> {
|
||||||
pendingLock = LOCK_NONE;
|
pendingLock = LOCK_NONE;
|
||||||
String oldValue = ((ListPreference) preference).getValue();
|
String oldValue = ((ListPreference) preference).getValue();
|
||||||
|
@ -799,6 +819,8 @@ public class SettingsActivity extends PreferenceActivity
|
||||||
final PreferenceCategory preferenceCategoryGeneral = (PreferenceCategory) findPreference("general");
|
final PreferenceCategory preferenceCategoryGeneral = (PreferenceCategory) findPreference("general");
|
||||||
viewThemeUtils.files.themePreferenceCategory(preferenceCategoryGeneral);
|
viewThemeUtils.files.themePreferenceCategory(preferenceCategoryGeneral);
|
||||||
|
|
||||||
|
readStoragePath();
|
||||||
|
|
||||||
prefDataLoc = findPreference(AppPreferencesImpl.DATA_STORAGE_LOCATION);
|
prefDataLoc = findPreference(AppPreferencesImpl.DATA_STORAGE_LOCATION);
|
||||||
if (prefDataLoc != null) {
|
if (prefDataLoc != null) {
|
||||||
prefDataLoc.setOnPreferenceClickListener(p -> {
|
prefDataLoc.setOnPreferenceClickListener(p -> {
|
||||||
|
@ -935,7 +957,9 @@ public class SettingsActivity extends PreferenceActivity
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
super.onActivityResult(requestCode, resultCode, 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);
|
String passcode = data.getStringExtra(PassCodeActivity.KEY_PASSCODE);
|
||||||
if (passcode != null && passcode.length() == 4) {
|
if (passcode != null && passcode.length() == 4) {
|
||||||
SharedPreferences.Editor appPrefs = PreferenceManager
|
SharedPreferences.Editor appPrefs = PreferenceManager
|
||||||
|
@ -978,7 +1002,7 @@ public class SettingsActivity extends PreferenceActivity
|
||||||
} else if (requestCode == ACTION_SET_STORAGE_LOCATION && data != null) {
|
} else if (requestCode == ACTION_SET_STORAGE_LOCATION && data != null) {
|
||||||
String newPath = data.getStringExtra(ChooseStorageLocationActivity.KEY_RESULT_STORAGE_LOCATION);
|
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 storageMigration = new StorageMigration(this, user, storagePath, newPath, viewThemeUtils);
|
||||||
storageMigration.setStorageMigrationProgressListener(this);
|
storageMigration.setStorageMigrationProgressListener(this);
|
||||||
storageMigration.migrate();
|
storageMigration.migrate();
|
||||||
|
@ -1128,6 +1152,12 @@ public class SettingsActivity extends PreferenceActivity
|
||||||
editor.apply();
|
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
|
@Override
|
||||||
public void onStorageMigrationFinished(String storagePath, boolean succeed) {
|
public void onStorageMigrationFinished(String storagePath, boolean succeed) {
|
||||||
if (succeed) {
|
if (succeed) {
|
||||||
|
|
|
@ -73,7 +73,7 @@ public class ShareActivity extends FileActivity {
|
||||||
boolean isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, optionalUser.get());
|
boolean isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, optionalUser.get());
|
||||||
|
|
||||||
Integer overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder);
|
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);
|
binding.shareFileIcon.setImageDrawable(drawable);
|
||||||
} else {
|
} else {
|
||||||
binding.shareFileIcon.setImageDrawable(MimeTypeUtil.getFileTypeIcon(file.getMimeType(),
|
binding.shareFileIcon.setImageDrawable(MimeTypeUtil.getFileTypeIcon(file.getMimeType(),
|
||||||
|
|
|
@ -20,6 +20,7 @@ import android.text.TextUtils
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.drawerlayout.widget.DrawerLayout
|
import androidx.drawerlayout.widget.DrawerLayout
|
||||||
import androidx.lifecycle.Lifecycle
|
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.MediaFoldersDetectionWork
|
||||||
import com.nextcloud.client.jobs.NotificationWork
|
import com.nextcloud.client.jobs.NotificationWork
|
||||||
import com.nextcloud.client.jobs.upload.FileUploadWorker
|
import com.nextcloud.client.jobs.upload.FileUploadWorker
|
||||||
import com.nextcloud.client.preferences.AppPreferences
|
|
||||||
import com.nextcloud.client.preferences.SubFolderRule
|
import com.nextcloud.client.preferences.SubFolderRule
|
||||||
import com.nextcloud.utils.extensions.getParcelableArgument
|
import com.nextcloud.utils.extensions.getParcelableArgument
|
||||||
import com.nextcloud.utils.extensions.isDialogFragmentReady
|
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.ui.dialog.parcel.SyncedFolderParcelable
|
||||||
import com.owncloud.android.utils.PermissionUtil
|
import com.owncloud.android.utils.PermissionUtil
|
||||||
import com.owncloud.android.utils.SyncedFolderUtils
|
import com.owncloud.android.utils.SyncedFolderUtils
|
||||||
import com.owncloud.android.utils.theme.ViewThemeUtils
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
@ -135,18 +134,12 @@ class SyncedFoldersActivity :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var preferences: AppPreferences
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var powerManagementService: PowerManagementService
|
lateinit var powerManagementService: PowerManagementService
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var clock: Clock
|
lateinit var clock: Clock
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var viewThemeUtils: ViewThemeUtils
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var syncedFolderProvider: SyncedFolderProvider
|
lateinit var syncedFolderProvider: SyncedFolderProvider
|
||||||
|
|
||||||
|
@ -221,16 +214,20 @@ class SyncedFoldersActivity :
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showPowerCheckDialog() {
|
fun buildPowerCheckDialog(): AlertDialog {
|
||||||
val builder = MaterialAlertDialogBuilder(this)
|
val builder = MaterialAlertDialogBuilder(this)
|
||||||
.setView(R.id.root_layout)
|
|
||||||
.setPositiveButton(R.string.common_ok) { dialog, _ -> dialog.dismiss() }
|
.setPositiveButton(R.string.common_ok) { dialog, _ -> dialog.dismiss() }
|
||||||
.setTitle(R.string.autoupload_disable_power_save_check)
|
.setTitle(R.string.autoupload_disable_power_save_check)
|
||||||
.setMessage(getString(R.string.power_save_check_dialog_message))
|
.setMessage(getString(R.string.power_save_check_dialog_message))
|
||||||
|
|
||||||
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(this, builder)
|
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
|
binding.emptyList.emptyListIcon.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
if (requestCode == SyncedFolderPreferencesDialogFragment.REQUEST_CODE__SELECT_REMOTE_FOLDER &&
|
if (requestCode == SyncedFolderPreferencesDialogFragment.REQUEST_CODE__SELECT_REMOTE_FOLDER &&
|
||||||
resultCode == RESULT_OK && dialogFragment != null
|
resultCode == RESULT_OK && dialogFragment != null
|
||||||
|
|
|
@ -35,7 +35,7 @@ class GroupfolderListAdapter(
|
||||||
|
|
||||||
private fun getFolderIcon(): LayerDrawable? {
|
private fun getFolderIcon(): LayerDrawable? {
|
||||||
val overlayDrawableId = R.drawable.ic_folder_overlay_account_group
|
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 {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
|
|
@ -18,7 +18,6 @@ import android.content.ContentValues;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.PorterDuff;
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
@ -97,6 +96,7 @@ import java.util.stream.Collectors;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||||
import me.zhanghai.android.fastscroll.PopupTextProvider;
|
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 footerId = UUID.randomUUID().getLeastSignificantBits();
|
||||||
private final long headerId = UUID.randomUUID().getLeastSignificantBits();
|
private final long headerId = UUID.randomUUID().getLeastSignificantBits();
|
||||||
|
private final SyncedFolderProvider syncedFolderProvider;
|
||||||
|
|
||||||
public OCFileListAdapter(
|
public OCFileListAdapter(
|
||||||
Activity activity,
|
Activity activity,
|
||||||
|
@ -172,9 +173,8 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
||||||
.get(activity)
|
.get(activity)
|
||||||
.getUserData(this.user.toPlatformAccount(),
|
.getUserData(this.user.toPlatformAccount(),
|
||||||
AccountUtils.Constants.KEY_USER_ID);
|
AccountUtils.Constants.KEY_USER_ID);
|
||||||
|
this.syncedFolderProvider = syncedFolderProvider;
|
||||||
this.viewThemeUtils = viewThemeUtils;
|
this.viewThemeUtils = viewThemeUtils;
|
||||||
|
|
||||||
ocFileListDelegate = new OCFileListDelegate(FileUploadHelper.Companion.instance(),
|
ocFileListDelegate = new OCFileListDelegate(FileUploadHelper.Companion.instance(),
|
||||||
activity,
|
activity,
|
||||||
ocFileListFragmentInterface,
|
ocFileListFragmentInterface,
|
||||||
|
@ -536,12 +536,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewExtensionsKt.setVisibleIf(holder.getShared(), !file.isOfflineOperation());
|
configureThumbnail(holder, file);
|
||||||
if (file.isFolder()) {
|
|
||||||
setColorFilterForOfflineCreateFolderOperations(holder, file);
|
|
||||||
} else {
|
|
||||||
setColorFilterForOfflineCreateFileOperations(holder, file);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindListItemViewHolder(ListItemViewHolder holder, OCFile 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);
|
holder.getOverflowMenu().setImageResource(R.drawable.ic_dots_vertical);
|
||||||
}
|
}
|
||||||
|
|
||||||
applyVisualsForOfflineOperations(holder, file);
|
configureThumbnail(holder, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareFileSize(ListItemViewHolder holder, OCFile file, long size) {
|
private void prepareFileSize(ListItemViewHolder holder, OCFile file, long size) {
|
||||||
holder.getFileSize().setVisibility(View.VISIBLE);
|
holder.getFileSize().setVisibility(View.VISIBLE);
|
||||||
ViewExtensionsKt.setVisibleIf(holder.getFileSizeSeparator(), !file.isOfflineOperation());
|
|
||||||
String fileSizeText = getFileSizeText(file, size);
|
String fileSizeText = getFileSizeText(file, size);
|
||||||
holder.getFileSize().setText(fileSizeText);
|
holder.getFileSize().setText(fileSizeText);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getFileSizeText(OCFile file, long size) {
|
private String getFileSizeText(OCFile file, long size) {
|
||||||
OfflineOperationEntity entity = mStorageManager.getOfflineEntityFromOCFile(file);
|
if (!file.isOfflineOperation()) {
|
||||||
boolean isRemoveOperation = entity != null && entity.getType() instanceof OfflineOperationType.RemoveFile;
|
return DisplayUtils.bytesToHumanReadable(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
OfflineOperationEntity entity = mStorageManager.getOfflineEntityFromOCFile(file);
|
||||||
|
boolean isRemoveOperation = (entity != null && entity.getType() instanceof OfflineOperationType.RemoveFile);
|
||||||
if (isRemoveOperation) {
|
if (isRemoveOperation) {
|
||||||
return activity.getString(R.string.oc_file_list_adapter_offline_operation_remove_description_text);
|
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 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ExecutorService executorService = Executors.newCachedThreadPool();
|
private final ExecutorService executorService = Executors.newCachedThreadPool();
|
||||||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
private void setColorFilterForOfflineCreateFileOperations(ListViewHolder holder, OCFile file) {
|
private void configureThumbnail(ListViewHolder holder, OCFile file) {
|
||||||
if (!file.isOfflineOperation()) {
|
final var context = MainApp.getAppContext();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
executorService.execute(() -> {
|
if (file.isOfflineOperation()) {
|
||||||
OfflineOperationEntity entity = mStorageManager.offlineOperationDao.getByPath(file.getDecryptedRemotePath());
|
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) {
|
if (entity != null && entity.getType() != null && entity.getType() instanceof OfflineOperationType.CreateFile createFileOperation) {
|
||||||
Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(createFileOperation.getLocalPath(), holder.getThumbnail().getWidth(), holder.getThumbnail().getHeight());
|
Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(createFileOperation.getLocalPath(), holder.getThumbnail().getWidth(), holder.getThumbnail().getHeight());
|
||||||
if (bitmap == null) return;
|
if (bitmap == null) return;
|
||||||
|
|
||||||
Bitmap thumbnail = BitmapUtils.addColorFilter(bitmap, Color.GRAY,100);
|
Bitmap thumbnail = BitmapUtils.addColorFilter(bitmap, Color.GRAY,100);
|
||||||
mainHandler.post(() -> holder.getThumbnail().setImageBitmap(thumbnail));
|
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() {
|
public void onDestroy() {
|
||||||
executorService.shutdown();
|
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
|
@Override
|
||||||
public void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) {
|
public void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) {
|
||||||
if (holder instanceof ListViewHolder) {
|
if (holder instanceof ListViewHolder) {
|
||||||
|
|
|
@ -21,7 +21,7 @@ import com.nextcloud.client.account.User
|
||||||
import com.nextcloud.client.jobs.download.FileDownloadHelper
|
import com.nextcloud.client.jobs.download.FileDownloadHelper
|
||||||
import com.nextcloud.client.jobs.upload.FileUploadHelper
|
import com.nextcloud.client.jobs.upload.FileUploadHelper
|
||||||
import com.nextcloud.client.preferences.AppPreferences
|
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.nextcloud.utils.mdm.MDMConfig
|
||||||
import com.owncloud.android.R
|
import com.owncloud.android.R
|
||||||
import com.owncloud.android.datamodel.FileDataStorageManager
|
import com.owncloud.android.datamodel.FileDataStorageManager
|
||||||
|
@ -250,7 +250,7 @@ class OCFileListDelegate(
|
||||||
if (shouldHideShare) {
|
if (shouldHideShare) {
|
||||||
gridViewHolder.shared.visibility = View.GONE
|
gridViewHolder.shared.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
showShareIcon(gridViewHolder, file)
|
configureSharedIconView(gridViewHolder, file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,9 +307,8 @@ class OCFileListDelegate(
|
||||||
R.color.bg_default
|
R.color.bg_default
|
||||||
}
|
}
|
||||||
|
|
||||||
gridViewHolder.itemLayout.apply {
|
gridViewHolder.itemLayout.run {
|
||||||
outlineProvider = createRoundedOutline(context, cornerRadius)
|
makeRounded(context, cornerRadius)
|
||||||
clipToOutline = true
|
|
||||||
setBackgroundColor(ContextCompat.getColor(context, itemLayoutBackgroundColorId))
|
setBackgroundColor(ContextCompat.getColor(context, itemLayoutBackgroundColorId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -367,34 +366,38 @@ class OCFileListDelegate(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showShareIcon(gridViewHolder: ListViewHolder, file: OCFile) {
|
private fun configureSharedIconView(gridViewHolder: ListViewHolder, file: OCFile) {
|
||||||
val sharedIconView = gridViewHolder.shared
|
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)) {
|
if (!MDMConfig.sharingSupport(context)) {
|
||||||
sharedIconView.visibility = View.GONE
|
return null
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gridViewHolder is OCFileListItemViewHolder || file.unreadCommentsCount == 0) {
|
if (file.isOfflineOperation) return null
|
||||||
sharedIconView.visibility = View.VISIBLE
|
|
||||||
if (file.isSharedWithSharee || file.isSharedWithMe) {
|
if (holder !is OCFileListItemViewHolder && file.unreadCommentsCount != 0) return null
|
||||||
if (showShareAvatar) {
|
|
||||||
sharedIconView.visibility = View.GONE
|
return when {
|
||||||
} else {
|
file.isSharedWithSharee || file.isSharedWithMe -> {
|
||||||
sharedIconView.visibility = View.VISIBLE
|
if (showShareAvatar) null else R.drawable.shared_via_users to R.string.shared_icon_shared
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
sharedIconView.setOnClickListener { ocFileListFragmentInterface.onShareIconClick(file) }
|
file.isSharedViaLink -> R.drawable.shared_via_link to R.string.shared_icon_shared_via_link
|
||||||
} else {
|
else -> R.drawable.ic_unshared to R.string.shared_icon_share
|
||||||
sharedIconView.visibility = View.GONE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -115,7 +115,7 @@ class ReceiveExternalFilesAdapter(
|
||||||
val isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, user)
|
val isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, user)
|
||||||
val isDarkModeActive = syncedFolderProvider.preferences.isDarkModeEnabled
|
val isDarkModeActive = syncedFolderProvider.preferences.isDarkModeEnabled
|
||||||
val overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder)
|
val overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder)
|
||||||
val icon = MimeTypeUtil.getFileIcon(isDarkModeActive, overlayIconId, context, viewThemeUtils)
|
val icon = MimeTypeUtil.getFolderIcon(isDarkModeActive, overlayIconId, context, viewThemeUtils)
|
||||||
thumbnailImageView.setImageDrawable(icon)
|
thumbnailImageView.setImageDrawable(icon)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ import com.owncloud.android.ui.activity.FileDisplayActivity
|
||||||
import com.owncloud.android.utils.DisplayUtils
|
import com.owncloud.android.utils.DisplayUtils
|
||||||
import com.owncloud.android.utils.KeyboardUtils
|
import com.owncloud.android.utils.KeyboardUtils
|
||||||
import com.owncloud.android.utils.theme.ViewThemeUtils
|
import com.owncloud.android.utils.theme.ViewThemeUtils
|
||||||
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -178,9 +179,13 @@ class RenameFileDialogFragment : DialogFragment(), DialogInterface.OnClickListen
|
||||||
|
|
||||||
if (isFileHidden(newFileName)) {
|
if (isFileHidden(newFileName)) {
|
||||||
binding.userInputContainer.error = getText(R.string.hidden_file_name_warning)
|
binding.userInputContainer.error = getText(R.string.hidden_file_name_warning)
|
||||||
|
positiveButton?.isEnabled = true
|
||||||
} else if (errorMessage != null) {
|
} else if (errorMessage != null) {
|
||||||
binding.userInputContainer.error = errorMessage
|
binding.userInputContainer.error = errorMessage
|
||||||
positiveButton?.isEnabled = false
|
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) {
|
} else if (binding.userInputContainer.error != null) {
|
||||||
binding.userInputContainer.error = null
|
binding.userInputContainer.error = null
|
||||||
// Called to remove extra padding
|
// Called to remove extra padding
|
||||||
|
@ -191,6 +196,17 @@ class RenameFileDialogFragment : DialogFragment(), DialogInterface.OnClickListen
|
||||||
|
|
||||||
override fun afterTextChanged(s: Editable) = Unit
|
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 {
|
companion object {
|
||||||
private const val ARG_TARGET_FILE = "TARGET_FILE"
|
private const val ARG_TARGET_FILE = "TARGET_FILE"
|
||||||
private const val ARG_PARENT_FOLDER = "PARENT_FOLDER"
|
private const val ARG_PARENT_FOLDER = "PARENT_FOLDER"
|
||||||
|
|
|
@ -868,7 +868,7 @@ public final class DisplayUtils {
|
||||||
boolean isDarkModeActive = preferences.isDarkModeEnabled();
|
boolean isDarkModeActive = preferences.isDarkModeEnabled();
|
||||||
|
|
||||||
Integer overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder);
|
Integer overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder);
|
||||||
LayerDrawable fileIcon = MimeTypeUtil.getFileIcon(isDarkModeActive, overlayIconId, context, viewThemeUtils);
|
LayerDrawable fileIcon = MimeTypeUtil.getFolderIcon(isDarkModeActive, overlayIconId, context, viewThemeUtils);
|
||||||
thumbnailView.setImageDrawable(fileIcon);
|
thumbnailView.setImageDrawable(fileIcon);
|
||||||
} else {
|
} else {
|
||||||
if (file.getRemoteId() != null && file.isPreviewAvailable()) {
|
if (file.getRemoteId() != null && file.isPreviewAvailable()) {
|
||||||
|
|
|
@ -7,12 +7,12 @@
|
||||||
*/
|
*/
|
||||||
package com.owncloud.android.utils
|
package com.owncloud.android.utils
|
||||||
|
|
||||||
import android.graphics.Rect
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.graphics.drawable.LayerDrawable
|
import android.graphics.drawable.LayerDrawable
|
||||||
import androidx.core.graphics.drawable.DrawableCompat
|
import androidx.core.graphics.drawable.DrawableCompat
|
||||||
|
|
||||||
class DrawableUtil {
|
object DrawableUtil {
|
||||||
|
|
||||||
fun changeColor(source: Drawable, color: Int): Drawable {
|
fun changeColor(source: Drawable, color: Int): Drawable {
|
||||||
val drawable = DrawableCompat.wrap(source)
|
val drawable = DrawableCompat.wrap(source)
|
||||||
|
@ -21,13 +21,27 @@ class DrawableUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addDrawableAsOverlay(backgroundDrawable: Drawable, overlayDrawable: Drawable): LayerDrawable {
|
fun addDrawableAsOverlay(backgroundDrawable: Drawable, overlayDrawable: Drawable): LayerDrawable {
|
||||||
val overlayBounds = Rect()
|
val containerDrawable = LayerDrawable(arrayOf(backgroundDrawable, overlayDrawable))
|
||||||
val overlayIconSize = backgroundDrawable.intrinsicWidth / 2
|
|
||||||
val topMargin = overlayIconSize.div(2)
|
|
||||||
overlayBounds.set(overlayIconSize, overlayIconSize + topMargin, overlayIconSize, overlayIconSize)
|
|
||||||
|
|
||||||
val layerDrawable = LayerDrawable(arrayOf(backgroundDrawable, overlayDrawable))
|
val overlayWidth = overlayDrawable.intrinsicWidth
|
||||||
layerDrawable.setLayerInset(1, overlayBounds.left, overlayBounds.top, overlayBounds.right, overlayBounds.bottom)
|
val overlayHeight = overlayDrawable.intrinsicHeight
|
||||||
return layerDrawable
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
package com.owncloud.android.utils;
|
package com.owncloud.android.utils;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.LayerDrawable;
|
import android.graphics.drawable.LayerDrawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
@ -19,6 +21,7 @@ import android.webkit.MimeTypeMap;
|
||||||
import com.nextcloud.android.common.ui.theme.utils.ColorRole;
|
import com.nextcloud.android.common.ui.theme.utils.ColorRole;
|
||||||
import com.owncloud.android.R;
|
import com.owncloud.android.R;
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
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.lib.resources.files.model.ServerFileInterface;
|
||||||
import com.owncloud.android.utils.theme.ViewThemeUtils;
|
import com.owncloud.android.utils.theme.ViewThemeUtils;
|
||||||
|
|
||||||
|
@ -95,9 +98,12 @@ public final class MimeTypeUtil {
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
int iconId = MimeTypeUtil.getFileTypeIconId(mimetype, filename);
|
int iconId = MimeTypeUtil.getFileTypeIconId(mimetype, filename);
|
||||||
Drawable icon = ContextCompat.getDrawable(context, iconId);
|
Drawable icon = ContextCompat.getDrawable(context, iconId);
|
||||||
|
if (icon == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (R.drawable.file_zip == iconId) {
|
if (R.drawable.file_zip == iconId) {
|
||||||
viewThemeUtils.platform.tintPrimaryDrawable(context, icon);
|
viewThemeUtils.platform.tintDrawable(context, icon, ColorRole.PRIMARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
return icon;
|
return icon;
|
||||||
|
@ -141,6 +147,45 @@ public final class MimeTypeUtil {
|
||||||
return determineIconIdByMimeTypeList(possibleMimeTypes);
|
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) {
|
public static Drawable getDefaultFolderIcon(Context context, ViewThemeUtils viewThemeUtils) {
|
||||||
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.folder);
|
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.folder);
|
||||||
assert(drawable != null);
|
assert(drawable != null);
|
||||||
|
@ -149,7 +194,7 @@ public final class MimeTypeUtil {
|
||||||
return drawable;
|
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);
|
Drawable folderDrawable = getDefaultFolderIcon(context, viewThemeUtils);
|
||||||
assert(folderDrawable != null);
|
assert(folderDrawable != null);
|
||||||
|
|
||||||
|
@ -159,16 +204,14 @@ public final class MimeTypeUtil {
|
||||||
return folderLayerDrawable;
|
return folderLayerDrawable;
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawableUtil drawableUtil = new DrawableUtil();
|
|
||||||
|
|
||||||
Drawable overlayDrawable = ContextCompat.getDrawable(context, overlayIconId);
|
Drawable overlayDrawable = ContextCompat.getDrawable(context, overlayIconId);
|
||||||
assert(overlayDrawable != null);
|
assert(overlayDrawable != null);
|
||||||
|
|
||||||
if (isDarkModeActive) {
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,5 +19,6 @@ enum class AppConfigKeys(val key: String) {
|
||||||
DisableClipboard("disable_clipboard"),
|
DisableClipboard("disable_clipboard"),
|
||||||
DisableMoreExternalSite("disable_more_external_site"),
|
DisableMoreExternalSite("disable_more_external_site"),
|
||||||
DisableIntro("disable_intro"),
|
DisableIntro("disable_intro"),
|
||||||
DisableLog("disable_log")
|
DisableLog("disable_log"),
|
||||||
|
EnforceProtection("enforce_protection")
|
||||||
}
|
}
|
||||||
|
|
16
app/src/main/res/drawable/ic_folder_offline.xml
Normal 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>
|
|
@ -35,6 +35,13 @@
|
||||||
<string name="advanced_settings">Paramètres avancés</string>
|
<string name="advanced_settings">Paramètres avancés</string>
|
||||||
<string name="allow_resharing">Autoriser le repartage</string>
|
<string name="allow_resharing">Autoriser le repartage</string>
|
||||||
<string name="app_config_base_url_title">URL de base</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_config_proxy_port_title">Port Proxy</string>
|
||||||
<string name="app_widget_description">Affiche un widget du tableau de bord</string>
|
<string name="app_widget_description">Affiche un widget du tableau de bord</string>
|
||||||
<string name="appbar_search_in">Recherche dans %s</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_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_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_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_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_top_bar_title">Assistant</string>
|
||||||
<string name="assistant_screen_unknown_task_status_text">Inconnu</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_already_exists">Ce nom de fichier existe déjà</string>
|
||||||
<string name="file_delete">Supprimer</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_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_details_no_content">Impossible de charger les détails</string>
|
||||||
<string name="file_icon">Fichier</string>
|
<string name="file_icon">Fichier</string>
|
||||||
<string name="file_keep">Conserver</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">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_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_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_checking_destination">Vérification de la destination…</string>
|
||||||
<string name="file_migration_cleaning">Nettoyage…</string>
|
<string name="file_migration_cleaning">Nettoyage…</string>
|
||||||
<string name="file_migration_dialog_title">Mise à jour du dossier de stockage des données</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_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_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_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_migrating">Déplacement des données…</string>
|
||||||
<string name="file_migration_ok_finished">Terminé</string>
|
<string name="file_migration_ok_finished">Terminé</string>
|
||||||
<string name="file_migration_override_data_folder">Remplacer</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_headline">Aucune notification</string>
|
||||||
<string name="notifications_no_results_message">Veuillez revenir plus tard.</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_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">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_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>
|
<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_category_sync">Synchroniser</string>
|
||||||
<string name="prefs_daily_backup_summary">Sauvegarde quotidienne de votre agenda et des contacts</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_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_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_active">Le chiffrement de bout en bout est configuré !</string>
|
||||||
<string name="prefs_e2e_mnemonic">Phrase secrète E2E</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="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="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_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_all">Tout sélectionner</string>
|
||||||
<string name="select_media_folder">Paramétrer le dossier média</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>
|
<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_permissions">Autorisations pour le partage</string>
|
||||||
<string name="share_remote_clarification">%1$s (distant)</string>
|
<string name="share_remote_clarification">%1$s (distant)</string>
|
||||||
<string name="share_room_clarification">%1$s (conversation)</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_new_email">Envoyer un nouvel e-mail</string>
|
||||||
<string name="share_send_note">Note au destinataire</string>
|
<string name="share_send_note">Note au destinataire</string>
|
||||||
<string name="share_settings">Paramètres</string>
|
<string name="share_settings">Paramètres</string>
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
<bool name="disable_sharing">false</bool>
|
<bool name="disable_sharing">false</bool>
|
||||||
<bool name="disable_clipboard">false</bool>
|
<bool name="disable_clipboard">false</bool>
|
||||||
<bool name="disable_log">false</bool>
|
<bool name="disable_log">false</bool>
|
||||||
|
<bool name="enforce_protection">false</bool>
|
||||||
|
|
||||||
<!-- Flags to enable/disable some features -->
|
<!-- Flags to enable/disable some features -->
|
||||||
<string name="send_files_to_other_apps">on</string>
|
<string name="send_files_to_other_apps">on</string>
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
<string name="app_config_base_url_title">Base URL</string>
|
<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_host_title">Proxy Hostname</string>
|
||||||
<string name="app_config_proxy_port_title">Proxy Port</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_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_delete_offline_folder">Delete Offline Folder</string>
|
||||||
<string name="offline_operations_worker_notification_conflict_text">Conflicted Folder: %s</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="please_select_a_server">Please select a server…</string>
|
||||||
<string name="notification_icon_description">Unread notifications exist</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="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_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_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_title">Internal two way sync</string>
|
||||||
<string name="two_way_sync_activity_disable_all_button_title">Disable for all folders</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="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>
|
</resources>
|
||||||
|
|
|
@ -61,4 +61,10 @@
|
||||||
android:restrictionType="bool"
|
android:restrictionType="bool"
|
||||||
android:title="@string/app_config_disable_log_title" />
|
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>
|
</restrictions>
|
|
@ -35,7 +35,7 @@
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/prefs_category_details" android:key="details">
|
<PreferenceCategory android:title="@string/prefs_category_details" android:key="details">
|
||||||
<ListPreference
|
<com.owncloud.android.ui.ListPreferenceDialog
|
||||||
android:title="@string/prefs_lock"
|
android:title="@string/prefs_lock"
|
||||||
android:key="lock"
|
android:key="lock"
|
||||||
android:dialogTitle="@string/prefs_lock_title"
|
android:dialogTitle="@string/prefs_lock_title"
|
||||||
|
|
|
@ -1028,6 +1028,11 @@
|
||||||
<sha256 value="e4b122da072402de71c8a20b85289d68e4d4446d7b1bee6dee4fef9d4677130b" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
<sha256 value="e4b122da072402de71c8a20b85289d68e4d4446d7b1bee6dee4fef9d4677130b" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</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">
|
<component group="androidx.compose.animation" name="animation" version="1.7.5">
|
||||||
<artifact name="animation-1.7.5.module">
|
<artifact name="animation-1.7.5.module">
|
||||||
<sha256 value="7c742bb407497fb4c322bd3d4abef83741ea6107a12168f579438303433497ff" origin="Generated by Gradle"/>
|
<sha256 value="7c742bb407497fb4c322bd3d4abef83741ea6107a12168f579438303433497ff" origin="Generated by Gradle"/>
|
||||||
|
|