Merge remote-tracking branch 'origin/master' into dev
|
@ -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'
|
||||
|
|
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;
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
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="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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
|
@ -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"/>
|
||||
|
|