insertOperations = prepareInsertSharesFromRemoteFile(shares);
+ if (!insertOperations.isEmpty()) {
+ applyBatch(insertOperations);
+ }
+ }
+
+ /**
+ * Prepares a list of ContentProviderOperation insert operations based on share information
+ * found in the given iterable of RemoteFile objects.
+ *
+ * Each RemoteFile may have multiple share entries (sharees), and for each one,
+ * a corresponding ContentProviderOperation is created for insertion into the shares table.
+ *
+ * @param remoteFiles An iterable list of RemoteFile objects containing sharee data.
+ * @return A list of ContentProviderOperation objects for batch insertion into the content provider.
+ */
+ private ArrayList prepareInsertSharesFromRemoteFile(Iterable remoteFiles) {
+ final ArrayList contentValueList = new ArrayList<>();
+ for (RemoteFile remoteFile : remoteFiles) {
+ final var contentValues = ShareeEntry.Companion.getContentValues(remoteFile, user.getAccountName());
+ if (contentValues == null) {
+ continue;
+ }
+ contentValueList.addAll(contentValues);
+ }
+
+ ArrayList operations = new ArrayList<>();
+ for (ContentValues contentValues : contentValueList) {
+ operations.add(ContentProviderOperation
+ .newInsert(ProviderTableMeta.CONTENT_URI_SHARE)
+ .withValues(contentValues)
+ .build());
+ }
+
+ return operations;
+ }
+
public void saveSharesDB(List shares) {
ArrayList operations = new ArrayList<>();
@@ -1755,20 +1840,26 @@ public void saveSharesDB(List shares) {
// Add operations to insert shares
operations = prepareInsertShares(shares, operations);
+ if (operations.isEmpty()) {
+ return;
+ }
+
// apply operations in batch
- if (operations.size() > 0) {
- Log_OC.d(TAG, String.format(Locale.ENGLISH, SENDING_TO_FILECONTENTPROVIDER_MSG, operations.size()));
- try {
- if (getContentResolver() != null) {
- getContentResolver().applyBatch(MainApp.getAuthority(), operations);
+ Log_OC.d(TAG, String.format(Locale.ENGLISH, SENDING_TO_FILECONTENTPROVIDER_MSG, operations.size()));
+ applyBatch(operations);
+ }
- } else {
- getContentProviderClient().applyBatch(operations);
- }
+ private void applyBatch(ArrayList operations) {
+ try {
+ if (getContentResolver() != null) {
+ getContentResolver().applyBatch(MainApp.getAuthority(), operations);
- } catch (OperationApplicationException | RemoteException e) {
- Log_OC.e(TAG, EXCEPTION_MSG + e.getMessage(), e);
+ } else {
+ getContentProviderClient().applyBatch(operations);
}
+
+ } catch (OperationApplicationException | RemoteException e) {
+ Log_OC.e(TAG, EXCEPTION_MSG + e.getMessage(), e);
}
}
@@ -1826,8 +1917,7 @@ public void saveSharesInFolder(ArrayList shares, OCFile folder) {
* @param operations List of operations
* @return
*/
- private ArrayList prepareInsertShares(
- Iterable shares, ArrayList operations) {
+ private ArrayList prepareInsertShares(Iterable shares, ArrayList operations) {
ContentValues contentValues;
// prepare operations to insert or update files to save in the given folder
@@ -1883,6 +1973,36 @@ private ArrayList prepareRemoveSharesInFile(
}
+ @IonosCustomization
+ public List getShares() {
+ String selection = ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + " = ?";
+ String[] selectionArgs = new String[]{user.getAccountName()};
+
+ Cursor cursor = null;
+ Uri uri = ProviderTableMeta.CONTENT_URI_SHARE;
+ if (getContentResolver() != null) {
+ cursor = getContentResolver().query(uri, null, selection, selectionArgs, null);
+ } else {
+ try {
+ cursor = getContentProviderClient().query(uri, null, selection, selectionArgs, null);
+ } catch (RemoteException e) {
+ Log_OC.e(TAG, "Could not get list of shares: " + e.getMessage(), e);
+ }
+ }
+
+ ArrayList shares = new ArrayList<>();
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ do {
+ shares.add(createShareInstance(cursor));
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ }
+
+ return shares;
+ }
+
public List getSharesWithForAFile(String filePath, String accountName) {
String selection = ProviderTableMeta.OCSHARES_PATH + AND
+ ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + AND
@@ -2696,6 +2816,18 @@ public List getAllFiles() {
return folderContent;
}
+ @IonosCustomization
+ public List getFavoriteFiles() {
+ List fileEntities = fileDao.getFavoriteFiles(user.getAccountName());
+ List favoriteFiles = new ArrayList<>(fileEntities.size());
+
+ for (FileEntity fileEntity : fileEntities) {
+ favoriteFiles.add(createFileInstance(fileEntity));
+ }
+
+ return favoriteFiles;
+ }
+
private String getString(Cursor cursor, String columnName) {
return cursor.getString(cursor.getColumnIndexOrThrow(columnName));
}
diff --git a/app/src/main/java/com/owncloud/android/datamodel/OCFile.java b/app/src/main/java/com/owncloud/android/datamodel/OCFile.java
index 19f8cc666409..6f528641510c 100644
--- a/app/src/main/java/com/owncloud/android/datamodel/OCFile.java
+++ b/app/src/main/java/com/owncloud/android/datamodel/OCFile.java
@@ -20,6 +20,7 @@
import android.os.Parcelable;
import android.text.TextUtils;
+import com.ionos.annotation.IonosCustomization;
import com.nextcloud.utils.BuildHelper;
import com.owncloud.android.R;
import com.owncloud.android.lib.common.network.WebdavEntry;
@@ -647,9 +648,12 @@ public boolean isGroupFolder() {
return permissions != null && permissions.contains(PERMISSION_GROUPFOLDER);
}
+ @IonosCustomization("Icon for all sharing types")
public Integer getFileOverlayIconId(boolean isAutoUploadFolder) {
if (WebdavEntry.MountType.GROUP == mountType || isGroupFolder()) {
return R.drawable.ic_folder_overlay_account_group;
+ } else if (sharedViaLink && !encrypted && (isSharedWithMe() || sharedWithSharee)) {
+ return R.drawable.ic_folder_all_share_types;
} else if (sharedViaLink && !encrypted) {
return R.drawable.ic_folder_overlay_link;
} else if (isSharedWithMe() || sharedWithSharee) {
diff --git a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java
index 3f74f9fee43a..8f2786647335 100644
--- a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java
+++ b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java
@@ -34,6 +34,8 @@
import android.widget.FrameLayout;
import android.widget.ImageView;
+import com.ionos.annotation.IonosCustomization;
+import com.ionos.utils.IonosBuildHelper;
import com.nextcloud.client.account.User;
import com.nextcloud.client.network.ConnectivityService;
import com.owncloud.android.MainApp;
@@ -609,6 +611,7 @@ protected Bitmap doInBackground(ThumbnailGenerationTaskObject... params) {
return thumbnail;
}
+ @IonosCustomization
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null && mImageViewReference != null) {
final ImageView imageView = mImageViewReference.get();
@@ -624,7 +627,9 @@ protected void onPostExecute(Bitmap bitmap) {
tagId = String.valueOf(((TrashbinFile) mFile).getRemoteId());
}
if (String.valueOf(imageView.getTag()).equals(tagId)) {
- if (gridViewEnabled) {
+ if (IonosBuildHelper.isIonosBuild()) {
+ imageView.setImageBitmap(bitmap);
+ } else if (gridViewEnabled) {
BitmapUtils.setRoundedBitmapForGridMode(bitmap, imageView);
} else {
BitmapUtils.setRoundedBitmap(bitmap, imageView);
@@ -1181,7 +1186,11 @@ private static GalleryImageGenerationTask getGalleryImageGenerationTask(ImageVie
return null;
}
+ @IonosCustomization("Use layout overlay instead of bitmap overlay")
public static Bitmap addVideoOverlay(Bitmap thumbnail, Context context) {
+ if (IonosBuildHelper.isIonosBuild()) {
+ return thumbnail;
+ }
Drawable playButtonDrawable = ResourcesCompat.getDrawable(MainApp.getAppContext().getResources(),
R.drawable.video_white,
diff --git a/app/src/main/java/com/owncloud/android/media/MediaControlView.kt b/app/src/main/java/com/owncloud/android/media/MediaControlView.kt
index 217c1c153501..28da66d75632 100644
--- a/app/src/main/java/com/owncloud/android/media/MediaControlView.kt
+++ b/app/src/main/java/com/owncloud/android/media/MediaControlView.kt
@@ -22,11 +22,14 @@ import android.view.LayoutInflater
import android.view.View
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.SeekBar
import android.widget.SeekBar.OnSeekBarChangeListener
import androidx.core.content.ContextCompat
import androidx.media3.common.Player
+import com.google.android.material.button.MaterialButton
+import com.ionos.annotation.IonosCustomization
import com.owncloud.android.MainApp
import com.owncloud.android.R
import com.owncloud.android.databinding.MediaControlBinding
@@ -73,6 +76,7 @@ class MediaControlView(context: Context, attrs: AttributeSet?) :
}
@Suppress("MagicNumber")
+ @IonosCustomization("Removed theming")
private fun initControllerView() {
binding.playBtn.requestFocus()
@@ -81,17 +85,10 @@ class MediaControlView(context: Context, attrs: AttributeSet?) :
binding.rewindBtn.setOnClickListener(this)
binding.progressBar.run {
- viewThemeUtils.platform.themeHorizontalSeekBar(this)
setMax(1000)
}
binding.progressBar.setOnSeekBarChangeListener(this)
-
- viewThemeUtils.material.run {
- colorMaterialButtonPrimaryTonal(binding.rewindBtn)
- colorMaterialButtonPrimaryTonal(binding.playBtn)
- colorMaterialButtonPrimaryTonal(binding.forwardBtn)
- }
}
/**
@@ -222,18 +219,25 @@ class MediaControlView(context: Context, attrs: AttributeSet?) :
}
}
+ @IonosCustomization("ImageButton support")
fun updatePausePlay() {
- binding.playBtn.icon = ContextCompat.getDrawable(
- context,
- // use isPlaying instead of playWhenReady
- // it represents only the play/pause state
- // which is needed to show play/pause icons
+ val iconResource =
if (playerControl?.isPlaying == true) {
R.drawable.ic_pause
} else {
R.drawable.ic_play
}
- )
+ val playBtn: View = binding.playBtn
+
+ if (playBtn is MaterialButton){
+ playBtn.icon = ContextCompat.getDrawable(
+ context,
+ iconResource
+ )
+ } else if(playBtn is ImageButton){
+ playBtn.setImageResource(iconResource)
+ }
+
binding.forwardBtn.visibility = if (playerControl?.isCommandAvailable(Player.COMMAND_SEEK_FORWARD) == true) {
VISIBLE
} else {
@@ -269,6 +273,7 @@ class MediaControlView(context: Context, attrs: AttributeSet?) :
}
@Suppress("MagicNumber")
+ @IonosCustomization("changed forward to 5 sec")
override fun onClick(v: View) {
playerControl?.let { playerControl ->
val playing = playerControl.playWhenReady
@@ -280,7 +285,8 @@ class MediaControlView(context: Context, attrs: AttributeSet?) :
}
R.id.rewindBtn -> {
- playerControl.seekBack()
+ val pos = playerControl.currentPosition
+ playerControl.seekTo(pos - 5000)
if (!playing) {
playerControl.pause() // necessary in some 2.3.x devices
}
@@ -288,7 +294,8 @@ class MediaControlView(context: Context, attrs: AttributeSet?) :
}
R.id.forwardBtn -> {
- playerControl.seekForward()
+ val pos = playerControl.currentPosition
+ playerControl.seekTo(pos + 5000)
if (!playing) {
playerControl.pause() // necessary in some 2.3.x devices
}
diff --git a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java
index 916ec1331f01..6ab6a9a89d68 100644
--- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java
+++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java
@@ -233,6 +233,11 @@ protected RemoteOperationResult run(OwnCloudClient client) {
mConflictsFound = 0;
mForgottenLocalFiles.clear();
+ if (mLocalFolder == null) {
+ Log_OC.e(TAG, "Local folder is null, cannot run refresh folder operation");
+ return new RemoteOperationResult<>(ResultCode.FILE_NOT_FOUND);
+ }
+
if (OCFile.ROOT_PATH.equals(mLocalFolder.getRemotePath()) && !mSyncFullAccount && !mOnlyFileMetadata) {
updateOCVersion(client);
updateUserProfile();
@@ -255,21 +260,32 @@ protected RemoteOperationResult run(OwnCloudClient client) {
mLocalFolder.setEtag("");
}
- mLocalFolder.setLastSyncDateForData(System.currentTimeMillis());
- fileDataStorageManager.saveFile(mLocalFolder);
+ if (mLocalFolder != null) {
+ mLocalFolder.setLastSyncDateForData(System.currentTimeMillis());
+ fileDataStorageManager.saveFile(mLocalFolder);
+ } else {
+ Log_OC.e(TAG, "Local folder is null, cannot set last sync date nor save file");
+ result = new RemoteOperationResult<>(ResultCode.FILE_NOT_FOUND);
+ }
}
- checkFolderConflictData(result);
-
- if (!mSyncFullAccount && mRemoteFolderChanged) {
+ if (!mSyncFullAccount && mRemoteFolderChanged && mLocalFolder != null) {
sendLocalBroadcast(EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result);
}
- if (result.isSuccess() && !mSyncFullAccount && !mOnlyFileMetadata) {
- refreshSharesForFolder(client); // share result is ignored
+ if (result.isSuccess() && result.getData() != null && !mSyncFullAccount && !mOnlyFileMetadata) {
+ final var remoteObject = result.getData();
+ final ArrayList remoteFiles = new ArrayList<>();
+ for (Object object: remoteObject) {
+ if (object instanceof RemoteFile remoteFile) {
+ remoteFiles.add(remoteFile);
+ }
+ }
+
+ fileDataStorageManager.saveSharesFromRemoteFile(remoteFiles);
}
- if (!mSyncFullAccount) {
+ if (!mSyncFullAccount && mLocalFolder != null) {
sendLocalBroadcast(EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result);
}
@@ -806,18 +822,6 @@ private void startContentSynchronizations(List filesTo
}
}
- /**
- * Syncs the Share resources for the files contained in the folder refreshed (children, not deeper descendants).
- *
- * @param client Handler of a session with an OC server.
- * @return The result of the remote operation retrieving the Share resources in the folder refreshed by the
- * operation.
- */
- private RemoteOperationResult refreshSharesForFolder(OwnCloudClient client) {
- GetSharesForFileOperation operation = new GetSharesForFileOperation(mLocalFolder.getRemotePath(), true, true, fileDataStorageManager);
- return operation.execute(client);
- }
-
/**
* Sends a message to any application component interested in the progress of the synchronization.
*
diff --git a/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java b/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java
index ed8bc8b4fcbf..eb795c6d15f6 100644
--- a/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java
+++ b/app/src/main/java/com/owncloud/android/providers/FileContentProvider.java
@@ -26,6 +26,7 @@
import android.os.Binder;
import android.text.TextUtils;
+import com.ionos.annotation.IonosCustomization;
import com.nextcloud.client.core.Clock;
import com.nextcloud.client.database.NextcloudDatabase;
import com.owncloud.android.R;
@@ -35,7 +36,9 @@
import com.owncloud.android.utils.MimeType;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Locale;
+import java.util.Set;
import javax.inject.Inject;
@@ -80,6 +83,10 @@ public class FileContentProvider extends ContentProvider {
private static final String[] PROJECTION_FILE_PATH_AND_OWNER = new String[]{
ProviderTableMeta._ID, ProviderTableMeta.FILE_PATH, ProviderTableMeta.FILE_ACCOUNT_OWNER
};
+ @IonosCustomization
+ private static final String[] PROJECTION_PARENT_ID = new String[]{
+ ProviderTableMeta._ID, ProviderTableMeta.FILE_PARENT
+ };
@Inject protected Clock clock;
@@ -89,21 +96,28 @@ public class FileContentProvider extends ContentProvider {
private UriMatcher mUriMatcher;
@Override
+ @IonosCustomization("Notify parent directories about deletion")
public int delete(@NonNull Uri uri, String where, String[] whereArgs) {
if (isCallerNotAllowed(uri)) {
return -1;
}
int count;
+ Set parentIds;
SupportSQLiteDatabase db = mDbHelper.getWritableDatabase();
db.beginTransaction();
try {
+ parentIds = queryParentIds(db, uri, where, whereArgs);
count = delete(db, uri, where, whereArgs);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
mContext.getContentResolver().notifyChange(uri, null);
+ for (long parentId : parentIds) {
+ Uri parentUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_DIR, parentId);
+ mContext.getContentResolver().notifyChange(parentUri, null);
+ }
return count;
}
@@ -229,6 +243,7 @@ public String getType(@NonNull Uri uri) {
}
@Override
+ @IonosCustomization("Notify parent directory when inserting a file")
public Uri insert(@NonNull Uri uri, ContentValues values) {
if (isCallerNotAllowed(uri)) {
return null;
@@ -244,6 +259,11 @@ public Uri insert(@NonNull Uri uri, ContentValues values) {
db.endTransaction();
}
mContext.getContentResolver().notifyChange(newUri, null);
+ Long parentId = values.getAsLong(ProviderTableMeta.FILE_PARENT);
+ if (parentId != null) {
+ Uri parentUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_DIR, parentId);
+ mContext.getContentResolver().notifyChange(parentUri, null);
+ }
return newUri;
}
@@ -372,7 +392,13 @@ private Uri insert(SupportSQLiteDatabase db, Uri uri, ContentValues values) {
private void updateFilesTableAccordingToShareInsertion(SupportSQLiteDatabase db, ContentValues newShare) {
ContentValues fileValues = new ContentValues();
- ShareType newShareType = ShareType.fromValue(newShare.getAsInteger(ProviderTableMeta.OCSHARES_SHARE_TYPE));
+ Integer shareTypeValue = newShare.getAsInteger(ProviderTableMeta.OCSHARES_SHARE_TYPE);
+ if (shareTypeValue == null) {
+ Log_OC.w(TAG, "Share type is null. Skipping file update.");
+ return;
+ }
+
+ ShareType newShareType = ShareType.fromValue(shareTypeValue);
switch (newShareType) {
case PUBLIC_LINK:
@@ -586,21 +612,28 @@ private Cursor query(SupportSQLiteDatabase db,
}
@Override
+ @IonosCustomization("Notify parent directories about update")
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
if (isCallerNotAllowed(uri)) {
return -1;
}
int count;
+ Set parentIds;
SupportSQLiteDatabase db = mDbHelper.getWritableDatabase();
db.beginTransaction();
try {
+ parentIds = queryParentIds(db, uri, selection, selectionArgs);
count = update(db, uri, values, selection, selectionArgs);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
mContext.getContentResolver().notifyChange(uri, null);
+ for (long parentId : parentIds) {
+ Uri parentUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_DIR, parentId);
+ mContext.getContentResolver().notifyChange(parentUri, null);
+ }
return count;
}
@@ -632,6 +665,28 @@ private int update(SupportSQLiteDatabase db, Uri uri, ContentValues values, Stri
}
}
+ @IonosCustomization
+ private Set queryParentIds(SupportSQLiteDatabase db, Uri uri, String where, String... whereArgs) {
+ Set result = new HashSet<>();
+ int uriMatch = mUriMatcher.match(uri);
+ if (uriMatch == ROOT_DIRECTORY || mUriMatcher.match(uri) == DIRECTORY || mUriMatcher.match(uri) == SINGLE_FILE) {
+ try (Cursor cursor = query(db, uri, PROJECTION_PARENT_ID, where, whereArgs, null)) {
+ if (cursor.moveToFirst()) {
+ do {
+ int parentIdColumnIndex = cursor.getColumnIndex(ProviderTableMeta.FILE_PARENT);
+ if (parentIdColumnIndex != -1 && !cursor.isNull(parentIdColumnIndex)) {
+ long parentId = cursor.getLong(parentIdColumnIndex);
+ result.add(parentId);
+ }
+ } while (cursor.moveToNext());
+ }
+ } catch (Exception e) {
+ Log_OC.d(TAG, "Error querying parent IDs", e);
+ }
+ }
+ return result;
+ }
+
@NonNull
@Override
public ContentProviderResult[] applyBatch(@NonNull ArrayList operations)
diff --git a/app/src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java b/app/src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java
index fc4c05dd4b77..2466858da3ea 100644
--- a/app/src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java
+++ b/app/src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java
@@ -27,6 +27,8 @@
import android.util.Log;
import android.widget.Toast;
+import com.ionos.annotation.IonosCustomization;
+import com.ionos.utils.IonosBuildHelper;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.owncloud.android.R;
@@ -376,6 +378,7 @@ public int update(@NonNull Uri uri, ContentValues values, String selection, Stri
@Nullable
@Override
@SuppressFBWarnings("IOI_USE_OF_FILE_STREAM_CONSTRUCTORS") // TODO remove with API26
+ @IonosCustomization
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(getContext());
@@ -396,7 +399,9 @@ public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) thr
Bitmap avatarBitmap = ThumbnailsCacheManager.getBitmapFromDiskCache(avatarKey);
- if (avatarBitmap == null) {
+ if (avatarBitmap == null && IonosBuildHelper.isIonosBuild()) {
+ avatarBitmap = BitmapUtils.drawableToBitmap(getContext().getDrawable(R.drawable.account_circle_white));
+ } else if (avatarBitmap == null) {
float avatarRadius = getContext().getResources().getDimension(R.dimen.list_item_avatar_icon_radius);
avatarBitmap = BitmapUtils.drawableToBitmap(TextDrawable.createNamedAvatar(displayName, avatarRadius));
}
diff --git a/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.java b/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.java
deleted file mode 100644
index 91dbd0f3f35b..000000000000
--- a/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Nextcloud Android client application
- *
- * @author Andy Scherzinger
- * @author Stefan Niedermann
- * Copyright (C) 2021 Andy Scherzinger
- * Copyright (C) 2021 Stefan Niedermann
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
- */
-
-package com.owncloud.android.ui;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.request.target.BitmapImageViewTarget;
-import com.nextcloud.client.account.User;
-import com.owncloud.android.R;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.lib.resources.shares.ShareeUser;
-import com.owncloud.android.utils.DisplayUtils;
-import com.owncloud.android.utils.theme.ViewThemeUtils;
-
-import java.util.List;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Px;
-import androidx.core.content.ContextCompat;
-import androidx.core.content.res.ResourcesCompat;
-import androidx.core.graphics.drawable.DrawableCompat;
-import androidx.core.graphics.drawable.RoundedBitmapDrawable;
-import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
-
-public class AvatarGroupLayout extends RelativeLayout implements DisplayUtils.AvatarGenerationListener {
- private static final String TAG = AvatarGroupLayout.class.getSimpleName();
-
- private final static int MAX_AVATAR_COUNT = 3;
-
- private final Drawable borderDrawable;
- @Px private final int avatarSize;
- @Px private final int avatarBorderSize;
- @Px private final int overlapPx;
-
- public AvatarGroupLayout(Context context) {
- this(context, null);
- }
-
- public AvatarGroupLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public AvatarGroupLayout(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public AvatarGroupLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- avatarBorderSize = DisplayUtils.convertDpToPixel(2, context);
- avatarSize = DisplayUtils.convertDpToPixel(40, context);
- overlapPx = DisplayUtils.convertDpToPixel(24, context);
- borderDrawable = ContextCompat.getDrawable(context, R.drawable.round_bgnd);
- assert borderDrawable != null;
- DrawableCompat.setTint(borderDrawable, ContextCompat.getColor(context, R.color.bg_default));
- }
-
- public void setAvatars(@NonNull User user,
- @NonNull List sharees,
- final ViewThemeUtils viewThemeUtils) {
- @NonNull Context context = getContext();
- removeAllViews();
- RelativeLayout.LayoutParams avatarLayoutParams;
- int avatarCount;
- int shareeSize = Math.min(sharees.size(), MAX_AVATAR_COUNT);
-
- Resources resources = context.getResources();
- float avatarRadius = resources.getDimension(R.dimen.list_item_avatar_icon_radius);
- ShareeUser sharee;
-
- for (avatarCount = 0; avatarCount < shareeSize; avatarCount++) {
- avatarLayoutParams = new RelativeLayout.LayoutParams(avatarSize, avatarSize);
- avatarLayoutParams.setMargins(0, 0, avatarCount * overlapPx, 0);
- avatarLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
-
- final ImageView avatar = new ImageView(context);
- avatar.setLayoutParams(avatarLayoutParams);
- avatar.setPadding(avatarBorderSize, avatarBorderSize, avatarBorderSize, avatarBorderSize);
-
- avatar.setBackground(borderDrawable);
- addView(avatar);
- avatar.requestLayout();
-
- if (avatarCount == 0 && sharees.size() > MAX_AVATAR_COUNT) {
- avatar.setImageResource(R.drawable.ic_people);
- viewThemeUtils.platform.tintTextDrawable(context, avatar.getDrawable());
- } else {
- sharee = sharees.get(avatarCount);
- switch (sharee.getShareType()) {
- case GROUP:
- case EMAIL:
- case ROOM:
- case CIRCLE:
- viewThemeUtils.files.createAvatar(sharee.getShareType(), avatar, context);
- break;
- case FEDERATED:
- showFederatedShareAvatar(context,
- sharee.getUserId(),
- avatarRadius,
- resources,
- avatar,
- viewThemeUtils);
- break;
- default:
- avatar.setTag(sharee);
- DisplayUtils.setAvatar(user,
- sharee.getUserId(),
- sharee.getDisplayName(),
- this,
- avatarRadius,
- resources,
- avatar,
- context);
- break;
- }
- }
- }
-
- // Recalculate container size based on avatar count
- int size = overlapPx * (avatarCount - 1) + avatarSize;
- ViewGroup.LayoutParams rememberParam = getLayoutParams();
- rememberParam.width = size;
- setLayoutParams(rememberParam);
- }
-
- private void showFederatedShareAvatar(Context context,
- String user,
- float avatarRadius,
- Resources resources,
- ImageView avatar,
- ViewThemeUtils viewThemeUtils) {
- // maybe federated share
- String[] split = user.split("@");
- String userId = split[0];
- String server = split[1];
-
- String url = "https://" + server + "/index.php/avatar/" + userId + "/" +
- resources.getInteger(R.integer.file_avatar_px);
-
- Drawable placeholder;
- try {
- placeholder = TextDrawable.createAvatarByUserId(userId, avatarRadius);
- } catch (Exception e) {
- Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e);
- placeholder = viewThemeUtils.platform.colorDrawable(ResourcesCompat.getDrawable(resources,
- R.drawable.account_circle_white,
- null),
- ContextCompat.getColor(context, R.color.black));
- }
-
- avatar.setTag(null);
- Glide.with(context).load(url)
- .asBitmap()
- .placeholder(placeholder)
- .error(placeholder)
- .into(new BitmapImageViewTarget(avatar) {
- @Override
- protected void setResource(Bitmap resource) {
- RoundedBitmapDrawable circularBitmapDrawable = RoundedBitmapDrawableFactory.create(resources,
- resource);
- circularBitmapDrawable.setCircular(true);
- avatar.setImageDrawable(circularBitmapDrawable);
- }
- });
- }
-
- @Override
- public void avatarGenerated(Drawable avatarDrawable, Object callContext) {
- ((ImageView) callContext).setImageDrawable(avatarDrawable);
- }
-
- @Override
- public boolean shouldCallGeneratedCallback(String tag, Object callContext) {
- return ((ImageView) callContext).getTag().equals(tag);
- }
-}
diff --git a/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt b/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt
new file mode 100644
index 000000000000..82bf5ae6bbb9
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt
@@ -0,0 +1,193 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Andy Scherzinger
+ * @author Stefan Niedermann
+ * Copyright (C) 2021 Andy Scherzinger
+ * Copyright (C) 2021 Stefan Niedermann
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
+ */
+package com.owncloud.android.ui
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.widget.ImageView
+import android.widget.RelativeLayout
+import androidx.annotation.Px
+import androidx.core.content.ContextCompat
+import androidx.core.content.res.ResourcesCompat
+import androidx.core.graphics.drawable.DrawableCompat
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
+import com.bumptech.glide.Glide
+import com.bumptech.glide.request.target.BitmapImageViewTarget
+import com.nextcloud.android.common.ui.theme.utils.ColorRole
+import com.nextcloud.client.account.User
+import com.owncloud.android.R
+import com.owncloud.android.lib.common.utils.Log_OC
+import com.owncloud.android.lib.resources.shares.ShareType
+import com.owncloud.android.lib.resources.shares.ShareeUser
+import com.owncloud.android.utils.DisplayUtils
+import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener
+import com.owncloud.android.utils.theme.ViewThemeUtils
+import kotlin.math.min
+
+@Suppress("MagicNumber")
+class AvatarGroupLayout @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : RelativeLayout(context, attrs, defStyleAttr, defStyleRes),
+ AvatarGenerationListener {
+ private val borderDrawable = ContextCompat.getDrawable(context, R.drawable.round_bgnd)
+
+ @Px
+ private val avatarSize: Int = DisplayUtils.convertDpToPixel(40f, context)
+
+ @Px
+ private val avatarBorderSize: Int = DisplayUtils.convertDpToPixel(2f, context)
+
+ @Px
+ private val overlapPx: Int = DisplayUtils.convertDpToPixel(24f, context)
+
+ init {
+ checkNotNull(borderDrawable)
+ DrawableCompat.setTint(borderDrawable, ContextCompat.getColor(context, R.color.bg_default))
+ }
+
+ @Suppress("LongMethod", "TooGenericExceptionCaught")
+ fun setAvatars(user: User, sharees: MutableList, viewThemeUtils: ViewThemeUtils) {
+ val context = getContext()
+ removeAllViews()
+ var avatarLayoutParams: LayoutParams?
+ val shareeSize = min(sharees.size, MAX_AVATAR_COUNT)
+ val resources = context.resources
+ val avatarRadius = resources.getDimension(R.dimen.list_item_avatar_icon_radius)
+ var sharee: ShareeUser
+
+ var avatarCount = 0
+ while (avatarCount < shareeSize) {
+ avatarLayoutParams = LayoutParams(avatarSize, avatarSize).apply {
+ setMargins(0, 0, avatarCount * overlapPx, 0)
+ addRule(ALIGN_PARENT_RIGHT)
+ }
+
+ val avatar = ImageView(context).apply {
+ layoutParams = avatarLayoutParams
+ setPadding(avatarBorderSize, avatarBorderSize, avatarBorderSize, avatarBorderSize)
+ background = borderDrawable
+ }
+
+ addView(avatar)
+ avatar.requestLayout()
+
+ if (avatarCount == 0 && sharees.size > MAX_AVATAR_COUNT) {
+ avatar.setImageResource(R.drawable.ic_people)
+ viewThemeUtils.platform.tintDrawable(context, avatar.drawable, ColorRole.ON_SURFACE)
+ } else {
+ sharee = sharees[avatarCount]
+ when (sharee.shareType) {
+ ShareType.GROUP, ShareType.EMAIL, ShareType.ROOM, ShareType.CIRCLE ->
+ viewThemeUtils.files.createAvatar(
+ sharee.shareType,
+ avatar,
+ context
+ )
+
+ ShareType.FEDERATED -> showFederatedShareAvatar(
+ context,
+ sharee.userId!!,
+ avatarRadius,
+ resources,
+ avatar,
+ viewThemeUtils
+ )
+
+ else -> {
+ avatar.tag = sharee
+ DisplayUtils.setAvatar(
+ user,
+ sharee.userId!!,
+ sharee.displayName,
+ this,
+ avatarRadius,
+ resources,
+ avatar,
+ context
+ )
+ }
+ }
+ }
+ avatarCount++
+ }
+
+ // Recalculate container size based on avatar count
+ val size = overlapPx * (avatarCount - 1) + avatarSize
+ val rememberParam = layoutParams
+ rememberParam.width = size
+ layoutParams = rememberParam
+ }
+
+ @Suppress("TooGenericExceptionCaught")
+ private fun showFederatedShareAvatar(
+ context: Context,
+ user: String,
+ avatarRadius: Float,
+ resources: Resources,
+ avatar: ImageView,
+ viewThemeUtils: ViewThemeUtils
+ ) {
+ // maybe federated share
+ val split = user.split("@".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+ val userId: String? = split[0]
+ val server = split[1]
+
+ val url = "https://" + server + "/index.php/avatar/" + userId + "/" +
+ resources.getInteger(R.integer.file_avatar_px)
+ var placeholder: Drawable?
+ try {
+ placeholder = TextDrawable.createAvatarByUserId(userId, avatarRadius)
+ } catch (e: Exception) {
+ Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e)
+ placeholder = viewThemeUtils.platform.colorDrawable(
+ ResourcesCompat.getDrawable(
+ resources,
+ R.drawable.account_circle_white,
+ null
+ )!!,
+ ContextCompat.getColor(context, R.color.black)
+ )
+ }
+
+ avatar.tag = null
+ Glide.with(context).load(url)
+ .asBitmap()
+ .placeholder(placeholder)
+ .error(placeholder)
+ .into(object : BitmapImageViewTarget(avatar) {
+ override fun setResource(resource: Bitmap?) {
+ resource?.let {
+ val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, it)
+ circularBitmapDrawable.isCircular = true
+ avatar.setImageDrawable(circularBitmapDrawable)
+ }
+ }
+ })
+ }
+
+ override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any) {
+ (callContext as ImageView).setImageDrawable(avatarDrawable)
+ }
+
+ override fun shouldCallGeneratedCallback(tag: String?, callContext: Any): Boolean =
+ (callContext as ImageView).tag == tag
+
+ companion object {
+ private val TAG: String = AvatarGroupLayout::class.java.simpleName
+ private const val MAX_AVATAR_COUNT = 3
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/RoundedCornersConstraintLayout.kt b/app/src/main/java/com/owncloud/android/ui/RoundedCornersConstraintLayout.kt
new file mode 100644
index 000000000000..6574db7cdfed
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/RoundedCornersConstraintLayout.kt
@@ -0,0 +1,25 @@
+/*
+ * IONOS HiDrive Next - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 STRATO GmbH.
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+package com.owncloud.android.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.owncloud.android.R
+
+class RoundedCornersConstraintLayout @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+) : ConstraintLayout(context, attrs, defStyleAttr) {
+
+ init {
+ setBackgroundResource(R.drawable.rounded_rect_4dp)
+ clipToOutline = true
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/SquareFrameLayout.kt b/app/src/main/java/com/owncloud/android/ui/SquareFrameLayout.kt
new file mode 100644
index 000000000000..4f12f95adcb0
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/SquareFrameLayout.kt
@@ -0,0 +1,23 @@
+/*
+ * IONOS HiDrive Next - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 STRATO GmbH.
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+package com.owncloud.android.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.FrameLayout
+
+class SquareFrameLayout @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+) : FrameLayout(context, attrs, defStyleAttr) {
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, widthMeasureSpec)
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/AnimatedDrawerListener.java b/app/src/main/java/com/owncloud/android/ui/activity/AnimatedDrawerListener.java
new file mode 100644
index 000000000000..27ee8cb0f8e3
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/activity/AnimatedDrawerListener.java
@@ -0,0 +1,80 @@
+/*
+ * IONOS HiDrive Next - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 STRATO GmbH..
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+package com.owncloud.android.ui.activity;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.view.View;
+
+import com.owncloud.android.R;
+import com.owncloud.android.utils.theme.ViewThemeUtils;
+
+import androidx.annotation.StringRes;
+import androidx.appcompat.app.ActionBarDrawerToggle;
+import androidx.drawerlayout.widget.DrawerLayout;
+
+class AnimatedDrawerListener extends ActionBarDrawerToggle {
+ private static final float CHANGE_GAIN = 0.1f;
+
+ private final Activity activity;
+ private final ViewThemeUtils viewThemeUtils;
+ private final ValueAnimator valueAnimator;
+
+ AnimatedDrawerListener(Activity activity,
+ DrawerLayout drawerLayout,
+ @StringRes int openDrawerContentDescRes,
+ @StringRes int closeDrawerContentDescRes,
+ ViewThemeUtils viewThemeUtils
+ ) {
+ super(activity, drawerLayout, openDrawerContentDescRes, closeDrawerContentDescRes);
+
+ this.activity = activity;
+ this.viewThemeUtils = viewThemeUtils;
+ this.valueAnimator = createValueAnimator();
+ }
+
+ private ValueAnimator createValueAnimator() {
+ int colorFrom = this.activity.getResources().getColor(R.color.bg_default);
+ int colorTo = getOpenedDrawerColor();
+ return ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
+ }
+
+ @Override
+ public void onDrawerSlide(View drawerView, float slideOffset) {
+ super.onDrawerSlide(drawerView, slideOffset);
+
+ if (shouldUpdateSystemBarColor(slideOffset)) {
+ this.valueAnimator.setCurrentFraction(slideOffset);
+ this.viewThemeUtils.platform.themeStatusBar(this.activity, (int) this.valueAnimator.getAnimatedValue());
+ }
+ }
+
+ @Override
+ public void onDrawerOpened(View drawerView) {
+ super.onDrawerOpened(drawerView);
+
+ this.viewThemeUtils.platform.themeStatusBar(this.activity, getOpenedDrawerColor());
+ }
+
+ @Override
+ public void onDrawerClosed(View drawerView) {
+ super.onDrawerClosed(drawerView);
+
+ this.viewThemeUtils.platform.themeStatusBar(this.activity);
+ }
+
+ private boolean shouldUpdateSystemBarColor(float slideOffset) {
+ float delta = Math.abs(slideOffset - this.valueAnimator.getAnimatedFraction());
+ return delta > CHANGE_GAIN;
+ }
+
+ private int getOpenedDrawerColor() {
+ return this.activity.getResources().getColor(R.color.drawer_header_background);
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java
index af85b9ebeb16..a503ec657bd1 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java
@@ -13,6 +13,7 @@
import android.os.Build;
import android.os.Bundle;
+import com.ionos.annotation.IonosCustomization;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.di.Injectable;
@@ -20,11 +21,14 @@
import com.nextcloud.client.mixins.SessionMixin;
import com.nextcloud.client.preferences.AppPreferences;
import com.nextcloud.client.preferences.DarkMode;
+import com.nextcloud.repository.ClientRepository;
+import com.nextcloud.repository.RemoteClientRepository;
import com.nextcloud.utils.extensions.WindowExtensionsKt;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.status.OCCapability;
+import com.owncloud.android.ui.fragment.filesRepository.FilesRepository;
import java.util.Optional;
@@ -67,11 +71,14 @@ public UserAccountManager getUserAccountManager() {
return accountManager;
}
+ private ClientRepository clientRepository;
+
@Override
+ @IonosCustomization("Window insets handling")
protected void onCreate(@Nullable Bundle savedInstanceState) {
boolean isApiLevel35OrHigher = (Build.VERSION.SDK_INT >= 35);
- if (isApiLevel35OrHigher) {
+ if (isApiLevel35OrHigher && isDefaultWindowInsetsHandlingEnabled()) {
enableEdgeToEdge();
WindowExtensionsKt.addSystemBarPaddings(getWindow());
}
@@ -83,6 +90,13 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
if (enableAccountHandling) {
mixinRegistry.onCreate(savedInstanceState);
}
+
+ clientRepository = new RemoteClientRepository(accountManager.getUser(), this, this);
+ }
+
+ @IonosCustomization("Window insets handling")
+ protected boolean isDefaultWindowInsetsHandlingEnabled() {
+ return true;
}
private void enableEdgeToEdge() {
@@ -195,4 +209,9 @@ public Optional getUser() {
public FileDataStorageManager getStorageManager() {
return fileDataStorageManager;
}
+
+ public ClientRepository getClientRepository() {
+ return clientRepository;
+ }
+
}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
index 3e02c21624a7..034d7a149bdf 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
@@ -50,11 +50,12 @@
import com.google.android.material.button.MaterialButton;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.progressindicator.LinearProgressIndicator;
+import com.ionos.annotation.IonosCustomization;
+import com.ionos.authorization_method.AuthorizationMethodActivity;
import com.nextcloud.client.account.User;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.files.DeepLinkConstants;
import com.nextcloud.client.network.ClientFactory;
-import com.nextcloud.client.onboarding.FirstRunActivity;
import com.nextcloud.client.preferences.AppPreferences;
import com.nextcloud.common.NextcloudClient;
import com.nextcloud.ui.ChooseAccountDialogFragment;
@@ -120,9 +121,9 @@
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBarDrawerToggle;
+import androidx.appcompat.content.res.AppCompatResources;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
-import androidx.core.content.res.ResourcesCompat;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
@@ -287,8 +288,9 @@ private void handleBottomNavigationViewClicks() {
/**
* initializes and sets up the drawer toggle.
*/
+ @IonosCustomization
private void setupDrawerToggle() {
- mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.string.drawer_open, R.string.drawer_close) {
+ mDrawerToggle = new AnimatedDrawerListener(this, mDrawerLayout, R.string.drawer_open, R.string.drawer_close, viewThemeUtils) {
private boolean isMenuItemChecked = false;
@Override
@@ -331,13 +333,9 @@ public void onDrawerOpened(View drawerView) {
mDrawerLayout.addDrawerListener(mDrawerToggle);
mDrawerToggle.setDrawerIndicatorEnabled(true);
mDrawerToggle.setDrawerSlideAnimationEnabled(true);
- Drawable backArrow = ResourcesCompat.getDrawable(getResources(),
- R.drawable.ic_arrow_back,
- null);
-
- if (backArrow != null) {
- viewThemeUtils.platform.tintToolbarArrowDrawable(this, mDrawerToggle, backArrow);
- }
+ Drawable drawerIndicator = AppCompatResources.getDrawable(this, R.drawable.ic_menu);
+ mDrawerToggle.setDrawerArrowDrawable(new SingleStateDrawerArrowDrawable(this, drawerIndicator));
+ mDrawerToggle.setHomeAsUpIndicator(R.drawable.ic_arrow_back);
}
/**
@@ -547,6 +545,7 @@ private void filterDrawerMenu(final Menu menu, @NonNull final User user) {
DrawerMenuUtil.removeMenuItem(menu, R.id.nav_community, !getResources().getBoolean(R.bool.participate_enabled));
DrawerMenuUtil.removeMenuItem(menu, R.id.nav_shared, !getResources().getBoolean(R.bool.shared_enabled));
DrawerMenuUtil.removeMenuItem(menu, R.id.nav_logout, !getResources().getBoolean(R.bool.show_drawer_logout));
+ DrawerMenuUtil.removePersonalFiles(menu);
}
@Subscribe(threadMode = ThreadMode.MAIN)
@@ -659,10 +658,10 @@ public void openManageAccounts() {
startActivityForResult(manageAccountsIntent, ACTION_MANAGE_ACCOUNTS);
}
+ @IonosCustomization
public void openAddAccount() {
if (MDMConfig.INSTANCE.showIntro(this)) {
- Intent firstRunIntent = new Intent(getApplicationContext(), FirstRunActivity.class);
- firstRunIntent.putExtra(FirstRunActivity.EXTRA_ALLOW_CLOSE, true);
+ Intent firstRunIntent = AuthorizationMethodActivity.createInstance(getApplicationContext());
startActivity(firstRunIntent);
} else {
startAccountCreation();
@@ -840,6 +839,7 @@ private void showQuota(boolean showQuota) {
* @param relative the percentage of space already used
* @param quotaValue {@link GetUserInfoRemoteOperation#SPACE_UNLIMITED} or other to determinate state
*/
+ @IonosCustomization
private void setQuotaInformation(long usedSpace, long totalSpace, int relative, long quotaValue) {
if (GetUserInfoRemoteOperation.SPACE_UNLIMITED == quotaValue) {
mQuotaTextPercentage.setText(String.format(
@@ -855,10 +855,9 @@ private void setQuotaInformation(long usedSpace, long totalSpace, int relative,
mQuotaProgressBar.setProgress(relative);
if (relative < RELATIVE_THRESHOLD_WARNING) {
- viewThemeUtils.material.colorProgressBar(mQuotaProgressBar);
+ mQuotaProgressBar.setIndicatorColor(getResources().getColor(R.color.sidemenu_progress_bar_color, getTheme()));
} else {
- viewThemeUtils.material.colorProgressBar(mQuotaProgressBar,
- getResources().getColor(R.color.infolevel_warning, getTheme()));
+ mQuotaProgressBar.setIndicatorColor(getResources().getColor(R.color.sidemenu_warn_progress_bar_color, getTheme()));
}
updateQuotaLink();
@@ -939,12 +938,12 @@ public void onLoadFailed(Exception e, Drawable errorDrawable) {
* Sets the menu item as checked in both the drawer and bottom navigation views, if applicable.
*/
@SuppressFBWarnings("RV")
+ @IonosCustomization("Removed server styles for the drawer")
public void setNavigationViewItemChecked() {
if (drawerNavigationView != null) {
MenuItem menuItem = drawerNavigationView.getMenu().findItem(menuItemId);
if (menuItem != null && !menuItem.isChecked()) {
- viewThemeUtils.platform.colorNavigationView(drawerNavigationView);
menuItem.setChecked(true);
}
}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/EditorWebView.java b/app/src/main/java/com/owncloud/android/ui/activity/EditorWebView.java
index 5ca616c337d6..f6c06a2461b6 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/EditorWebView.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/EditorWebView.java
@@ -72,7 +72,7 @@ public void onUrlLoaded(String loadedUrl) {
this.url = loadedUrl;
if (!url.isEmpty()) {
- new WebViewUtil().setProxyKKPlus(this.getWebView());
+ new WebViewUtil(getApplicationContext()).setProxyKKPlus(this.getWebView());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java b/app/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java
index dad57adc3398..663eaa42b7c8 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java
@@ -22,6 +22,7 @@
import android.webkit.WebView;
import android.widget.ProgressBar;
+import com.ionos.annotation.IonosCustomization;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.databinding.ExternalsiteWebviewBinding;
@@ -54,6 +55,7 @@ public class ExternalSiteWebView extends FileActivity {
String url;
@Override
+ @IonosCustomization
protected final void onCreate(Bundle savedInstanceState) {
Log_OC.v(TAG, "onCreate() start");
bindView();
@@ -78,6 +80,8 @@ protected final void onCreate(Bundle savedInstanceState) {
setContentView(getRootView());
postOnCreate();
+
+ viewThemeUtils.platform.themeStatusBar(this);
}
protected void postOnCreate() {
@@ -147,7 +151,7 @@ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request
}
});
- new WebViewUtil().setProxyKKPlus(getWebView());
+ new WebViewUtil(getApplicationContext()).setProxyKKPlus(getWebView());
getWebView().loadUrl(url);
}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java
index e232e313c32b..66a95d9cbf86 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java
@@ -32,6 +32,8 @@
import android.text.TextUtils;
import com.google.android.material.snackbar.Snackbar;
+import com.ionos.annotation.IonosCustomization;
+import com.ionos.utils.IonosBuildHelper;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.jobs.BackgroundJobManager;
@@ -89,6 +91,8 @@
import com.owncloud.android.ui.fragment.FileDetailFragment;
import com.owncloud.android.ui.fragment.FileDetailSharingFragment;
import com.owncloud.android.ui.fragment.OCFileListFragment;
+import com.owncloud.android.ui.fragment.filesRepository.FilesRepository;
+import com.owncloud.android.ui.fragment.filesRepository.RemoteFilesRepository;
import com.owncloud.android.ui.helpers.FileOperationsHelper;
import com.owncloud.android.ui.preview.PreviewImageActivity;
import com.owncloud.android.ui.preview.PreviewMediaActivity;
@@ -184,6 +188,8 @@ public abstract class FileActivity extends DrawerActivity
private NetworkChangeReceiver networkChangeReceiver;
+ private FilesRepository filesRepository;
+
private void registerNetworkChangeReceiver() {
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(networkChangeReceiver, filter);
@@ -242,6 +248,8 @@ protected void onCreate(Bundle savedInstanceState) {
bindService(new Intent(this, OperationsService.class), mOperationsServiceConnection,
Context.BIND_AUTO_CREATE);
registerNetworkChangeReceiver();
+
+ filesRepository = new RemoteFilesRepository(getClientRepository(), this);
}
@Override
@@ -457,7 +465,14 @@ protected void requestCredentialsUpdate(Account account) {
new CheckRemoteWipeTask(backgroundJobManager, account, new WeakReference<>(this)).execute();
}
+ @IonosCustomization("Remove account with invalid token on Ionos build")
public void performCredentialsUpdate(Account account, Context context) {
+ if (IonosBuildHelper.isIonosBuild()) {
+ /// Remove account and allow SessionMixin to handle switching to another account
+ /// or requesting new account creation
+ backgroundJobManager.startAccountRemovalJob(account.name, false);
+ return;
+ }
try {
/// step 1 - invalidate credentials of current account
OwnCloudAccount ocAccount = new OwnCloudAccount(account, context);
@@ -982,4 +997,8 @@ private FileDetailFragment getFileDetailFragment() {
}
return null;
}
+
+ public FilesRepository getFilesRepository() {
+ return filesRepository;
+ }
}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
index d38a5d67dfc4..79f2bbefc0c9 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
@@ -47,6 +47,8 @@
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar;
+import com.ionos.annotation.IonosCustomization;
+import com.ionos.player.ui.PlayerLauncher;
import com.nextcloud.appReview.InAppReviewHelper;
import com.nextcloud.client.account.User;
import com.nextcloud.client.appinfo.AppInfo;
@@ -253,6 +255,9 @@ public class FileDisplayActivity extends FileActivity
@Inject Clock clock;
@Inject SyncedFolderProvider syncedFolderProvider;
+ @IonosCustomization
+ @Inject PlayerLauncher playerLauncher;
+
public static Intent openFileIntent(Context context, User user, OCFile file) {
final Intent intent = new Intent(context, PreviewImageActivity.class);
intent.putExtra(FileActivity.EXTRA_FILE, file);
@@ -455,6 +460,7 @@ public void onConfigurationChanged(@NonNull Configuration newConfig) {
}
@Override
+ @IonosCustomization("Hide account id")
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
@@ -480,7 +486,7 @@ protected void onPostCreate(Bundle savedInstanceState) {
onOpenFileIntent(getIntent());
} else if (RESTART.equals(getIntent().getAction())) {
// most likely switched to different account
- DisplayUtils.showSnackMessage(this, String.format(getString(R.string.logged_in_as), accountManager.getUser().getAccountName()));
+ DisplayUtils.showSnackMessage(this, String.format(getString(R.string.logged_in_as), accountManager.getUser().toOwnCloudAccount().getDisplayName()));
}
upgradeNotificationForInstantUpload();
@@ -1495,6 +1501,7 @@ private boolean checkForRemoteOperationError(RemoteOperationResult syncResult) {
/**
* Show a text message on screen view for notifying user if content is loading or folder is empty
*/
+ @IonosCustomization
private void setBackgroundText() {
final OCFileListFragment ocFileListFragment = getListOfFilesFragment();
if (ocFileListFragment != null) {
@@ -1502,7 +1509,7 @@ private void setBackgroundText() {
ocFileListFragment.setEmptyListLoadingMessage();
} else {
if (MainApp.isOnlyOnDevice()) {
- ocFileListFragment.setMessageForEmptyList(R.string.file_list_empty_headline, R.string.file_list_empty_on_device, R.drawable.ic_list_empty_folder, true);
+ ocFileListFragment.setMessageForEmptyList(R.string.file_list_empty_headline, R.string.file_list_empty_on_device, R.drawable.ic_list_empty_folder);
} else {
connectivityService.isNetworkAndServerAvailable(result -> {
if (result) {
@@ -1654,7 +1661,7 @@ public void browseToRoot() {
if (listOfFiles != null) { // should never be null, indeed
OCFile root = getStorageManager().getFileByPath(OCFile.ROOT_PATH);
listOfFiles.listDirectory(root, MainApp.isOnlyOnDevice(), false);
- setFile(listOfFiles.getCurrentFile());
+ setFile(root);
startSyncFolderOperation(root, false);
}
binding.fabMain.setImageResource(R.drawable.ic_plus);
@@ -2238,13 +2245,11 @@ public void startMediaPreview(OCFile file, long startPlaybackPosition, boolean a
}
}
+ @IonosCustomization("Launch of custom player")
private void startMediaActivity(OCFile file, long startPlaybackPosition, boolean autoplay, Optional user) {
- Intent previewMediaIntent = new Intent(this, PreviewMediaActivity.class);
- previewMediaIntent.putExtra(PreviewMediaActivity.EXTRA_FILE, file);
- previewMediaIntent.putExtra(PreviewMediaActivity.EXTRA_USER, user.get());
- previewMediaIntent.putExtra(PreviewMediaActivity.EXTRA_START_POSITION, startPlaybackPosition);
- previewMediaIntent.putExtra(PreviewMediaActivity.EXTRA_AUTOPLAY, autoplay);
- startActivity(previewMediaIntent);
+ OCFileListFragment listOfFiles = getListOfFilesFragment();
+ SearchType searchType = listOfFiles != null ? listOfFiles.getCurrentSearchType() : null;
+ playerLauncher.launch(this, file, searchType);
}
public void configureToolbarForPreview(OCFile file) {
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt
index 7cba32bb9a55..d7b46c591adb 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt
@@ -22,6 +22,7 @@ import android.view.MenuItem
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.localbroadcastmanager.content.LocalBroadcastManager
+import com.ionos.annotation.IonosCustomization
import com.nextcloud.client.di.Injectable
import com.nextcloud.utils.fileNameValidator.FileNameValidator
import com.owncloud.android.R
@@ -206,6 +207,7 @@ open class FolderPickerActivity :
/**
* Show a text message on screen view for notifying user if content is loading or folder is empty
*/
+ @IonosCustomization
private fun setBackgroundText() {
val listFragment = listOfFilesFragment
@@ -219,7 +221,6 @@ open class FolderPickerActivity :
R.string.folder_list_empty_headline,
R.string.file_list_empty_moving,
R.drawable.ic_list_empty_create_folder,
- true
)
} else {
it.setEmptyListLoadingMessage()
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.kt
index 0034cf33122f..79cd16cd7fd3 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.kt
@@ -28,6 +28,8 @@ import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.common.collect.Sets
+import com.ionos.annotation.IonosCustomization
+import com.ionos.authorization_method.AuthorizationMethodActivity
import com.nextcloud.client.account.User
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.client.jobs.download.FileDownloadHelper
@@ -237,10 +239,9 @@ class ManageAccountsActivity :
return result
}
+ @IonosCustomization
override fun showFirstRunActivity() {
- val intent = Intent(applicationContext, FirstRunActivity::class.java).apply {
- putExtra(FirstRunActivity.EXTRA_ALLOW_CLOSE, true)
- }
+ val intent = AuthorizationMethodActivity.createInstance(applicationContext);
startActivity(intent)
}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java
index fa623c1705dd..1a8c479c11ba 100755
--- a/app/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java
@@ -47,6 +47,9 @@
import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.ionos.annotation.IonosCustomization;
+import com.ionos.privacy.DataProtectionActivity;
+import com.ionos.privacy.PrivacyPreferences;
import com.nextcloud.client.account.User;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.jobs.upload.FileUploadHelper;
@@ -136,6 +139,7 @@ public class ReceiveExternalFilesActivity extends FileActivity
public static final int SINGLE_PARENT = 1;
@Inject AppPreferences preferences;
+ @Inject PrivacyPreferences privacyPreferences;
@Inject LocalBroadcastManager localBroadcastManager;
@Inject SyncedFolderProvider syncedFolderProvider;
@@ -169,6 +173,7 @@ public class ReceiveExternalFilesActivity extends FileActivity
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
@Override
+ @IonosCustomization
protected void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) {
String parentPath = savedInstanceState.getString(KEY_PARENTS);
@@ -182,6 +187,14 @@ protected void onCreate(Bundle savedInstanceState) {
mAccountManager = (AccountManager) getSystemService(Context.ACCOUNT_SERVICE);
super.onCreate(savedInstanceState);
+
+ String accountName = accountManager.getCurrentOwnCloudAccount() != null
+ ? accountManager.getCurrentOwnCloudAccount().getName()
+ : null;
+ if (!privacyPreferences.isDataProtectionProcessed(accountName)) {
+ startActivity(DataProtectionActivity.createIntent(this));
+ }
+
binding = ReceiveExternalFilesBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
@@ -292,9 +305,10 @@ protected void onDestroy() {
}
@Override
+ @IonosCustomization
public void onSortingOrderChosen(FileSortOrder newSortOrder) {
preferences.setSortOrder(mFile, newSortOrder);
- sortButton.setText(DisplayUtils.getSortOrderStringId(newSortOrder));
+ sortButton.setIconResource(DisplayUtils.getSortOrderIconRes(newSortOrder));
populateDirectoryList(null);
}
@@ -728,6 +742,7 @@ private void setupActionBarSubtitle() {
}
}
+ @IonosCustomization
private void populateDirectoryList(OCFile file) {
setupEmptyList();
setupToolbar();
@@ -795,7 +810,7 @@ private void populateDirectoryList(OCFile file) {
sortButton = binding.toolbarLayout.sortButton;
FileSortOrder sortOrder = preferences.getSortOrderByFolder(mFile);
- sortButton.setText(DisplayUtils.getSortOrderStringId(sortOrder));
+ sortButton.setIconResource(DisplayUtils.getSortOrderIconRes(sortOrder));
sortButton.setOnClickListener(l -> openSortingOrderDialogFragment(getSupportFragmentManager(), sortOrder));
}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java
index bde331c722a3..0a7f161d96cc 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java
@@ -16,6 +16,7 @@
package com.owncloud.android.ui.activity;
import android.app.Activity;
+import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
@@ -41,6 +42,9 @@
import android.webkit.URLUtil;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.ionos.annotation.IonosCustomization;
+import com.ionos.privacy.PrivacySettingsActivity;
+import com.ionos.utils.IonosBuildHelper;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.di.Injectable;
@@ -146,6 +150,7 @@ public class SettingsActivity extends PreferenceActivity
@SuppressWarnings("deprecation")
@Override
+ @IonosCustomization("Delegate fix")
public void onCreate(Bundle savedInstanceState) {
boolean isApiLevel35OrHigher = (Build.VERSION.SDK_INT >= 35);
if (isApiLevel35OrHigher) {
@@ -153,10 +158,11 @@ public void onCreate(Bundle savedInstanceState) {
WindowExtensionsKt.setNoLimitLayout(getWindow());
}
- super.onCreate(savedInstanceState);
-
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
+
+ super.onCreate(savedInstanceState);
+
addPreferencesFromResource(R.xml.preferences);
setupActionBar();
@@ -195,6 +201,10 @@ public void onCreate(Bundle savedInstanceState) {
// workaround for mismatched color when app dark mode and system dark mode don't agree
setListBackground();
+
+ // workaround to set custom paddings
+ setListPadding();
+
showPasscodeDialogIfEnforceAppProtection();
if (isApiLevel35OrHigher) {
@@ -362,9 +372,10 @@ private void setupSyncCategory() {
viewThemeUtils.files.themePreferenceCategory(preferenceCategorySync);
setupAutoUploadPreference(preferenceCategorySync);
- setupInternalTwoWaySyncPreference();
+ setupInternalTwoWaySyncPreference(preferenceCategorySync);
}
+ @IonosCustomization
private void setupMoreCategory() {
final PreferenceCategory preferenceCategoryMore = (PreferenceCategory) findPreference("more");
viewThemeUtils.files.themePreferenceCategory(preferenceCategoryMore);
@@ -381,6 +392,8 @@ private void setupMoreCategory() {
removeE2E(preferenceCategoryMore);
+ setupPrivacySettingsPreference(preferenceCategoryMore);
+
setupHelpPreference(preferenceCategoryMore);
setupRecommendPreference(preferenceCategoryMore);
@@ -429,6 +442,7 @@ private void setupLoggingPreference(PreferenceCategory preferenceCategoryMore) {
}
+ @IonosCustomization
private void setupRecommendPreference(PreferenceCategory preferenceCategoryMore) {
boolean recommendEnabled = getResources().getBoolean(R.bool.recommend_enabled);
Preference pRecommend = findPreference("recommend");
@@ -443,8 +457,13 @@ private void setupRecommendPreference(PreferenceCategory preferenceCategoryMore)
String appName = getString(R.string.app_name);
String downloadUrlGooglePlayStore = getString(R.string.url_app_download);
String downloadUrlFDroid = getString(R.string.fdroid_link);
- String downloadUrls = String.format(getString(R.string.recommend_urls),
- downloadUrlGooglePlayStore, downloadUrlFDroid);
+ String downloadUrls;
+ if (URLUtil.isValidUrl(downloadUrlFDroid)) {
+ downloadUrls = String.format(getString(R.string.recommend_urls),
+ downloadUrlGooglePlayStore, downloadUrlFDroid);
+ } else {
+ downloadUrls = downloadUrlGooglePlayStore;
+ }
String recommendSubject = String.format(getString(R.string.recommend_subject), appName);
String recommendText = String.format(getString(R.string.recommend_text),
@@ -571,6 +590,16 @@ private void showRemoveE2EAlertDialog(PreferenceCategory preferenceCategoryMore,
.show();
}
+ private void setupPrivacySettingsPreference(PreferenceCategory preferenceCategoryMore) {
+ Preference privacySettings = findPreference("privacy_settings");
+ if (privacySettings != null) {
+ privacySettings.setOnPreferenceClickListener(preference -> {
+ startActivity(PrivacySettingsActivity.createIntent(this));
+ return true;
+ });
+ }
+ }
+
private void setupHelpPreference(PreferenceCategory preferenceCategoryMore) {
boolean helpEnabled = getResources().getBoolean(R.bool.help_enabled);
Preference pHelp = findPreference("help");
@@ -599,9 +628,15 @@ private void setupAutoUploadPreference(PreferenceCategory preferenceCategoryMore
}
}
- private void setupInternalTwoWaySyncPreference() {
+ @IonosCustomization("internal_two_way_sync was hidden")
+ private void setupInternalTwoWaySyncPreference(PreferenceCategory preferenceCategorySync) {
Preference twoWaySync = findPreference("internal_two_way_sync");
+ if (IonosBuildHelper.isIonosBuild()) {
+ preferenceCategorySync.removePreference(twoWaySync);
+ return;
+ }
+
twoWaySync.setOnPreferenceClickListener(preference -> {
Intent intent = new Intent(this, InternalTwoWaySyncActivity.class);
startActivity(intent);
@@ -840,6 +875,7 @@ private void disableLock(String lock) {
}
}
+ @IonosCustomization("Workaround to hide prefStoragePath")
private void setupGeneralCategory() {
final PreferenceCategory preferenceCategoryGeneral = (PreferenceCategory) findPreference("general");
viewThemeUtils.files.themePreferenceCategory(preferenceCategoryGeneral);
@@ -848,6 +884,7 @@ private void setupGeneralCategory() {
prefDataLoc = findPreference(AppPreferencesImpl.DATA_STORAGE_LOCATION);
if (prefDataLoc != null) {
+ preferenceCategoryGeneral.removePreference(prefDataLoc);
prefDataLoc.setOnPreferenceClickListener(p -> {
Intent intent = new Intent(MainApp.getAppContext(), ChooseStorageLocationActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
@@ -890,6 +927,12 @@ private void setListBackground() {
getListView().setBackgroundColor(ContextCompat.getColor(this, R.color.bg_default));
}
+ @IonosCustomization
+ private void setListPadding() {
+ int bottom = (int) getResources().getDimension(R.dimen.settings_screen_list_bottom_padding);
+ getListView().setPadding(0, 0, 0, bottom);
+ }
+
private String getAppVersion() {
String temp;
try {
@@ -1129,6 +1172,11 @@ public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}
+ @Override
+ protected void attachBaseContext(Context newBase) {
+ super.attachBaseContext(getDelegate().attachBaseContext2(newBase));
+ }
+
private AppCompatDelegate getDelegate() {
if (delegate == null) {
delegate = AppCompatDelegate.create(this, null);
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.java
index 3c6b724e553f..f880e0a42fc5 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/ShareActivity.java
@@ -17,6 +17,7 @@
import android.graphics.drawable.LayerDrawable;
import android.os.Bundle;
+import com.ionos.annotation.IonosCustomization;
import com.nextcloud.client.account.User;
import com.owncloud.android.R;
import com.owncloud.android.databinding.ShareActivityBinding;
@@ -39,6 +40,7 @@
import javax.inject.Inject;
+import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
@@ -55,12 +57,20 @@ public class ShareActivity extends FileActivity {
SyncedFolderProvider syncedFolderProvider;
@Override
+ @IonosCustomization("Setup toolbar if available")
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ShareActivityBinding binding = ShareActivityBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ if (toolbar != null) {
+ setSupportActionBar(toolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ toolbar.setNavigationOnClickListener(v -> finish());
+ }
+
OCFile file = getFile();
Optional optionalUser = getUser();
if (!optionalUser.isPresent()) {
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SingleStateDrawerArrowDrawable.kt b/app/src/main/java/com/owncloud/android/ui/activity/SingleStateDrawerArrowDrawable.kt
new file mode 100644
index 000000000000..60ed8da431ed
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/activity/SingleStateDrawerArrowDrawable.kt
@@ -0,0 +1,24 @@
+/*
+ * IONOS HiDrive Next - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 STRATO GmbH.
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+package com.owncloud.android.ui.activity
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.drawable.Drawable
+import androidx.appcompat.graphics.drawable.DrawerArrowDrawable
+
+class SingleStateDrawerArrowDrawable(
+ context: Context,
+ private val drawable: Drawable,
+) : DrawerArrowDrawable(context) {
+
+ override fun draw(canvas: Canvas) {
+ drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
+ drawable.draw(canvas)
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt
index d18c26825cb6..f449bf9646cd 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt
@@ -26,6 +26,7 @@ import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.Lifecycle
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.ionos.annotation.IonosCustomization
import com.nextcloud.client.core.Clock
import com.nextcloud.client.device.PowerManagementService
import com.nextcloud.client.di.Injectable
@@ -802,6 +803,8 @@ class SyncedFoldersActivity :
item.setExcludeHidden(excludeHidden)
}
+ @IonosCustomization("StackOverflow fix")
+ private var externalStoragePermissionRequestCount = 0
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
when (requestCode) {
PermissionUtil.PERMISSIONS_EXTERNAL_STORAGE -> {
@@ -809,6 +812,8 @@ class SyncedFoldersActivity :
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted
load(getItemsDisplayedPerFolder(), true)
+ } else if (externalStoragePermissionRequestCount++ == 0) {
+ PermissionUtil.requestExternalStoragePermission(this, viewThemeUtils, true)
}
}
else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults)
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ToolbarActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/ToolbarActivity.java
index 0e56749caa79..3dd44e04b2fb 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/ToolbarActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/ToolbarActivity.java
@@ -13,7 +13,6 @@
*/
package com.owncloud.android.ui.activity;
-import android.animation.AnimatorInflater;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.Color;
@@ -30,6 +29,7 @@
import com.google.android.material.button.MaterialButton;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.textview.MaterialTextView;
+import com.ionos.annotation.IonosCustomization;
import com.nextcloud.client.di.Injectable;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
@@ -74,6 +74,7 @@ public abstract class ToolbarActivity extends BaseActivity implements Injectable
* Toolbar setup that must be called in implementer's {@link #onCreate} after {@link #setContentView} if they want
* to use the toolbar.
*/
+ @IonosCustomization
private void setupToolbar(boolean isHomeSearchToolbarShow, boolean showSortListButtonGroup) {
mToolbar = findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
@@ -101,8 +102,6 @@ private void setupToolbar(boolean isHomeSearchToolbarShow, boolean showSortListB
mToolbarSpinner = findViewById(R.id.toolbar_spinner);
- viewThemeUtils.material.themeToolbar(mToolbar);
- viewThemeUtils.material.colorToolbarOverflowIcon(mToolbar);
viewThemeUtils.platform.themeStatusBar(this);
viewThemeUtils.material.colorMaterialTextButton(mSwitchAccountButton);
}
@@ -162,25 +161,20 @@ public void hideSearchView(OCFile chosenFile) {
}
}
+ @IonosCustomization
private void showHomeSearchToolbar(String title, boolean isRoot) {
showHomeSearchToolbar(isHomeSearchToolbarShow && isRoot);
- mSearchText.setText(getString(R.string.appbar_search_in, title));
+ mSearchText.setText(getString(R.string.actionbar_search, title));
}
@SuppressLint("PrivateResource")
+ @IonosCustomization
private void showHomeSearchToolbar(boolean isShow) {
- viewThemeUtils.material.themeToolbar(mToolbar);
if (isShow) {
viewThemeUtils.platform.resetStatusBar(this);
- mAppBar.setStateListAnimator(AnimatorInflater.loadStateListAnimator(mAppBar.getContext(),
- R.animator.appbar_elevation_off));
mDefaultToolbar.setVisibility(View.GONE);
mHomeSearchToolbar.setVisibility(View.VISIBLE);
- viewThemeUtils.material.themeCardView(mHomeSearchToolbar);
- viewThemeUtils.material.themeSearchBarText(mSearchText);
} else {
- mAppBar.setStateListAnimator(AnimatorInflater.loadStateListAnimator(mAppBar.getContext(),
- R.animator.appbar_elevation_on));
viewThemeUtils.platform.themeStatusBar(this);
mDefaultToolbar.setVisibility(View.VISIBLE);
mHomeSearchToolbar.setVisibility(View.GONE);
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java
index 36b12acd7e68..cb592a5047b4 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java
@@ -28,6 +28,7 @@
import android.widget.ArrayAdapter;
import android.widget.TextView;
+import com.ionos.annotation.IonosCustomization;
import com.nextcloud.client.account.User;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.jobs.upload.FileUploadHelper;
@@ -133,6 +134,7 @@ public static void startUploadActivityForResult(Activity activity,
@Override
@SuppressLint("WrongViewCast") // wrong error on finding local_files_list
+ @IonosCustomization
public void onCreate(Bundle savedInstanceState) {
Log_OC.d(TAG, "onCreate() start");
super.onCreate(savedInstanceState);
@@ -168,7 +170,7 @@ public void onCreate(Bundle savedInstanceState) {
/// USER INTERFACE
// Drop-down navigation
- mDirectories = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item);
+ mDirectories = new ArrayAdapter<>(this, R.layout.simple_spinner_item);
mDirectories.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
fillDirectoryDropdown();
@@ -276,6 +278,7 @@ private void fillDirectoryDropdown() {
}
@Override
+ @IonosCustomization
public boolean onCreateOptionsMenu(Menu menu) {
mOptionsMenu = menu;
getMenuInflater().inflate(R.menu.activity_upload_files, menu);
@@ -288,7 +291,6 @@ public boolean onCreateOptionsMenu(Menu menu) {
final MenuItem item = menu.findItem(R.id.action_search);
mSearchView = (SearchView) MenuItemCompat.getActionView(item);
viewThemeUtils.androidx.themeToolbarSearchView(mSearchView);
- viewThemeUtils.platform.tintTextDrawable(this, menu.findItem(R.id.action_choose_storage_path).getIcon());
mSearchView.setOnSearchClickListener(v -> mToolbarSpinner.setVisibility(View.GONE));
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java
index a1a27d25429f..4c40de496d22 100755
--- a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java
@@ -20,6 +20,7 @@
import android.view.MenuItem;
import android.view.View;
+import com.ionos.annotation.IonosCustomization;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.core.Clock;
@@ -142,12 +143,11 @@ private void handleUploadWorkerState() {
uploadListAdapter.loadUploadItemsFromDb();
}
+ @IonosCustomization
private void setupContent() {
binding.list.setEmptyView(binding.emptyList.getRoot());
binding.emptyList.getRoot().setVisibility(View.GONE);
binding.emptyList.emptyListIcon.setImageResource(R.drawable.uploads);
- binding.emptyList.emptyListIcon.getDrawable().mutate();
- binding.emptyList.emptyListIcon.setAlpha(0.5f);
binding.emptyList.emptyListIcon.setVisibility(View.VISIBLE);
binding.emptyList.emptyListViewHeadline.setText(getString(R.string.upload_list_empty_headline));
binding.emptyList.emptyListViewText.setText(getString(R.string.upload_list_empty_text_auto_upload));
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java
index e1cf782b39cb..741394837e90 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java
@@ -28,6 +28,7 @@
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.SimpleTarget;
+import com.ionos.annotation.IonosCustomization;
import com.nextcloud.client.account.User;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.preferences.AppPreferences;
@@ -231,7 +232,9 @@ public void onLoadFailed(Exception e, Drawable errorDrawable) {
}
}
+ @IonosCustomization("Hide account id")
private void populateUserInfoUi(UserInfo userInfo) {
+ binding.userinfoUsername.setVisibility(View.GONE);
binding.userinfoUsername.setText(user.getAccountName());
binding.userinfoIcon.setTag(user.getAccountName());
DisplayUtils.setAvatar(user,
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.java
index 3c20828d1950..3dfa77abb1fa 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.java
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.java
@@ -6,6 +6,8 @@
*/
package com.owncloud.android.ui.adapter;
+import com.ionos.annotation.IonosCustomization;
+import com.ionos.utils.IonosBuildHelper;
import com.nextcloud.client.account.User;
import com.nextcloud.ui.ImageDetailFragment;
import com.owncloud.android.datamodel.OCFile;
@@ -54,7 +56,13 @@ public ImageDetailFragment getImageDetailFragment() {
@NonNull
@Override
+ @IonosCustomization("Hide tabs in IONOS")
public Fragment createFragment(int position) {
+ if (IonosBuildHelper.isIonosBuild()) {
+ fileDetailSharingFragment = FileDetailSharingFragment.newInstance(file, user);
+ return fileDetailSharingFragment;
+ }
+
return switch (position) {
default -> {
fileDetailActivitiesFragment = FileDetailActivitiesFragment.newInstance(file, user);
@@ -72,7 +80,12 @@ public Fragment createFragment(int position) {
}
@Override
+ @IonosCustomization("Hide tabs in IONOS")
public int getItemCount() {
+ if (IonosBuildHelper.isIonosBuild()) {
+ return showSharingTab ? 1 : 0;
+ }
+
if (showSharingTab) {
if (MimeTypeUtil.isImage(file)) {
return 3;
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/GallerySimpleAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/GallerySimpleAdapter.kt
new file mode 100644
index 000000000000..7193db5da28e
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/GallerySimpleAdapter.kt
@@ -0,0 +1,243 @@
+/*
+ * IONOS HiDrive Next - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 STRATO GmbH.
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+package com.owncloud.android.ui.adapter
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.nextcloud.client.account.User
+import com.nextcloud.client.network.ClientFactoryImpl
+import com.nextcloud.client.preferences.AppPreferences
+import com.owncloud.android.MainApp
+import com.owncloud.android.databinding.GalleryHeaderBinding
+import com.owncloud.android.databinding.GallerySimpleItemBinding
+import com.owncloud.android.datamodel.FileDataStorageManager
+import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.ui.activity.ComponentsGetter
+import com.owncloud.android.ui.fragment.GalleryFragment
+import com.owncloud.android.ui.fragment.GalleryFragmentBottomSheetDialog
+import com.owncloud.android.ui.fragment.SearchType
+import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface
+import com.owncloud.android.utils.DisplayUtils
+import com.owncloud.android.utils.FileSortOrder
+import com.owncloud.android.utils.MimeTypeUtil
+import com.owncloud.android.utils.glide.CustomGlideStreamLoader
+import com.owncloud.android.utils.theme.ViewThemeUtils
+
+class GallerySimpleAdapter(
+ context: Context,
+ user: User,
+ private val ocFileListFragmentInterface: OCFileListFragmentInterface,
+ preferences: AppPreferences,
+ transferServiceGetter: ComponentsGetter,
+ viewThemeUtils: ViewThemeUtils,
+ private var columns: Int,
+ private val defaultThumbnailSize: Int
+) : RecyclerView.Adapter(), CommonOCFileListAdapterInterface {
+
+ private val storageManager = transferServiceGetter.storageManager
+ private val clientFactory = ClientFactoryImpl(MainApp.getAppContext())
+ private val getThumbnailUrl = clientFactory.create(user).baseUri.toString() + "/index.php/core/preview"
+ private val modelLoader = CustomGlideStreamLoader(user, clientFactory)
+
+ private val ocFileListDelegate = OCFileListDelegate(
+ transferServiceGetter.fileUploaderHelper,
+ context,
+ ocFileListFragmentInterface,
+ user,
+ storageManager,
+ false,
+ preferences,
+ true,
+ transferServiceGetter,
+ showMetadata = false,
+ showShareAvatar = false,
+ viewThemeUtils
+ )
+
+ private val items: MutableList = mutableListOf()
+
+ private var layoutManager: GridLayoutManager? = null
+
+ companion object {
+ private const val VIEW_TYPE_HEADER = 0
+ private const val VIEW_TYPE_ITEM = 1
+ }
+
+ fun showAllGalleryItems(
+ remotePath: String,
+ mediaState: GalleryFragmentBottomSheetDialog.MediaState,
+ photoFragment: GalleryFragment,
+ ) {
+ val files = storageManager.allGalleryItems
+ .filter { it != null && it.remotePath.startsWith(remotePath) && it.isSatisfiesMediaState(mediaState) }
+ .distinct()
+ .sortedByDescending { it.modificationTimestamp }
+
+ if (files.isEmpty()) {
+ photoFragment.setEmptyListMessage(SearchType.GALLERY_SEARCH)
+ }
+
+ val filesGroupedByMonthAndYear = files.groupBy { file ->
+ val month = DisplayUtils.getDateByPattern(file.modificationTimestamp, DisplayUtils.MONTH_PATTERN)
+ val year = DisplayUtils.getDateByPattern(file.modificationTimestamp, DisplayUtils.YEAR_PATTERN)
+ month to year
+ }
+
+ items.clear()
+ filesGroupedByMonthAndYear.forEach { (month, year), files ->
+ items.add(GalleryItem.Header(month, year))
+ items.addAll(files.map(GalleryItem::FileItem))
+ }
+
+ notifyDataSetChanged()
+ }
+
+ private fun OCFile.isSatisfiesMediaState(mediaState: GalleryFragmentBottomSheetDialog.MediaState): Boolean {
+ return when (mediaState) {
+ GalleryFragmentBottomSheetDialog.MediaState.MEDIA_STATE_PHOTOS_ONLY -> MimeTypeUtil.isImage(mimeType)
+ GalleryFragmentBottomSheetDialog.MediaState.MEDIA_STATE_VIDEOS_ONLY -> MimeTypeUtil.isVideo(mimeType)
+ GalleryFragmentBottomSheetDialog.MediaState.MEDIA_STATE_DEFAULT -> true
+ }
+ }
+
+ fun setLayoutManager(layoutManager: GridLayoutManager) {
+ layoutManager.spanCount = columns
+ layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
+ override fun getSpanSize(position: Int): Int {
+ return if (getItemViewType(position) == VIEW_TYPE_HEADER) {
+ layoutManager.spanCount
+ } else {
+ 1
+ }
+ }
+ }
+ this.layoutManager = layoutManager
+ }
+
+ fun changeColumn(newColumn: Int) {
+ this.columns = newColumn
+ this.layoutManager?.spanCount = newColumn
+ }
+
+ fun getItem(position: Int): OCFile? {
+ return (items[position] as? GalleryItem.FileItem)?.file
+ }
+
+ fun isEmpty(): Boolean {
+ return items.isEmpty()
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ return when (items[position]) {
+ is GalleryItem.Header -> VIEW_TYPE_HEADER
+ is GalleryItem.FileItem -> VIEW_TYPE_ITEM
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ if (viewType == VIEW_TYPE_HEADER) {
+ val binding = GalleryHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return GalleryHeaderViewHolder(binding)
+ } else {
+ val binding = GallerySimpleItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return GallerySimpleItemHolder(
+ binding,
+ defaultThumbnailSize,
+ getThumbnailUrl,
+ modelLoader,
+ ocFileListFragmentInterface,
+ )
+ }
+ }
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ when (val item = items[position]) {
+ is GalleryItem.Header -> (holder as GalleryHeaderViewHolder).let {
+ it.binding.month.text = item.month
+ it.binding.year.text = item.year
+ }
+
+ is GalleryItem.FileItem -> (holder as GallerySimpleItemHolder).bind(item.file)
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+
+ override fun isMultiSelect(): Boolean {
+ return ocFileListDelegate.isMultiSelect
+ }
+
+ override fun cancelAllPendingTasks() {
+ ocFileListDelegate.cancelAllPendingTasks()
+ }
+
+ override fun getItemPosition(file: OCFile): Int {
+ return items.indexOfFirst { it is GalleryItem.FileItem && it.file == file }
+ }
+
+ override fun swapDirectory(
+ user: User,
+ directory: OCFile,
+ storageManager: FileDataStorageManager,
+ onlyOnDevice: Boolean,
+ mLimitToMimeType: String
+ ) {
+ // TODO("Not yet implemented")
+ }
+
+ override fun setHighlightedItem(file: OCFile) {
+ // TODO("Not yet implemented")
+ }
+
+ override fun setSortOrder(mFile: OCFile, sortOrder: FileSortOrder) {
+ // TODO("Not yet implemented")
+ }
+
+ override fun addCheckedFile(file: OCFile) {
+ ocFileListDelegate.addCheckedFile(file)
+ }
+
+ override fun isCheckedFile(file: OCFile): Boolean {
+ return ocFileListDelegate.isCheckedFile(file)
+ }
+
+ override fun getCheckedItems(): Set {
+ return ocFileListDelegate.checkedItems
+ }
+
+ override fun removeCheckedFile(file: OCFile) {
+ ocFileListDelegate.removeCheckedFile(file)
+ }
+
+ override fun notifyItemChanged(file: OCFile) {
+ notifyItemChanged(getItemPosition(file))
+ }
+
+ override fun getFilesCount(): Int {
+ return items.size
+ }
+
+ override fun setMultiSelect(boolean: Boolean) {
+ ocFileListDelegate.isMultiSelect = boolean
+ notifyDataSetChanged()
+ }
+
+ override fun clearCheckedItems() {
+ ocFileListDelegate.clearCheckedItems()
+ }
+
+ sealed class GalleryItem {
+ data class Header(val month: String, val year: String) : GalleryItem()
+ data class FileItem(val file: OCFile) : GalleryItem()
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/GallerySimpleItemHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/GallerySimpleItemHolder.kt
new file mode 100644
index 000000000000..e05c46901cc1
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/GallerySimpleItemHolder.kt
@@ -0,0 +1,40 @@
+/*
+ * IONOS HiDrive Next - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 STRATO Gmbh.
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+package com.owncloud.android.ui.adapter
+
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import com.bumptech.glide.Glide
+import com.owncloud.android.databinding.GallerySimpleItemBinding
+import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.ui.fragment.GalleryFragment
+import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface
+import com.owncloud.android.utils.glide.CustomGlideStreamLoader
+
+class GallerySimpleItemHolder(
+ private val binding: GallerySimpleItemBinding,
+ private val thumbnailSize: Int,
+ private val getThumbnailUrl: String,
+ private val modelLoader: CustomGlideStreamLoader,
+ private val ocFileListFragmentInterface: OCFileListFragmentInterface
+) : ViewHolder(binding.root) {
+
+ fun bind(file: OCFile) {
+ binding.thumbnail.setOnClickListener {
+ ocFileListFragmentInterface.onItemClicked(file)
+ GalleryFragment.setLastMediaItemPosition(absoluteAdapterPosition)
+ }
+
+ Glide
+ .with(itemView.context)
+ .using(modelLoader)
+ .load("$getThumbnailUrl?fileId=${file.localId}&x=$thumbnailSize&y=$thumbnailSize&a=1&mode=cover&forceIcon=0")
+ .placeholder(com.elyeproj.loaderviewlibrary.R.color.default_color)
+ .override(thumbnailSize, thumbnailSize)
+ .into(binding.thumbnail)
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.java b/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.java
index b2e70da44dd8..35817d2d5ed5 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.java
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.java
@@ -16,9 +16,11 @@
import android.content.Context;
import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.View;
+import com.ionos.annotation.IonosCustomization;
import com.nextcloud.android.lib.resources.files.FileDownloadLimit;
import com.nextcloud.utils.mdm.MDMConfig;
import com.owncloud.android.R;
@@ -50,6 +52,7 @@ public LinkShareViewHolder(FileDetailsShareLinkShareItemBinding binding,
this.viewThemeUtils = viewThemeUtils;
}
+ @IonosCustomization
public void bind(OCShare publicShare, ShareeListAdapterListener listener) {
if (ShareType.EMAIL == publicShare.getShareType()) {
binding.name.setText(publicShare.getSharedWithDisplayName());
@@ -58,8 +61,11 @@ public void bind(OCShare publicShare, ShareeListAdapterListener listener) {
null));
binding.copyLink.setVisibility(View.GONE);
- binding.icon.getBackground().setColorFilter(context.getResources().getColor(R.color.nc_grey),
- PorterDuff.Mode.SRC_IN);
+ Drawable background = binding.icon.getBackground();
+ if(background != null) {
+ background.setColorFilter(context.getResources().getColor(R.color.nc_grey),
+ PorterDuff.Mode.SRC_IN);
+ }
binding.icon.getDrawable().mutate().setColorFilter(context.getResources().getColor(R.color.icon_on_nc_grey),
PorterDuff.Mode.SRC_IN);
} else {
@@ -74,7 +80,7 @@ public void bind(OCShare publicShare, ShareeListAdapterListener listener) {
}
}
- viewThemeUtils.platform.colorImageViewBackgroundAndIcon(binding.icon);
+ // viewThemeUtils.platform.colorImageViewBackgroundAndIcon(binding.icon);
}
FileDownloadLimit downloadLimit = publicShare.getFileDownloadLimit();
@@ -103,11 +109,12 @@ public void bind(OCShare publicShare, ShareeListAdapterListener listener) {
}
}
+ @IonosCustomization
private void setPermissionName(OCShare publicShare, String permissionName) {
if (!TextUtils.isEmpty(permissionName) && !SharingMenuHelper.isSecureFileDrop(publicShare)) {
binding.permissionName.setText(permissionName);
binding.permissionName.setVisibility(View.VISIBLE);
- viewThemeUtils.androidx.colorPrimaryTextViewElement(binding.permissionName);
+ // viewThemeUtils.androidx.colorPrimaryTextViewElement(binding.permissionName);
} else {
binding.permissionName.setVisibility(View.GONE);
}
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ListGridItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/ListGridItemViewHolder.kt
index 3132a7fafc60..382e5b264724 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/ListGridItemViewHolder.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/ListGridItemViewHolder.kt
@@ -8,7 +8,11 @@
package com.owncloud.android.ui.adapter
import android.widget.TextView
+import com.ionos.annotation.IonosCustomization
+import com.ionos.player.ui.common.PlayerProgressIndicator
internal interface ListGridItemViewHolder : ListViewHolder {
val fileName: TextView
+ @IonosCustomization("Show current playback progress")
+ val playerProgressIndicator: PlayerProgressIndicator
}
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java
index 16c032707a29..a7c773413bd6 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java
@@ -21,6 +21,7 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import com.ionos.annotation.IonosCustomization;
import com.nextcloud.android.common.ui.theme.utils.ColorRole;
import com.nextcloud.client.preferences.AppPreferences;
import com.owncloud.android.R;
@@ -157,6 +158,7 @@ public long getItemId(int position) {
}
@Override
+ @IonosCustomization
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder instanceof LocalFileListFooterViewHolder) {
((LocalFileListFooterViewHolder) holder).footerText.setText(getFooterText());
@@ -179,8 +181,7 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi
if (isCheckedFile(file)) {
gridViewHolder.itemLayout.setBackgroundColor(ContextCompat.getColor(mContext, R.color.selected_item_background));
- gridViewHolder.checkbox.setImageDrawable(
- viewThemeUtils.platform.tintDrawable(mContext, R.drawable.ic_checkbox_marked, ColorRole.PRIMARY));
+ gridViewHolder.checkbox.setImageResource(R.drawable.ic_checkbox_marked);
} else {
gridViewHolder.itemLayout.setBackgroundColor(mContext.getResources().getColor(R.color.bg_default));
gridViewHolder.checkbox.setImageResource(R.drawable.ic_checkbox_blank_outline);
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
index 452e366448a8..03a0a3eab633 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
@@ -31,6 +31,8 @@
import com.elyeproj.loaderviewlibrary.LoaderImageView;
import com.google.android.material.chip.Chip;
+import com.ionos.annotation.IonosCustomization;
+import com.ionos.utils.IonosBuildHelper;
import com.nextcloud.android.common.ui.theme.utils.ColorRole;
import com.nextcloud.android.lib.resources.recommendations.Recommendation;
import com.nextcloud.client.account.User;
@@ -350,6 +352,7 @@ public OCFile getItem(int position) {
}
@Override
+ @IonosCustomization
public int getItemViewType(int position) {
if (shouldShowHeader() && position == 0) {
return VIEW_TYPE_HEADER;
@@ -360,6 +363,10 @@ public int getItemViewType(int position) {
return VIEW_TYPE_FOOTER;
}
+ if (IonosBuildHelper.isIonosBuild()) {
+ return VIEW_TYPE_ITEM;
+ }
+
OCFile item = getItem(position);
if (item == null) {
return VIEW_TYPE_ITEM;
@@ -535,8 +542,14 @@ private void updateLivePhotoIndicators(ListViewHolder holder, OCFile file) {
}
}
+ @IonosCustomization("Custom grid view, Show current playback progress")
private void bindListGridItemViewHolder(ListGridItemViewHolder holder, OCFile file) {
holder.getFileName().setText(mStorageManager.getFilenameConsideringOfflineOperation(file));
+ holder.getPlayerProgressIndicator().setFile(file);
+
+ if (IonosBuildHelper.isIonosBuild()) {
+ return;
+ }
boolean gridImage = MimeTypeUtil.isImage(file) || MimeTypeUtil.isVideo(file);
if (gridView && gridImage) {
@@ -550,33 +563,20 @@ private void bindListGridItemViewHolder(ListGridItemViewHolder holder, OCFile fi
}
}
+ @IonosCustomization("Change share icon")
private void bindListItemViewHolder(ListItemViewHolder holder, OCFile file) {
+ holder.getSharedAvatars().setVisibility(View.GONE);
+ holder.getSharedAvatars().removeAllViews();
if ((file.isSharedWithMe() || file.isSharedWithSharee()) && !isMultiSelect() && !gridView &&
!hideItemOptions) {
- holder.getSharedAvatars().setVisibility(View.VISIBLE);
- holder.getSharedAvatars().removeAllViews();
-
- String fileOwner = file.getOwnerId();
- List sharees = file.getSharees();
-
- // use fileOwner if not oneself, then add at first
- ShareeUser fileOwnerSharee = new ShareeUser(fileOwner, file.getOwnerDisplayName(), ShareType.USER);
- if (!TextUtils.isEmpty(fileOwner) &&
- !fileOwner.equals(userId) &&
- !sharees.contains(fileOwnerSharee)) {
- sharees.add(fileOwnerSharee);
+ if(file.isSharedViaLink()){
+ holder.getShared().setImageResource(R.drawable.ic_shared_all_types);
+ } else {
+ holder.getShared().setImageResource(R.drawable.shared_via_users);
}
-
- Collections.reverse(sharees);
-
- Log_OC.d(this, "sharees of " + file.getFileName() + ": " + sharees);
-
- holder.getSharedAvatars().setAvatars(user, sharees, viewThemeUtils);
- holder.getSharedAvatars().setOnClickListener(
+ holder.getShared().setVisibility(View.VISIBLE);
+ holder.getShared().setOnClickListener(
view -> ocFileListFragmentInterface.onShareIconClick(file));
- } else {
- holder.getSharedAvatars().setVisibility(View.GONE);
- holder.getSharedAvatars().removeAllViews();
}
// tags
@@ -748,6 +748,7 @@ private String generateFooterText(int filesCount, int foldersCount) {
return output;
}
+ @IonosCustomization("Hide header in IONOS")
public boolean shouldShowHeader() {
if (currentDirectory == null) {
return false;
@@ -765,7 +766,7 @@ public boolean shouldShowHeader() {
return false;
}
- return !TextUtils.isEmpty(currentDirectory.getRichWorkspace().trim());
+ return !IonosBuildHelper.isIonosBuild() && !TextUtils.isEmpty(currentDirectory.getRichWorkspace().trim());
}
/**
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt
index accfc04b6a8c..13c216a13a0d 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt
@@ -16,6 +16,7 @@ import android.widget.ImageView
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import com.elyeproj.loaderviewlibrary.LoaderImageView
+import com.ionos.annotation.IonosCustomization
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.client.account.User
import com.nextcloud.client.jobs.download.FileDownloadHelper
@@ -212,6 +213,7 @@ class OCFileListDelegate(
}
@Suppress("MagicNumber")
+ @IonosCustomization
fun bindGridViewHolder(
gridViewHolder: ListViewHolder,
file: OCFile,
@@ -221,7 +223,18 @@ class OCFileListDelegate(
// thumbnail
gridViewHolder.imageFileName?.text = file.fileName
gridViewHolder.thumbnail.tag = file.fileId
- setThumbnail(gridViewHolder.thumbnail, gridViewHolder.shimmerThumbnail, file)
+ OCFileListThumbnailLoader(
+ file,
+ gridViewHolder,
+ user,
+ storageManager,
+ asyncTasks,
+ gridView,
+ context,
+ preferences,
+ viewThemeUtils,
+ syncFolderProvider
+ ).load()
// item layout + click listeners
bindGridItemLayout(file, gridViewHolder)
@@ -275,8 +288,9 @@ class OCFileListDelegate(
}
}
+ @IonosCustomization
private fun bindGridItemLayout(file: OCFile, gridViewHolder: ListViewHolder) {
- setItemLayoutBackgroundColor(file, gridViewHolder)
+ setItemLayoutBackground(file, gridViewHolder)
setCheckBoxImage(file, gridViewHolder)
setItemLayoutOnClickListeners(file, gridViewHolder)
@@ -300,6 +314,26 @@ class OCFileListDelegate(
}
}
+ @IonosCustomization
+ private fun setItemLayoutBackground(file: OCFile, gridViewHolder: ListViewHolder) {
+ val isSelected = file.fileId == highlightedItem?.fileId || isCheckedFile(file)
+ if (gridViewHolder is OCFileListGridItemViewHolder) {
+ val itemLayoutBackgroundResId = if (isSelected) {
+ R.drawable.grid_mode_selected_item_background
+ } else {
+ R.drawable.grid_mode_item_background
+ }
+ gridViewHolder.itemLayout.setBackgroundResource(itemLayoutBackgroundResId)
+ } else {
+ val itemLayoutBackgroundColor = if (isSelected) {
+ ContextCompat.getColor(context, R.color.selected_item_background)
+ } else {
+ ContextCompat.getColor(context, R.color.bg_default)
+ }
+ gridViewHolder.itemLayout.setBackgroundColor(itemLayoutBackgroundColor)
+ }
+ }
+
private fun setItemLayoutBackgroundColor(file: OCFile, gridViewHolder: ListViewHolder) {
val cornerRadius = context.resources.getDimension(R.dimen.selected_grid_container_radius)
@@ -322,11 +356,10 @@ class OCFileListDelegate(
}
}
+ @IonosCustomization
private fun setCheckBoxImage(file: OCFile, gridViewHolder: ListViewHolder) {
if (isCheckedFile(file)) {
- gridViewHolder.checkbox.setImageDrawable(
- viewThemeUtils.platform.tintDrawable(context, R.drawable.ic_checkbox_marked, ColorRole.PRIMARY)
- )
+ gridViewHolder.checkbox.setImageResource(R.drawable.ic_checkbox_marked)
} else {
gridViewHolder.checkbox.setImageResource(R.drawable.ic_checkbox_blank_outline)
}
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListGridItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListGridItemViewHolder.kt
index 05ee4731e994..579ee91ada7a 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListGridItemViewHolder.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListGridItemViewHolder.kt
@@ -14,6 +14,8 @@ import android.widget.LinearLayout
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.elyeproj.loaderviewlibrary.LoaderImageView
+import com.ionos.annotation.IonosCustomization
+import com.ionos.player.ui.common.PlayerProgressIndicator
import com.owncloud.android.databinding.GridItemBinding
internal class OCFileListGridItemViewHolder(var binding: GridItemBinding) :
@@ -25,6 +27,9 @@ internal class OCFileListGridItemViewHolder(var binding: GridItemBinding) :
get() = binding.Filename
override val thumbnail: ImageView
get() = binding.thumbnail
+ @IonosCustomization("Show current playback progress")
+ override val playerProgressIndicator: PlayerProgressIndicator
+ get() = binding.playerProgressIndicator
override fun showVideoOverlay() {
binding.videoOverlay.visibility = View.VISIBLE
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt
index 38b3310f0bbe..d2215e87d5ec 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListItemViewHolder.kt
@@ -16,6 +16,8 @@ import androidx.recyclerview.widget.RecyclerView
import com.elyeproj.loaderviewlibrary.LoaderImageView
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
+import com.ionos.annotation.IonosCustomization
+import com.ionos.player.ui.common.PlayerProgressIndicator
import com.owncloud.android.databinding.ListItemBinding
import com.owncloud.android.ui.AvatarGroupLayout
@@ -55,6 +57,9 @@ internal class OCFileListItemViewHolder(private var binding: ListItemBinding) :
get() = binding.tagMore
override val fileDetailGroup: LinearLayout
get() = binding.fileDetailGroup
+ @IonosCustomization("Show current playback progress")
+ override val playerProgressIndicator: PlayerProgressIndicator
+ get() = binding.playerProgressIndicator
override fun showVideoOverlay() {
binding.thumbnailLayout.videoOverlay.visibility = View.VISIBLE
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListThumbnailLoader.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListThumbnailLoader.kt
new file mode 100644
index 000000000000..0aad2909aa64
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListThumbnailLoader.kt
@@ -0,0 +1,156 @@
+/*
+ * IONOS HiDrive Next - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 STRATO GmbH.
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+package com.owncloud.android.ui.adapter
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.os.AsyncTask
+import android.view.View
+import android.widget.ImageView
+import com.elyeproj.loaderviewlibrary.LoaderImageView
+import com.nextcloud.client.account.User
+import com.nextcloud.client.preferences.AppPreferences
+import com.owncloud.android.R
+import com.owncloud.android.datamodel.FileDataStorageManager
+import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.datamodel.SyncedFolderProvider
+import com.owncloud.android.datamodel.ThumbnailsCacheManager
+import com.owncloud.android.datamodel.ThumbnailsCacheManager.AsyncThumbnailDrawable
+import com.owncloud.android.datamodel.ThumbnailsCacheManager.ThumbnailGenerationTask
+import com.owncloud.android.datamodel.ThumbnailsCacheManager.ThumbnailGenerationTaskObject
+import com.owncloud.android.lib.common.utils.Log_OC
+import com.owncloud.android.utils.MimeTypeUtil
+import com.owncloud.android.utils.theme.ViewThemeUtils
+
+class OCFileListThumbnailLoader(
+ private val file: OCFile,
+ private val viewHolder: ListViewHolder,
+ private val user: User,
+ private val storageManager: FileDataStorageManager,
+ private val asyncTasks: MutableList,
+ private val gridView: Boolean,
+ private val context: Context,
+ private val preferences: AppPreferences,
+ private val viewThemeUtils: ViewThemeUtils,
+ private val syncedFolderProvider: SyncedFolderProvider?,
+) {
+ private val iconView: ImageView? = viewHolder.itemLayout.findViewById(R.id.icon)
+ private val videoOverlay: ImageView? = viewHolder.itemLayout.findViewById(R.id.videoOverlay)
+ private val thumbnailView: ImageView = viewHolder.thumbnail
+ private val shimmerView: LoaderImageView = viewHolder.shimmerThumbnail
+
+ fun load() {
+ if (file.isFolder) {
+ showFolderIcon()
+ } else if (!file.isPreviewAvailable || file.remoteId == null) {
+ showFileIcon()
+ } else {
+ loadFromCacheOrRemote()
+ }
+ }
+
+ private fun loadFromCacheOrRemote() {
+ val cacheKey = ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.remoteId
+ val cachedThumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(cacheKey)
+ if (cachedThumbnail == null || file.isUpdateThumbnailNeeded) {
+ loadFromRemote()
+ } else {
+ showThumbnail(cachedThumbnail)
+ }
+ }
+
+ private fun loadFromRemote() {
+ if (!ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, thumbnailView)) {
+ return
+ }
+
+ showShimmer()
+
+ try {
+ val task = ThumbnailGenerationTask(
+ thumbnailView,
+ storageManager,
+ user,
+ asyncTasks,
+ gridView,
+ file.remoteId,
+ )
+
+ task.setListener(object : ThumbnailGenerationTask.Listener {
+ override fun onSuccess() {
+ showExistedThumbnail()
+ }
+
+ override fun onError() {
+ showFileIcon()
+ }
+ })
+
+ val px = ThumbnailsCacheManager.getThumbnailDimension()
+ val tempBitmap = Bitmap.createBitmap(px, px, Bitmap.Config.RGB_565)
+ val asyncDrawable = AsyncThumbnailDrawable(context.resources, tempBitmap, task)
+ thumbnailView.setImageDrawable(asyncDrawable)
+
+ asyncTasks.add(task)
+ val taskObject = ThumbnailGenerationTaskObject(file, file.remoteId)
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, taskObject)
+ } catch (e: Exception) {
+ Log_OC.d(this::class.simpleName, "ThumbnailGenerationTask : " + e.message)
+ }
+ }
+
+ private fun showFolderIcon() {
+ val isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, user)
+ val isDarkModeActive = preferences.isDarkModeEnabled
+ val overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder)
+ val fileIcon = MimeTypeUtil.getFolderIcon(isDarkModeActive, overlayIconId, context, viewThemeUtils)
+ showIcon(fileIcon)
+ }
+
+ private fun showFileIcon() {
+ val fileIcon = MimeTypeUtil.getFileTypeIcon(file.mimeType, file.fileName, context, viewThemeUtils)
+ showIcon(fileIcon)
+ }
+
+ private fun showIcon(icon: Drawable) {
+ if (iconView != null) {
+ iconView.setImageDrawable(icon)
+ iconView.visibility = View.VISIBLE
+ thumbnailView.visibility = View.GONE
+ shimmerView.visibility = View.GONE
+ videoOverlay?.visibility = View.GONE
+ } else {
+ thumbnailView.setImageDrawable(icon)
+ thumbnailView.visibility = View.VISIBLE
+ shimmerView.visibility = View.GONE
+ videoOverlay?.visibility = View.GONE
+ }
+ }
+
+ private fun showThumbnail(thumbnail: Bitmap) {
+ thumbnailView.setImageBitmap(thumbnail)
+ showExistedThumbnail()
+ }
+
+ private fun showExistedThumbnail() {
+ iconView?.visibility = View.GONE
+ thumbnailView.visibility = View.VISIBLE
+ shimmerView.visibility = View.GONE
+ videoOverlay?.visibility = if (MimeTypeUtil.isVideo(file)) View.VISIBLE else View.GONE
+ }
+
+ private fun showShimmer() {
+ shimmerView.setImageResource(R.drawable.background)
+ shimmerView.resetLoader()
+ iconView?.visibility = View.GONE
+ thumbnailView.visibility = View.GONE
+ shimmerView.visibility = View.VISIBLE
+ videoOverlay?.visibility = View.GONE
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCShareToOCFileConverter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCShareToOCFileConverter.kt
index 186e628e7f8a..e05766a1a5da 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/OCShareToOCFileConverter.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCShareToOCFileConverter.kt
@@ -7,6 +7,7 @@
*/
package com.owncloud.android.ui.adapter
+import com.ionos.annotation.IonosCustomization
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.resources.shares.OCShare
import com.owncloud.android.lib.resources.shares.ShareType
@@ -36,6 +37,7 @@ object OCShareToOCFileConverter {
.sortedByDescending { it.firstShareTimestamp }
}
+ @IonosCustomization("Set share fileSource as file localId")
private fun buildOcFile(path: String, shares: List): OCFile {
require(shares.all { it.path == path })
// common attributes
@@ -49,6 +51,7 @@ object OCShareToOCFileConverter {
note = firstShare.note
fileId = firstShare.fileSource
remoteId = firstShare.remoteId.toString()
+ localId = firstShare.fileSource
// use first share timestamp as timestamp
firstShareTimestamp = shares.minOf { it.sharedDate * MILLIS_PER_SECOND }
// don't have file length or mod timestamp
@@ -63,7 +66,13 @@ object OCShareToOCFileConverter {
file.isSharedWithSharee = true
file.sharees = shares
.filter { it.shareType != ShareType.PUBLIC_LINK && it.shareType != ShareType.EMAIL }
- .map { ShareeUser(it.shareWith, it.sharedWithDisplayName, it.shareType) }
+ .map {
+ ShareeUser(
+ userId = it.userId,
+ displayName = it.sharedWithDisplayName,
+ shareType = it.shareType
+ )
+ }
}
return file
}
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/QuickSharingPermissionsAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/QuickSharingPermissionsAdapter.kt
index 64ffa0350002..1041e5a018c9 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/QuickSharingPermissionsAdapter.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/QuickSharingPermissionsAdapter.kt
@@ -14,6 +14,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
+import com.ionos.annotation.IonosCustomization
import com.owncloud.android.databinding.ItemQuickSharePermissionsBinding
import com.owncloud.android.datamodel.QuickPermissionModel
import com.owncloud.android.utils.theme.ViewThemeUtils
@@ -48,10 +49,10 @@ class QuickSharingPermissionsAdapter(
RecyclerView
.ViewHolder(itemView) {
+ @IonosCustomization("Disable icon tinting")
fun bindData(quickPermissionModel: QuickPermissionModel) {
binding.tvQuickShareName.text = quickPermissionModel.permissionName
if (quickPermissionModel.isSelected) {
- viewThemeUtils.platform.colorImageView(binding.tvQuickShareCheckIcon)
binding.tvQuickShareCheckIcon.visibility = View.VISIBLE
} else {
binding.tvQuickShareCheckIcon.visibility = View.INVISIBLE
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ShareViewHolder.java b/app/src/main/java/com/owncloud/android/ui/adapter/ShareViewHolder.java
index 4c9b95471f29..19875097f38b 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/ShareViewHolder.java
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/ShareViewHolder.java
@@ -18,6 +18,8 @@
import android.view.View;
import android.widget.ImageView;
+import com.ionos.annotation.IonosCustomization;
+import com.ionos.utils.IonosBuildHelper;
import com.nextcloud.client.account.User;
import com.owncloud.android.R;
import com.owncloud.android.databinding.FileDetailsShareShareItemBinding;
@@ -126,7 +128,12 @@ private void setPermissionName(String permissionName) {
}
}
+ @IonosCustomization
private void setImage(ImageView avatar, String name, @DrawableRes int fallback) {
+ if (IonosBuildHelper.isIonosBuild()) {
+ avatar.setImageResource(R.drawable.account_circle_white);
+ return;
+ }
try {
avatar.setImageDrawable(TextDrawable.createNamedAvatar(name, avatarRadiusDimension));
} catch (StringIndexOutOfBoundsException e) {
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.java
index 2b22b72050b9..afeba8ec43cf 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.java
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.java
@@ -17,6 +17,8 @@
import android.view.ViewGroup;
import android.widget.ImageView;
+import com.ionos.annotation.IonosCustomization;
+import com.ionos.utils.IonosBuildHelper;
import com.nextcloud.client.account.User;
import com.nextcloud.utils.mdm.MDMConfig;
import com.owncloud.android.R;
@@ -226,6 +228,7 @@ public void remove(OCShare share) {
/**
* sort all by creation time, then email/link shares on top
*/
+ @IonosCustomization("Hide internal share link")
protected final void sortShares() {
List links = new ArrayList<>();
List users = new ArrayList<>();
@@ -245,7 +248,7 @@ protected final void sortShares() {
shares.addAll(users);
// add internal share link at end
- if (!encrypted && sharesType == SharesType.INTERNAL) {
+ if (!IonosBuildHelper.isIonosBuild() && !encrypted) {
final OCShare ocShare = new OCShare();
ocShare.setShareType(ShareType.INTERNAL);
shares.add(ocShare);
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.kt
index 215f23f1f4b3..ccefb3d9ac33 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.kt
@@ -18,6 +18,7 @@ import android.widget.PopupMenu
import androidx.annotation.VisibleForTesting
import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter
import com.afollestad.sectionedrecyclerview.SectionedViewHolder
+import com.ionos.annotation.IonosCustomization
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.client.core.Clock
import com.owncloud.android.R
@@ -437,11 +438,10 @@ class SyncedFolderAdapter(
binding.root
)
+ @IonosCustomization
private fun setSyncButtonActiveIcon(syncStatusButton: ImageButton, enabled: Boolean) {
if (enabled) {
- syncStatusButton.setImageDrawable(
- viewThemeUtils.platform.tintDrawable(context, R.drawable.ic_cloud_sync_on, ColorRole.PRIMARY)
- )
+ syncStatusButton.setImageResource(R.drawable.ic_cloud_sync_on)
} else {
syncStatusButton.setImageResource(R.drawable.ic_cloud_sync_off)
}
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/TrashbinListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/TrashbinListAdapter.java
index edf604df5f09..c4fd6452a2fe 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/TrashbinListAdapter.java
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/TrashbinListAdapter.java
@@ -12,11 +12,13 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
+import com.ionos.annotation.IonosCustomization;
import com.nextcloud.android.common.ui.theme.utils.ColorRole;
import com.nextcloud.client.account.User;
import com.nextcloud.client.preferences.AppPreferences;
@@ -30,6 +32,7 @@
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.trashbin.model.TrashbinFile;
import com.owncloud.android.ui.interfaces.TrashbinActivityInterface;
+import com.owncloud.android.utils.BitmapUtils;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.FileSortOrder;
import com.owncloud.android.utils.MimeTypeUtil;
@@ -117,6 +120,7 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int
}
}
+ @IonosCustomization("Checkbox style")
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder instanceof TrashbinFileViewHolder) {
@@ -153,8 +157,7 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi
// checkbox
if (isCheckedFile(file)) {
- trashbinFileViewHolder.binding.customCheckbox.setImageDrawable(
- viewThemeUtils.platform.tintDrawable(context, R.drawable.ic_checkbox_marked, ColorRole.PRIMARY));
+ trashbinFileViewHolder.binding.customCheckbox.setImageResource(R.drawable.ic_checkbox_marked);
} else {
trashbinFileViewHolder.binding.customCheckbox.setImageResource(R.drawable.ic_checkbox_blank_outline);
}
@@ -249,6 +252,7 @@ private String generateFooterText(int filesCount, int foldersCount) {
return output;
}
+ @IonosCustomization("Async thumbnail fix")
private void setThumbnail(TrashbinFile file, ImageView thumbnailView) {
if (file.isFolder()) {
thumbnailView.setImageDrawable(MimeTypeUtil.getDefaultFolderIcon(context, viewThemeUtils));
@@ -275,6 +279,12 @@ private void setThumbnail(TrashbinFile file, ImageView thumbnailView) {
// generate new thumbnail
if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, thumbnailView)) {
try {
+ Drawable drawable = thumbnailView.getDrawable();
+ if (drawable != null) {
+ int px = ThumbnailsCacheManager.getThumbnailDimension();
+ thumbnail = BitmapUtils.drawableToBitmap(drawable, px, px);
+ }
+
final ThumbnailsCacheManager.ThumbnailGenerationTask task =
new ThumbnailsCacheManager.ThumbnailGenerationTask(thumbnailView,
storageManager,
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchItemViewHolder.kt
index 69054c3ed240..17aa066716c2 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchItemViewHolder.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchItemViewHolder.kt
@@ -11,18 +11,20 @@ import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.view.View
+import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import com.afollestad.sectionedrecyclerview.SectionedViewHolder
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
-import com.nextcloud.android.common.ui.theme.utils.ColorRole
+import com.ionos.annotation.IonosCustomization
import com.nextcloud.client.account.User
import com.nextcloud.client.network.ClientFactory
import com.nextcloud.model.SearchResultEntryType
import com.nextcloud.utils.CalendarEventManager
import com.nextcloud.utils.ContactManager
import com.nextcloud.utils.extensions.getType
+import com.owncloud.android.R
import com.owncloud.android.databinding.UnifiedSearchItemBinding
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.lib.common.SearchResultEntry
@@ -110,6 +112,7 @@ class UnifiedSearchItemViewHolder(
}
}
+ @IonosCustomization("Custom drawable tinting. Use mimetype icon for unknown entry types.")
private fun getPlaceholder(
entry: SearchResultEntry,
entryType: SearchResultEntryType,
@@ -120,8 +123,14 @@ class UnifiedSearchItemViewHolder(
}
val defaultDrawable = MimeTypeUtil.getFileTypeIcon(mimetype, entry.title, context, viewThemeUtils)
+
+ if (entryType == SearchResultEntryType.Unknown && defaultDrawable != null) {
+ return defaultDrawable
+ }
+
val drawable: Drawable = ResourcesCompat.getDrawable(context.resources, iconId, null) ?: defaultDrawable
- return viewThemeUtils.platform.tintDrawable(context, drawable, ColorRole.PRIMARY)
+ val color = ContextCompat.getColor(context, R.color.filelist_file_icon_color)
+ return viewThemeUtils.platform.colorDrawable(drawable, color)
}
private inner class RoundIfNeededListener(private val entry: SearchResultEntry) :
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java
index 1f2c7144a813..301e83229e1c 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java
@@ -20,6 +20,7 @@
import android.view.ViewGroup;
import android.widget.ImageView;
+import com.ionos.annotation.IonosCustomization;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.utils.mdm.MDMConfig;
@@ -242,7 +243,9 @@ public void bind(User user,
*
* @param user the account
*/
+ @IonosCustomization("Hide account id")
private void setUser(User user) {
+ binding.account.setVisibility(View.GONE);
binding.account.setText(DisplayUtils.convertIdn(user.getAccountName(), false));
binding.account.setTag(user.getAccountName());
}
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/AccountRemovalDialog.kt b/app/src/main/java/com/owncloud/android/ui/dialog/AccountRemovalDialog.kt
index e42f1b7c626f..e6ecf82110dc 100644
--- a/app/src/main/java/com/owncloud/android/ui/dialog/AccountRemovalDialog.kt
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/AccountRemovalDialog.kt
@@ -16,6 +16,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.ionos.annotation.IonosCustomization
import com.nextcloud.client.account.User
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.client.di.Injectable
@@ -47,6 +48,7 @@ class AccountRemovalDialog : DialogFragment(), AvatarGenerationListener, Injecta
user = requireArguments().getParcelableArgument(KEY_USER, User::class.java)
}
+ @IonosCustomization("Hide account name")
override fun onStart() {
super.onStart()
@@ -65,6 +67,7 @@ class AccountRemovalDialog : DialogFragment(), AvatarGenerationListener, Injecta
binding.userName.text = UserAccountManager.getDisplayName(user)
binding.account.text = user?.let { DisplayUtils.convertIdn(it.accountName, false) }
+ binding.account.visibility = View.GONE
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/LoadingDialog.kt b/app/src/main/java/com/owncloud/android/ui/dialog/LoadingDialog.kt
index ed179977f7af..c441304a9b62 100644
--- a/app/src/main/java/com/owncloud/android/ui/dialog/LoadingDialog.kt
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/LoadingDialog.kt
@@ -8,13 +8,13 @@
*/
package com.owncloud.android.ui.dialog
+import android.app.Dialog
import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
-import com.nextcloud.android.common.ui.theme.utils.ColorRole
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.ionos.annotation.IonosCustomization
import com.nextcloud.client.di.Injectable
+import com.owncloud.android.R
import com.owncloud.android.databinding.LoadingDialogBinding
import com.owncloud.android.utils.theme.ViewThemeUtils
import javax.inject.Inject
@@ -35,18 +35,13 @@ class LoadingDialog : DialogFragment(), Injectable {
isCancelable = false
}
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
- binding = LoadingDialogBinding.inflate(inflater, container, false)
+ @IonosCustomization
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ binding = LoadingDialogBinding.inflate(layoutInflater)
binding.loadingText.text = mMessage
-
- val loadingDrawable = binding.loadingBar.indeterminateDrawable
- if (loadingDrawable != null) {
- viewThemeUtils?.platform?.tintDrawable(requireContext(), loadingDrawable)
- }
-
- viewThemeUtils?.platform?.colorViewBackground(binding.loadingLayout, ColorRole.SURFACE)
-
- return binding.root
+ return MaterialAlertDialogBuilder(requireContext(), R.style.Theme_ownCloud_LoadingDialog)
+ .setView(binding.root)
+ .create()
}
override fun onDestroyView() {
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/SortingOrderDialogFragment.kt b/app/src/main/java/com/owncloud/android/ui/dialog/SortingOrderDialogFragment.kt
index 8a593cabf541..5d12b5695c79 100644
--- a/app/src/main/java/com/owncloud/android/ui/dialog/SortingOrderDialogFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/SortingOrderDialogFragment.kt
@@ -14,6 +14,7 @@ import android.os.Bundle
import android.view.View
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.ionos.annotation.IonosCustomization
import com.nextcloud.client.di.Injectable
import com.owncloud.android.R
import com.owncloud.android.databinding.SortingOrderFragmentBinding
@@ -49,6 +50,7 @@ class SortingOrderDialogFragment : DialogFragment(), Injectable {
*
* @param binding the parent binding
*/
+ @IonosCustomization("Remove material buttons styling")
private fun setupDialogElements(binding: SortingOrderFragmentBinding) {
val bindings = listOf(
binding.sortByNameAscending to FileSortOrder.SORT_A_TO_Z,
@@ -63,11 +65,9 @@ class SortingOrderDialogFragment : DialogFragment(), Injectable {
view.tag = sortOrder
view.let {
it.setOnClickListener(OnSortOrderClickListener())
- viewThemeUtils?.material?.colorMaterialButtonPrimaryBorderless(it)
}
}
- viewThemeUtils?.material?.colorMaterialButtonPrimaryTonal(binding.cancel)
binding.cancel.setOnClickListener { dismiss() }
}
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/StoragePermissionDialogFragment.kt b/app/src/main/java/com/owncloud/android/ui/dialog/StoragePermissionDialogFragment.kt
index 220f28edd680..82fc80031827 100644
--- a/app/src/main/java/com/owncloud/android/ui/dialog/StoragePermissionDialogFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/StoragePermissionDialogFragment.kt
@@ -17,6 +17,7 @@ import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.ionos.annotation.IonosCustomization
import com.nextcloud.client.di.Injectable
import com.owncloud.android.R
import com.owncloud.android.utils.theme.ViewThemeUtils
@@ -59,29 +60,26 @@ class StoragePermissionDialogFragment : DialogFragment(), Injectable {
}
}
+ @IonosCustomization
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val title = when {
- permissionRequired -> R.string.file_management_permission
- else -> R.string.file_management_permission_optional
+ permissionRequired -> R.string.ionos_file_management_permission
+ else -> R.string.ionos_file_management_permission_optional
}
val explanationResource = when {
- permissionRequired -> R.string.file_management_permission_text
- else -> R.string.file_management_permission_optional_text
+ permissionRequired -> R.string.ionos_file_management_permission_text
+ else -> R.string.ionos_file_management_permission_optional_text
}
val message = getString(explanationResource, getString(R.string.app_name))
- val dialogBuilder = MaterialAlertDialogBuilder(requireContext())
+ val dialogBuilder = MaterialAlertDialogBuilder(requireContext(), R.style.Theme_ownCloud_Dialog)
.setTitle(title)
.setMessage(message)
- .setPositiveButton(R.string.storage_permission_full_access) { _, _ ->
+ .setPositiveButton(R.string.permission_allow) { _, _ ->
setResult(Result.FULL_ACCESS)
dismiss()
}
- .setNegativeButton(R.string.storage_permission_media_read_only) { _, _ ->
- setResult(Result.MEDIA_READ_ONLY)
- dismiss()
- }
- .setNeutralButton(R.string.common_cancel) { _, _ ->
+ .setNegativeButton(R.string.permission_deny) { _, _ ->
setResult(Result.CANCEL)
dismiss()
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java
index f38898d8da2e..7def1d54c5a6 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java
@@ -41,6 +41,8 @@
import android.widget.TextView;
import com.google.android.material.button.MaterialButton;
+import com.ionos.annotation.IonosCustomization;
+import com.ionos.utils.IonosBuildHelper;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.preferences.AppPreferences;
@@ -371,9 +373,14 @@ public void onDestroyView() {
binding = null;
}
+ @IonosCustomization
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
+ if (IonosBuildHelper.isIonosBuild()) {
+ return true;
+ }
+
setGridViewColumns(detector.getScaleFactor());
preferences.setGridColumns(mScale);
@@ -601,6 +608,7 @@ public void setMessageForEmptyList(@StringRes final int headline, @StringRes fin
});
}
+ @IonosCustomization
public void setEmptyListMessage(final SearchType searchType) {
new Handler(Looper.getMainLooper()).post(() -> {
if (searchType == SearchType.OFFLINE_MODE) {
@@ -611,16 +619,15 @@ public void setEmptyListMessage(final SearchType searchType) {
} else if (searchType == SearchType.NO_SEARCH) {
setMessageForEmptyList(R.string.file_list_empty_headline,
R.string.file_list_empty,
- R.drawable.ic_list_empty_folder,
- true);
+ R.drawable.ic_list_empty_folder);
} else if (searchType == SearchType.FILE_SEARCH) {
setMessageForEmptyList(R.string.file_list_empty_headline_server_search,
R.string.file_list_empty,
- R.drawable.ic_search_light_grey);
+ R.drawable.ic_search);
} else if (searchType == SearchType.FAVORITE_SEARCH) {
setMessageForEmptyList(R.string.file_list_empty_favorite_headline,
R.string.file_list_empty_favorites_filter_list,
- R.drawable.ic_star_light_yellow);
+ R.drawable.favorite);
} else if (searchType == SearchType.RECENTLY_MODIFIED_SEARCH) {
setMessageForEmptyList(R.string.file_list_empty_headline_server_search,
R.string.file_list_empty_recently_modified,
@@ -628,7 +635,7 @@ public void setEmptyListMessage(final SearchType searchType) {
} else if (searchType == SearchType.REGULAR_FILTER) {
setMessageForEmptyList(R.string.file_list_empty_headline_search,
R.string.file_list_empty_search,
- R.drawable.ic_search_light_grey);
+ R.drawable.ic_search);
} else if (searchType == SearchType.SHARED_FILTER) {
setMessageForEmptyList(R.string.file_list_empty_shared_headline,
R.string.file_list_empty_shared,
@@ -640,7 +647,7 @@ public void setEmptyListMessage(final SearchType searchType) {
} else if (searchType == SearchType.LOCAL_SEARCH) {
setMessageForEmptyList(R.string.file_list_empty_headline_server_search,
R.string.file_list_empty_local_search,
- R.drawable.ic_search_light_grey);
+ R.drawable.ic_search);
}
});
}
@@ -684,8 +691,14 @@ public void onRefresh(boolean ignoreETag) {
}
@Override
+ @IonosCustomization
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
+ if (IonosBuildHelper.isIonosBuild()) {
+ mScale = preferences.getGridColumns();
+ setGridViewColumns(1f);
+ return;
+ }
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
maxColumnSize = 10;
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
index 1c20e2e58a94..f328808b4117 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
@@ -24,6 +24,8 @@
import com.google.android.material.chip.Chip;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
+import com.ionos.annotation.IonosCustomization;
+import com.ionos.utils.IonosBuildHelper;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.di.Injectable;
@@ -321,7 +323,15 @@ private void onOverflowIconClicked() {
.show(fragmentManager, "actions");
}
+ @IonosCustomization("Hide tabs in IONOS")
private void setupViewPager() {
+ if (IonosBuildHelper.isIonosBuild()) {
+ FileDetailTabAdapter adapter = new FileDetailTabAdapter(requireActivity(), getFile(), user, showSharingTab());
+ binding.pager.setAdapter(adapter);
+ binding.tabLayout.setVisibility(View.GONE);
+ return;
+ }
+
binding.tabLayout.removeAllTabs();
binding.tabLayout.addTab(binding.tabLayout.newTab().setText(R.string.drawer_item_activities).setIcon(R.drawable.ic_activity));
@@ -352,19 +362,7 @@ public void onPageScrolled(int position, float positionOffset, int positionOffse
}
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
}
-
- @Override
- public void onPageSelected(int position) {
- super.onPageSelected(position);
- if (binding != null) {
- final var tab = binding.tabLayout.getTabAt(position);
- if (tab != null) {
- tab.select();
- }
- }
- }
});
-
binding.tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
@@ -389,11 +387,9 @@ public void onTabReselected(TabLayout.Tab tab) {
});
binding.tabLayout.post(() -> {
- if (binding != null) {
- TabLayout.Tab tab = binding.tabLayout.getTabAt(activeTab);
- if (tab == null) return;
- tab.select();
- }
+ TabLayout.Tab tab1 = binding.tabLayout.getTabAt(activeTab);
+ if (tab1 == null) return;
+ tab1.select();
});
}
@@ -842,8 +838,9 @@ public void initiateSharingProcess(String shareeName,
*
* @param isFragmentReplaced
*/
+ @IonosCustomization("Hide tabs in IONOS")
public void showHideFragmentView(boolean isFragmentReplaced) {
- binding.tabLayout.setVisibility(isFragmentReplaced ? View.GONE : View.VISIBLE);
+ binding.tabLayout.setVisibility(View.GONE);
binding.pager.setVisibility(isFragmentReplaced ? View.GONE : View.VISIBLE);
binding.sharingFrameContainer.setVisibility(isFragmentReplaced ? View.VISIBLE : View.GONE);
FloatingActionButton mFabMain = requireActivity().findViewById(R.id.fab_main);
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java
index 1d6b78a4361a..48566f929b50 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java
@@ -30,7 +30,11 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.LinearLayout;
+import com.ionos.annotation.IonosCustomization;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.di.Injectable;
@@ -59,6 +63,8 @@
import com.owncloud.android.ui.adapter.ShareeListAdapterListener;
import com.owncloud.android.ui.asynctasks.RetrieveHoverCardAsyncTask;
import com.owncloud.android.ui.dialog.SharePasswordDialogFragment;
+import com.owncloud.android.ui.fragment.share.RemoteShareRepository;
+import com.owncloud.android.ui.fragment.share.ShareRepository;
import com.owncloud.android.ui.fragment.util.FileDetailSharingFragmentHelper;
import com.owncloud.android.ui.helpers.FileOperationsHelper;
import com.owncloud.android.utils.ClipboardUtil;
@@ -80,6 +86,7 @@
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import kotlin.Unit;
public class FileDetailSharingFragment extends Fragment implements ShareeListAdapterListener,
DisplayUtils.AvatarGenerationListener,
@@ -148,22 +155,60 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
if (fileActivity == null) {
throw new IllegalArgumentException("FileActivity may not be null");
}
+
+ fileDataStorageManager = fileActivity.getStorageManager();
+ fetchSharees();
}
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
+ private void fetchSharees() {
+ final var activity = fileActivity;
+ if (activity == null) {
+ return;
+ }
- refreshCapabilitiesFromDB();
- refreshSharesFromDB();
+ final var clientRepository = activity.getClientRepository();
+ if (clientRepository == null) {
+ return;
+ }
+
+ final var storageManager = fileDataStorageManager;
+ if (storageManager == null) {
+ return;
+ }
+
+ ShareRepository shareRepository = new RemoteShareRepository(clientRepository, activity, storageManager);
+ shareRepository.fetchSharees(file.getRemotePath(), () -> {
+ refreshCapabilitiesFromDB();
+ refreshSharesFromDB();
+ showShareContainer();
+ return Unit.INSTANCE;
+ }, () -> {
+ showShareContainer();
+ DisplayUtils.showSnackMessage(getView(), R.string.error_fetching_sharees);
+ return Unit.INSTANCE;
+ });
+ }
+
+ private void showShareContainer() {
+ if (binding == null) {
+ return;
+ }
+
+ final LinearLayout shimmerLayout = binding.shimmerLayout.getRoot();
+ shimmerLayout.clearAnimation();
+ shimmerLayout.setVisibility(View.GONE);
+
+ binding.shareContainer.setVisibility(View.VISIBLE);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FileDetailsSharingFragmentBinding.inflate(inflater, container, false);
+ final Animation blinkAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.blink);
+ binding.shimmerLayout.getRoot().startAnimation(blinkAnimation);
+
fileOperationsHelper = fileActivity.getFileOperationsHelper();
- fileDataStorageManager = fileActivity.getStorageManager();
AccountManager accountManager = AccountManager.get(requireContext());
String userId = accountManager.getUserData(user.toPlatformAccount(),
@@ -190,7 +235,9 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
viewThemeUtils,
file.isEncrypted(),
SharesType.EXTERNAL);
-
+
+ externalShareeListAdapter.setHasStableIds(true);
+
binding.sharesListExternal.setAdapter(externalShareeListAdapter);
binding.sharesListExternal.setLayoutManager(new LinearLayoutManager(requireContext()));
@@ -233,6 +280,7 @@ public void onStop() {
searchConfig.reset();
}
+ @IonosCustomization
private void setupView() {
setShareWithYou();
@@ -284,7 +332,7 @@ private void setupView() {
}
} else {
binding.createLink.setText(R.string.create_link);
- binding.searchView.setQueryHint(getResources().getString(R.string.share_search_internal));
+ binding.searchView.setQueryHint(getResources().getString(R.string.ionos_share_search));
}
binding.createLink.setOnClickListener(v -> createPublicShareLink());
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingMenuBottomSheetDialog.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingMenuBottomSheetDialog.java
index be6779b9573a..8e8d2f42fc92 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingMenuBottomSheetDialog.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingMenuBottomSheetDialog.java
@@ -12,10 +12,10 @@
import android.os.Bundle;
import android.view.View;
-import android.view.ViewGroup;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.bottomsheet.BottomSheetDialog;
+import com.ionos.annotation.IonosCustomization;
import com.nextcloud.utils.mdm.MDMConfig;
import com.owncloud.android.databinding.FileDetailsSharingMenuBottomSheetFragmentBinding;
import com.owncloud.android.lib.resources.shares.OCShare;
@@ -43,22 +43,14 @@ public FileDetailSharingMenuBottomSheetDialog(FileActivity fileActivity,
}
@Override
+ @IonosCustomization("Remove custom window LayoutParams. Disable icon tinting")
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = FileDetailsSharingMenuBottomSheetFragmentBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
- if (getWindow() != null) {
- getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
- }
-
viewThemeUtils.platform.themeDialog(binding.getRoot());
- viewThemeUtils.platform.colorImageView(binding.menuIconAdvancedPermissions);
- viewThemeUtils.platform.colorImageView(binding.menuIconSendLink);
- viewThemeUtils.platform.colorImageView(binding.menuIconUnshare);
- viewThemeUtils.platform.colorImageView(binding.menuIconSendNewEmail);
-
updateUI();
setupClickListener();
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.java
index b264078da243..672c2bf84765 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.java
@@ -22,6 +22,7 @@
import android.view.View;
import android.view.ViewGroup;
+import com.ionos.annotation.IonosCustomization;
import com.nextcloud.utils.extensions.IntentExtensionsKt;
import com.owncloud.android.BuildConfig;
import com.owncloud.android.R;
@@ -34,8 +35,7 @@
import com.owncloud.android.ui.activity.FolderPickerActivity;
import com.owncloud.android.ui.activity.ToolbarActivity;
import com.owncloud.android.ui.adapter.CommonOCFileListAdapterInterface;
-import com.owncloud.android.ui.adapter.GalleryAdapter;
-import com.owncloud.android.ui.adapter.OCFileListDelegate;
+import com.owncloud.android.ui.adapter.GallerySimpleAdapter;
import com.owncloud.android.ui.asynctasks.GallerySearchTask;
import com.owncloud.android.ui.events.ChangeMenuEvent;
@@ -61,14 +61,16 @@ public class GalleryFragment extends OCFileListFragment implements GalleryFragme
private AsyncTask photoSearchTask;
private long endDate;
private int limit = 150;
- private GalleryAdapter mAdapter;
+ @IonosCustomization("Custom adapter")
+ private GallerySimpleAdapter mAdapter;
private static final int SELECT_LOCATION_REQUEST_CODE = 212;
private GalleryFragmentBottomSheetDialog galleryFragmentBottomSheetDialog;
@Inject FileDataStorageManager fileDataStorageManager;
private final static int maxColumnSizeLandscape = 5;
- private final static int maxColumnSizePortrait = 2;
+ @IonosCustomization("increased quantity")
+ private final static int maxColumnSizePortrait = 3;
private int columnSize;
protected void setPhotoSearchQueryRunning(boolean value) {
@@ -167,8 +169,9 @@ public void onActivityCreated(Bundle savedInstanceState) {
}
@Override
+ @IonosCustomization("Custom adapter")
protected void setAdapter(Bundle args) {
- mAdapter = new GalleryAdapter(requireContext(),
+ mAdapter = new GallerySimpleAdapter(requireContext(),
accountManager.getUser(),
this,
preferences,
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragmentBottomSheetDialog.kt b/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragmentBottomSheetDialog.kt
index 178c5e6bb3c3..bac6659a5382 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragmentBottomSheetDialog.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragmentBottomSheetDialog.kt
@@ -13,7 +13,10 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import com.ionos.annotation.IonosCustomization
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.client.di.Injectable
import com.owncloud.android.R
@@ -32,6 +35,9 @@ class GalleryFragmentBottomSheetDialog(
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentGalleryBottomSheetBinding.inflate(layoutInflater, container, false)
+ val bottomSheetDialog = dialog as BottomSheetDialog
+ bottomSheetDialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED
+ bottomSheetDialog.behavior.skipCollapsed = true
return binding.root
}
@@ -41,24 +47,10 @@ class GalleryFragmentBottomSheetDialog(
setupClickListener()
}
+ @IonosCustomization
private fun setupLayout() {
viewThemeUtils.platform.colorViewBackground(binding.bottomSheet, ColorRole.SURFACE)
- listOf(
- binding.tickMarkShowImages,
- binding.tickMarkShowVideos
- ).forEach {
- viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY)
- }
-
- listOf(
- binding.btnSelectMediaFolder,
- binding.btnHideVideos,
- binding.btnHideImages
- ).forEach {
- viewThemeUtils.material.colorMaterialButtonText(it)
- }
-
when (currentMediaState) {
MediaState.MEDIA_STATE_PHOTOS_ONLY -> {
binding.tickMarkShowImages.visibility = View.VISIBLE
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/LocalFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/LocalFileListFragment.java
index e82f04e04129..1fccb802dc48 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/LocalFileListFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/LocalFileListFragment.java
@@ -20,6 +20,7 @@
import android.view.View;
import android.view.ViewGroup;
+import com.ionos.annotation.IonosCustomization;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.preferences.AppPreferences;
import com.owncloud.android.R;
@@ -91,16 +92,17 @@ public void onAttach(@NonNull Activity activity) {
* {@inheritDoc}
*/
@Override
+ @IonosCustomization
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log_OC.i(TAG, "onCreateView() start");
View v = super.onCreateView(inflater, container, savedInstanceState);
if (!mContainerActivity.isFolderPickerMode()) {
setMessageForEmptyList(R.string.file_list_empty_headline, R.string.local_file_list_empty,
- R.drawable.ic_list_empty_folder, true);
+ R.drawable.ic_list_empty_folder);
} else {
setMessageForEmptyList(R.string.folder_list_empty_headline, R.string.local_folder_list_empty,
- R.drawable.ic_list_empty_folder, true);
+ R.drawable.ic_list_empty_folder);
}
setSwipeEnabled(false); // Disable pull-to-refresh
@@ -114,6 +116,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
* {@inheritDoc}
*/
@Override
+ @IonosCustomization
public void onActivityCreated(Bundle savedInstanceState) {
Log_OC.i(TAG, "onActivityCreated() start");
@@ -136,7 +139,7 @@ public void onActivityCreated(Bundle savedInstanceState) {
});
FileSortOrder sortOrder = preferences.getSortOrderByType(FileSortOrder.Type.localFileListView);
- mSortButton.setText(DisplayUtils.getSortOrderStringId(sortOrder));
+ mSortButton.setIconResource(DisplayUtils.getSortOrderIconRes(sortOrder));
setGridSwitchButton();
mSwitchGridViewButton.setOnClickListener(v -> {
@@ -303,8 +306,9 @@ public int getFilesCount() {
return mAdapter.getFilesCount();
}
+ @IonosCustomization
public void sortFiles(FileSortOrder sortOrder) {
- mSortButton.setText(DisplayUtils.getSortOrderStringId(sortOrder));
+ mSortButton.setIconResource(DisplayUtils.getSortOrderIconRes(sortOrder));
mAdapter.setSortOrder(sortOrder);
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
index 556cbbcc6637..853f52350062 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
@@ -41,15 +41,15 @@
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
+import com.ionos.annotation.IonosCustomization;
+import com.ionos.scanbot.controller.ScanbotController;
import com.nextcloud.android.lib.resources.files.ToggleFileLockRemoteOperation;
-import com.nextcloud.android.lib.resources.recommendations.GetRecommendationsRemoteOperation;
import com.nextcloud.android.lib.richWorkspace.RichWorkspaceDirectEditingRemoteOperation;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.device.DeviceInfo;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.documentscan.AppScanOptionalFeature;
-import com.nextcloud.client.documentscan.DocumentScanActivity;
import com.nextcloud.client.editimage.EditImageActivity;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.network.ClientFactory;
@@ -74,7 +74,6 @@
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
import com.owncloud.android.lib.common.Creator;
import com.owncloud.android.lib.common.OwnCloudClient;
-import com.owncloud.android.lib.common.OwnCloudClientFactory;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
@@ -153,6 +152,7 @@
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import kotlin.Unit;
import static com.owncloud.android.datamodel.OCFile.ROOT_PATH;
import static com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment.SETUP_ENCRYPTION_DIALOG_TAG;
@@ -219,6 +219,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
@Inject ShortcutUtil shortcutUtil;
@Inject SyncedFolderProvider syncedFolderProvider;
@Inject AppScanOptionalFeature appScanOptionalFeature;
+ @Inject ScanbotController scanbotController;
protected FileFragment.ContainerActivity mContainerActivity;
@@ -283,6 +284,10 @@ public void onResume() {
handleSearchEvent(searchEvent);
}
+ if (getActivity() instanceof FileDisplayActivity fda) {
+ fda.startSyncFolderOperation(getCurrentFile(), true);
+ }
+
super.onResume();
}
@@ -314,6 +319,7 @@ public void onAttach(@NonNull Context context) {
* {@inheritDoc}
*/
@Override
+ @IonosCustomization
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log_OC.i(TAG, "onCreateView() start");
View v = super.onCreateView(inflater, container, savedInstanceState);
@@ -337,11 +343,6 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
mFabMain = requireActivity().findViewById(R.id.fab_main);
- if (mFabMain != null) {
- // is not available in FolderPickerActivity
- viewThemeUtils.material.themeFAB(mFabMain);
- }
-
Log_OC.i(TAG, "onCreateView() end");
return v;
}
@@ -434,7 +435,6 @@ public void onActivityCreated(Bundle savedInstanceState) {
listDirectory(MainApp.isOnlyOnDevice(), false);
}
- // TODO - This can be replaced via separate class
public void fetchRecommendedFiles() {
OCFile folder = getCurrentFile();
@@ -442,26 +442,12 @@ public void fetchRecommendedFiles() {
return;
}
- new Thread(() -> {{
- try {
- User user = accountManager.getUser();
- final var client = OwnCloudClientFactory.createNextcloudClient(user.toPlatformAccount(), requireActivity());
- final var result = new GetRecommendationsRemoteOperation().execute(client);
- if (result.isSuccess()) {
- final var recommendations = result.getResultData().getRecommendations();
- Log_OC.d(TAG,"Recommended files fetched size: " + recommendations.size());
- requireActivity().runOnUiThread(new Runnable() {
- @SuppressLint("NotifyDataSetChanged")
- @Override
- public void run() {
- mAdapter.updateRecommendedFiles(recommendations);
- }
- });
- }
- } catch (Exception e) {
- Log_OC.d(TAG,"Exception fetchRecommendedFiles: " + e);
- }
- }}).start();
+ if (getActivity() instanceof FileActivity fileActivity) {
+ fileActivity.getFilesRepository().fetchRecommendedFiles(recommendations -> {
+ mAdapter.updateRecommendedFiles(recommendations);
+ return Unit.INSTANCE;
+ });
+ }
}
protected void setAdapter(Bundle args) {
@@ -516,24 +502,21 @@ protected void prepareCurrentSearch(SearchEvent event) {
/**
* register listener on FAB.
*/
+ @IonosCustomization("Show simple FAB menu bottom sheet")
public void registerFabListener() {
FileActivity activity = (FileActivity) getActivity();
if (mFabMain != null) {
// is not available in FolderPickerActivity
- viewThemeUtils.material.themeFAB(mFabMain);
mFabMain.setOnClickListener(v -> {
PermissionUtil.requestMediaLocationPermission(activity);
- final OCFileListBottomSheetDialog dialog =
- new OCFileListBottomSheetDialog(activity,
+ final SimpleOCFileListBottomSheetDialog dialog =
+ new SimpleOCFileListBottomSheetDialog(activity,
this,
deviceInfo,
- accountManager.getUser(),
- getCurrentFile(),
themeUtils,
viewThemeUtils,
- editorUtils,
appScanOptionalFeature);
dialog.getBehavior().setState(BottomSheetBehavior.STATE_EXPANDED);
@@ -598,9 +581,7 @@ public void scanDocUpload() {
final OCFile currentFile = getCurrentFile();
if (fileDisplayActivity != null && currentFile != null && currentFile.isFolder()) {
- Intent intent = new Intent(requireContext(), DocumentScanActivity.class);
- intent.putExtra(DocumentScanActivity.EXTRA_FOLDER, currentFile.getRemotePath());
- startActivity(intent);
+ scanbotController.scanToDocument(requireContext(), currentFile.getRemotePath());
} else {
Log.w(TAG, "scanDocUpload: Failed to start doc scanning, fileDisplayActivity=" + fileDisplayActivity +
", currentFile=" + currentFile);
@@ -814,6 +795,7 @@ public void onItemCheckedStateChanged(ActionMode mode, int position, long id, bo
* Load menu and customize UI when action mode is started.
*/
@Override
+ @IonosCustomization
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mActiveActionMode = mode;
// Determine if actionMode is "new" or not (already affected by item-selection)
@@ -822,8 +804,6 @@ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// fake menu to be able to use bottom sheet instead
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.custom_menu_placeholder, menu);
- final MenuItem item = menu.findItem(R.id.custom_menu_placeholder_item);
- item.setIcon(viewThemeUtils.platform.colorDrawable(item.getIcon(), ContextCompat.getColor(requireContext(), R.color.white)));
mode.invalidate();
//set actionMode color
@@ -1495,6 +1475,11 @@ public OCFile getCurrentFile() {
return mFile;
}
+ @IonosCustomization
+ public SearchType getCurrentSearchType() {
+ return currentSearchType;
+ }
+
/**
* Calls {@link OCFileListFragment#listDirectory(OCFile, boolean, boolean)} with a null parameter
*/
@@ -1604,6 +1589,7 @@ public void updateOCFile(@NonNull OCFile file) {
mAdapter.notifyItemChanged(file);
}
+ @IonosCustomization
private void updateLayout() {
// decide grid vs list view
if (isGridViewPreferred(mFile)) {
@@ -1613,7 +1599,7 @@ private void updateLayout() {
}
if (mSortButton != null) {
- mSortButton.setText(DisplayUtils.getSortOrderStringId(preferences.getSortOrderByFolder(mFile)));
+ mSortButton.setIconResource(DisplayUtils.getSortOrderIconRes(preferences.getSortOrderByFolder(mFile)));
}
if (mSwitchGridViewButton != null) {
setGridSwitchButton();
@@ -1638,8 +1624,9 @@ private void invalidateActionMode() {
}
}
+ @IonosCustomization
public void sortFiles(FileSortOrder sortOrder) {
- mSortButton.setText(DisplayUtils.getSortOrderStringId(sortOrder));
+ mSortButton.setIconResource(DisplayUtils.getSortOrderIconRes(sortOrder));
mAdapter.setSortOrder(mFile, sortOrder);
}
@@ -2211,6 +2198,7 @@ public boolean isLoading() {
*
* @param visible Desired visibility for the FAB.
*/
+ @IonosCustomization
public void setFabVisible(final boolean visible) {
if (mFabMain == null) {
// is not available in FolderPickerActivity
@@ -2221,7 +2209,6 @@ public void setFabVisible(final boolean visible) {
getActivity().runOnUiThread(() -> {
if (visible) {
mFabMain.show();
- viewThemeUtils.material.themeFAB(mFabMain);
} else {
mFabMain.hide();
}
@@ -2261,6 +2248,7 @@ private void showFabWithBehavior(boolean visible) {
*
* @param enabled Desired visibility for the FAB.
*/
+ @IonosCustomization
public void setFabEnabled(final boolean enabled) {
if (mFabMain == null) {
// is not available in FolderPickerActivity
@@ -2271,10 +2259,8 @@ public void setFabEnabled(final boolean enabled) {
getActivity().runOnUiThread(() -> {
if (enabled) {
mFabMain.setEnabled(true);
- viewThemeUtils.material.themeFAB(mFabMain);
} else {
mFabMain.setEnabled(false);
- viewThemeUtils.material.themeFAB(mFabMain);
}
});
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/QuickSharingPermissionsBottomSheetDialog.java b/app/src/main/java/com/owncloud/android/ui/fragment/QuickSharingPermissionsBottomSheetDialog.java
index 820a6b0a7f22..0702dec9f14c 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/QuickSharingPermissionsBottomSheetDialog.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/QuickSharingPermissionsBottomSheetDialog.java
@@ -12,10 +12,10 @@
import android.os.Bundle;
import android.view.View;
-import android.view.ViewGroup;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.bottomsheet.BottomSheetDialog;
+import com.ionos.annotation.IonosCustomization;
import com.owncloud.android.R;
import com.owncloud.android.databinding.QuickSharingPermissionsBottomSheetFragmentBinding;
import com.owncloud.android.datamodel.QuickPermissionModel;
@@ -57,15 +57,12 @@ public QuickSharingPermissionsBottomSheetDialog(FileActivity fileActivity,
}
@Override
+ @IonosCustomization("Remove custom window LayoutParams")
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = QuickSharingPermissionsBottomSheetFragmentBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
- if (getWindow() != null) {
- getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
- }
-
viewThemeUtils.platform.themeDialog(binding.getRoot());
setUpRecyclerView();
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/SimpleOCFileListBottomSheetDialog.java b/app/src/main/java/com/owncloud/android/ui/fragment/SimpleOCFileListBottomSheetDialog.java
new file mode 100644
index 000000000000..292402ff8d22
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/SimpleOCFileListBottomSheetDialog.java
@@ -0,0 +1,106 @@
+/*
+ * IONOS HiDrive Next - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 STRATO GmbH.
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+package com.owncloud.android.ui.fragment;
+
+import android.os.Bundle;
+import android.view.View;
+
+import com.google.android.material.bottomsheet.BottomSheetDialog;
+import com.nextcloud.android.common.ui.theme.utils.ColorRole;
+import com.nextcloud.client.device.DeviceInfo;
+import com.nextcloud.client.di.Injectable;
+import com.nextcloud.client.documentscan.AppScanOptionalFeature;
+import com.owncloud.android.R;
+import com.owncloud.android.databinding.SimpleFileListActionsBottomSheetFragmentBinding;
+import com.owncloud.android.ui.activity.FileActivity;
+import com.owncloud.android.utils.theme.ThemeUtils;
+import com.owncloud.android.utils.theme.ViewThemeUtils;
+
+/**
+ * Simple FAB menu {@link android.app.Dialog} styled as a bottom sheet for main actions.
+ */
+public class SimpleOCFileListBottomSheetDialog extends BottomSheetDialog implements Injectable {
+
+ private SimpleFileListActionsBottomSheetFragmentBinding binding;
+ private final OCFileListBottomSheetActions actions;
+ private final DeviceInfo deviceInfo;
+ private final ThemeUtils themeUtils;
+ private final ViewThemeUtils viewThemeUtils;
+
+ private final AppScanOptionalFeature appScanOptionalFeature;
+
+
+ public SimpleOCFileListBottomSheetDialog(FileActivity fileActivity,
+ OCFileListBottomSheetActions actions,
+ DeviceInfo deviceInfo,
+ ThemeUtils themeUtils,
+ ViewThemeUtils viewThemeUtils,
+ AppScanOptionalFeature appScanOptionalFeature) {
+ super(fileActivity);
+ this.actions = actions;
+ this.deviceInfo = deviceInfo;
+ this.themeUtils = themeUtils;
+ this.viewThemeUtils = viewThemeUtils;
+ this.appScanOptionalFeature = appScanOptionalFeature;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ binding = SimpleFileListActionsBottomSheetFragmentBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ viewThemeUtils.platform.colorViewBackground(binding.getRoot(), ColorRole.SURFACE);
+
+ binding.addToCloud.setText(getContext().getResources().getString(R.string.add_to_cloud,
+ themeUtils.getDefaultDisplayNameForRootFolder(getContext())));
+
+ if (!deviceInfo.hasCamera(getContext())) {
+ binding.menuDirectCameraUpload.setVisibility(View.GONE);
+ }
+
+ setupClickListener();
+ }
+
+ private void setupClickListener() {
+ binding.menuMkdir.setOnClickListener(v -> {
+ actions.createFolder();
+ dismiss();
+ });
+
+ binding.menuUploadFromApp.setOnClickListener(v -> {
+ actions.uploadFromApp();
+ dismiss();
+ });
+
+ binding.menuDirectCameraUpload.setOnClickListener(v -> {
+ actions.directCameraUpload();
+ dismiss();
+ });
+
+ if (appScanOptionalFeature.isAvailable()) {
+ binding.menuScanDocUpload.setOnClickListener(v -> {
+ actions.scanDocUpload();
+ dismiss();
+ });
+ } else {
+ binding.menuScanDocUpload.setVisibility(View.GONE);
+ }
+
+ binding.menuUploadFiles.setOnClickListener(v -> {
+ actions.uploadFiles();
+ dismiss();
+ });
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ binding = null;
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/UnifiedSearchFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/UnifiedSearchFragment.kt
index a158c8052948..01970f8e4cff 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/UnifiedSearchFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/UnifiedSearchFragment.kt
@@ -24,6 +24,7 @@ import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
+import com.ionos.annotation.IonosCustomization
import com.nextcloud.client.account.CurrentAccountProvider
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.client.core.AsyncRunner
@@ -140,7 +141,7 @@ class UnifiedSearchFragment :
setupFileDisplayActivity()
setupAdapter()
if (supportsOpeningCalendarContactsLocally()) {
- checkPermissions()
+ // checkPermissions()
}
}
@@ -183,6 +184,7 @@ class UnifiedSearchFragment :
}
}
+ @IonosCustomization
private fun setUpViewModel() {
vm.searchResults.observe(this, this::onSearchResultChanged)
vm.isLoading.observe(this) { loading ->
@@ -208,9 +210,7 @@ class UnifiedSearchFragment :
requireContext().getString(R.string.file_list_empty_headline_server_search)
binding.emptyList.emptyListViewText.text =
requireContext().getString(R.string.file_list_empty_unified_search_no_results)
- binding.emptyList.emptyListIcon.setImageDrawable(
- viewThemeUtils.platform.tintDrawable(requireContext(), R.drawable.ic_search_grey)
- )
+ binding.emptyList.emptyListIcon.setImageResource(R.drawable.ic_search)
}
}
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt
index 52e7d03b695b..231573ae306c 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt
@@ -24,6 +24,7 @@ import android.widget.CompoundButton
import android.widget.DatePicker
import android.widget.Toast
import androidx.core.app.ActivityCompat
+import com.ionos.annotation.IonosCustomization
import com.nextcloud.client.account.User
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.jobs.BackgroundJobManager
@@ -220,16 +221,8 @@ class BackupFragment : FileFragment(), OnDateSetListener, Injectable {
}
}
+ @IonosCustomization
private fun applyUserColor() {
- viewThemeUtils.androidx.colorSwitchCompat(binding.contacts)
- viewThemeUtils.androidx.colorSwitchCompat(binding.calendar)
- viewThemeUtils.androidx.colorSwitchCompat(binding.dailyBackup)
-
- viewThemeUtils.material.colorMaterialButtonPrimaryFilled(binding.backupNow)
- viewThemeUtils.material.colorMaterialButtonPrimaryOutlined(binding.contactsDatepicker)
-
- viewThemeUtils.platform.colorTextView(binding.dataToBackUpTitle)
- viewThemeUtils.platform.colorTextView(binding.backupSettingsTitle)
}
override fun onResume() {
@@ -493,6 +486,7 @@ class BackupFragment : FileFragment(), OnDateSetListener, Injectable {
}
}
+ @IonosCustomization
private fun openDate(savedDate: Calendar?) {
val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
if (contactsPreferenceActivity == null) {
@@ -529,10 +523,6 @@ class BackupFragment : FileFragment(), OnDateSetListener, Injectable {
show()
}
- viewThemeUtils.platform.colorTextButtons(
- datePickerDialog!!.getButton(DatePickerDialog.BUTTON_NEGATIVE),
- datePickerDialog!!.getButton(DatePickerDialog.BUTTON_POSITIVE)
- )
} else {
DisplayUtils.showSnackMessage(
requireView().findViewById(R.id.contacts_linear_layout),
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListAdapter.kt
index efb896dcfad0..4ac06bcd15e3 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListAdapter.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListAdapter.kt
@@ -24,6 +24,7 @@ import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter
import com.afollestad.sectionedrecyclerview.SectionedViewHolder
import com.bumptech.glide.request.animation.GlideAnimation
import com.bumptech.glide.request.target.SimpleTarget
+import com.ionos.annotation.IonosCustomization
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.client.network.ClientFactory
import com.owncloud.android.R
@@ -319,6 +320,7 @@ class BackupListAdapter(
return checkedVCards.toIntArray()
}
+ @IonosCustomization("Crash fix")
fun selectAll(selectAll: Boolean) {
if (selectAll) {
contacts.forEachIndexed { index, _ -> checkedVCards.add(index) }
@@ -326,6 +328,7 @@ class BackupListAdapter(
checkedVCards.clear()
checkedCalendars.clear()
}
+ notifyDataSetChanged()
showRestoreButton()
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/filesRepository/FilesRepository.kt b/app/src/main/java/com/owncloud/android/ui/fragment/filesRepository/FilesRepository.kt
new file mode 100644
index 000000000000..576fa5021525
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/filesRepository/FilesRepository.kt
@@ -0,0 +1,24 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 Alper Ozturk
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.owncloud.android.ui.fragment.filesRepository
+
+import com.nextcloud.android.lib.resources.recommendations.Recommendation
+
+interface FilesRepository {
+
+ /**
+ * Fetches a list of recommended files from the Nextcloud server.
+ *
+ * This function runs on the IO dispatcher and retrieves recommendations
+ * using the Nextcloud client. The results are passed to the provided callback on the main thread.
+ *
+ * @param onCompleted A callback function that receives the list of recommended files.
+ *
+ */
+ fun fetchRecommendedFiles(onCompleted: (ArrayList) -> Unit)
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/filesRepository/RemoteFilesRepository.kt b/app/src/main/java/com/owncloud/android/ui/fragment/filesRepository/RemoteFilesRepository.kt
new file mode 100644
index 000000000000..d44c54d63fdb
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/filesRepository/RemoteFilesRepository.kt
@@ -0,0 +1,48 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 Alper Ozturk
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.owncloud.android.ui.fragment.filesRepository
+
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import com.nextcloud.android.lib.resources.recommendations.GetRecommendationsRemoteOperation
+import com.nextcloud.android.lib.resources.recommendations.Recommendation
+import com.nextcloud.repository.ClientRepository
+import com.owncloud.android.lib.common.utils.Log_OC
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+@Suppress("TooGenericExceptionCaught")
+class RemoteFilesRepository(
+ private val clientRepository: ClientRepository,
+ lifecycleOwner: LifecycleOwner
+) : FilesRepository {
+ private val tag = "FilesRepository"
+ private val scope = lifecycleOwner.lifecycleScope
+
+ override fun fetchRecommendedFiles(onCompleted: (ArrayList) -> Unit) {
+ scope.launch(Dispatchers.IO) {
+ try {
+ val client = clientRepository.getNextcloudClient() ?: return@launch
+ val result = GetRecommendationsRemoteOperation().execute(client)
+ if (result.isSuccess) {
+ val recommendations = result.getResultData().recommendations
+ Log_OC.d(tag, "Recommended files fetched size: " + recommendations.size)
+
+ withContext(Dispatchers.Main) {
+ onCompleted(recommendations)
+ }
+ } else {
+ Log_OC.d(tag, "Recommended files cannot be fetched: " + result.code)
+ }
+ } catch (e: Exception) {
+ Log_OC.d(tag, "Exception caught while fetching recommended files: $e")
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt
new file mode 100644
index 000000000000..2a371c1bcbe7
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/RemoteShareRepository.kt
@@ -0,0 +1,59 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 Alper Ozturk
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.owncloud.android.ui.fragment.share
+
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import com.nextcloud.repository.ClientRepository
+import com.owncloud.android.datamodel.FileDataStorageManager
+import com.owncloud.android.lib.common.utils.Log_OC
+import com.owncloud.android.operations.GetSharesForFileOperation
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class RemoteShareRepository(
+ private val clientRepository: ClientRepository,
+ lifecycleOwner: LifecycleOwner,
+ private val fileDataStorageManager: FileDataStorageManager
+) : ShareRepository {
+ private val tag = "RemoteShareRepository"
+ private val scope = lifecycleOwner.lifecycleScope
+
+ override fun fetchSharees(remotePath: String, onCompleted: () -> Unit, onError: () -> Unit) {
+ scope.launch(Dispatchers.IO) {
+ val client = clientRepository.getOwncloudClient() ?: return@launch
+ val operation =
+ GetSharesForFileOperation(
+ path = remotePath,
+ reshares = true,
+ subfiles = false,
+ storageManager = fileDataStorageManager
+ )
+
+ @Suppress("DEPRECATION")
+ val result = operation.execute(client)
+
+ Log_OC.i(tag, "Remote path for the refresh shares: $remotePath")
+
+ withContext(Dispatchers.Main) {
+ if (result.isSuccess) {
+ Log_OC.d(tag, "Successfully refreshed shares for the specified remote path.")
+ onCompleted()
+ } else {
+ Log_OC.w(
+ tag,
+ "Failed to refresh shares for the specified remote path. " +
+ "An error occurred during the operation."
+ )
+ onError()
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/share/ShareRepository.kt b/app/src/main/java/com/owncloud/android/ui/fragment/share/ShareRepository.kt
new file mode 100644
index 000000000000..1f34ef5f11ca
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/share/ShareRepository.kt
@@ -0,0 +1,12 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 Alper Ozturk
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.owncloud.android.ui.fragment.share
+
+interface ShareRepository {
+ fun fetchSharees(remotePath: String, onCompleted: () -> Unit, onError: () -> Unit)
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt
index 09fe1537a307..1825cd1bfa34 100644
--- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt
@@ -21,6 +21,7 @@ import androidx.drawerlayout.widget.DrawerLayout
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
+import com.ionos.annotation.IonosCustomization
import com.nextcloud.client.account.User
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.editimage.EditImageActivity
@@ -52,6 +53,12 @@ import com.owncloud.android.ui.fragment.OCFileListFragment
import com.owncloud.android.ui.preview.model.PreviewImageActivityState
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.MimeTypeUtil
+import android.graphics.drawable.ColorDrawable
+import android.view.ViewGroup
+import androidx.activity.enableEdgeToEdge
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.updatePadding
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
import java.io.Serializable
import javax.inject.Inject
@@ -79,10 +86,22 @@ class PreviewImageActivity : FileActivity(), FileFragment.ContainerActivity, OnR
lateinit var localBroadcastManager: LocalBroadcastManager
private var actionBar: ActionBar? = null
+ private var showDirectoryWhenDeletionCompleted = false
+ @IonosCustomization
override fun onCreate(savedInstanceState: Bundle?) {
+ enableEdgeToEdge()
super.onCreate(savedInstanceState)
+ val contentContainer = (window.decorView as ViewGroup).getChildAt(0)
+ ViewCompat.setOnApplyWindowInsetsListener(contentContainer) { view, windowInsets ->
+ val insetsType = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
+ val insets = windowInsets.getInsets(insetsType)
+ val actionBarView = view.findViewById(androidx.appcompat.R.id.action_bar)
+ actionBarView?.updatePadding(left = insets.left, top = insets.top, right = insets.right)
+ WindowInsetsCompat.CONSUMED
+ }
+
actionBar = supportActionBar
if (savedInstanceState != null && !savedInstanceState.getBoolean(
@@ -102,8 +121,10 @@ class PreviewImageActivity : FileActivity(), FileFragment.ContainerActivity, OnR
val chosenFile = intent.getParcelableArgument(EXTRA_FILE, OCFile::class.java)
updateActionBarTitleAndHomeButton(chosenFile)
+ viewThemeUtils.platform.themeStatusBar(this, getColor(R.color.preview_image_system_bars_color))
if (actionBar != null) {
viewThemeUtils.files.setWhiteBackButton(this, actionBar!!)
+ actionBar?.setBackgroundDrawable(ColorDrawable(getColor(R.color.preview_image_system_bars_color)))
actionBar?.setDisplayHomeAsUpEnabled(true)
}
@@ -119,6 +140,13 @@ class PreviewImageActivity : FileActivity(), FileFragment.ContainerActivity, OnR
observeWorkerState()
}
+ @IonosCustomization("Remove default window insets paddings")
+ override fun isDefaultWindowInsetsHandlingEnabled() = false
+
+ fun showDirectoryWhenDeletionCompleted() {
+ showDirectoryWhenDeletionCompleted = true
+ }
+
fun toggleActionBarVisibility(hide: Boolean) {
if (actionBar == null) {
return
@@ -487,10 +515,15 @@ class PreviewImageActivity : FileActivity(), FileFragment.ContainerActivity, OnR
fun toggleFullScreen() {
if (fullScreenAnchorView == null) return
- val visible = (
- fullScreenAnchorView!!.systemUiVisibility
- and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
- ) == 0
+ val visible = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ val insets = window.decorView.rootWindowInsets
+ insets.isVisible(WindowInsets.Type.statusBars())
+ || insets.isVisible(WindowInsets.Type.navigationBars())
+ } else {
+ @Suppress("DEPRECATION")
+ (fullScreenAnchorView!!.systemUiVisibility
+ and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0
+ }
if (visible) {
hideSystemUI(fullScreenAnchorView!!)
@@ -536,6 +569,7 @@ class PreviewImageActivity : FileActivity(), FileFragment.ContainerActivity, OnR
private fun hideSystemUI(anchorView: View) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ actionBar?.hide()
window.insetsController?.let { controller ->
controller.hide(WindowInsets.Type.systemBars())
controller.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
@@ -555,6 +589,7 @@ class PreviewImageActivity : FileActivity(), FileFragment.ContainerActivity, OnR
private fun showSystemUI(anchorView: View) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ actionBar?.show()
window.insetsController?.let { controller ->
controller.show(WindowInsets.Type.systemBars())
diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt
index 91a4f16b3c89..43b6eac71e98 100644
--- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt
@@ -43,6 +43,7 @@ import com.caverock.androidsvg.SVG
import com.caverock.androidsvg.SVGParseException
import com.github.chrisbanes.photoview.PhotoView
import com.google.android.material.snackbar.Snackbar
+import com.ionos.annotation.IonosCustomization
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.jobs.BackgroundJobManager
@@ -262,6 +263,7 @@ class PreviewImageFragment : FileFragment(), Injectable {
}
}
+ @IonosCustomization
private fun adjustResizedImage(thumbnail: Bitmap?, width: Int, height: Int) {
var resizedImage = getResizedBitmap(file, width, height)
@@ -270,7 +272,7 @@ class PreviewImageFragment : FileFragment(), Injectable {
binding.image.visibility = View.VISIBLE
binding.emptyListView.visibility = View.GONE
binding.emptyListProgress.visibility = View.GONE
- binding.image.setBackgroundColor(resources.getColor(R.color.background_color_inverse))
+ binding.image.setBackgroundColor(resources.getColor(R.color.preview_image_background))
bitmap = resizedImage
} else {
@@ -286,7 +288,7 @@ class PreviewImageFragment : FileFragment(), Injectable {
containerActivity.storageManager,
connectivityService,
containerActivity.storageManager.user,
- resources.getColor(R.color.background_color_inverse)
+ resources.getColor(R.color.preview_image_background)
)
if (resizedImage == null) {
resizedImage = thumbnail
@@ -589,6 +591,7 @@ class PreviewImageFragment : FileFragment(), Injectable {
}
}
+ @IonosCustomization
private fun showLoadedImage(result: LoadImage?) {
val imageView = imageViewRef.get()
val bitmap = result?.bitmap
@@ -630,7 +633,7 @@ class PreviewImageFragment : FileFragment(), Injectable {
val progressView = progressViewRef.get()
progressView?.visibility = View.GONE
- imageView.setBackgroundColor(resources.getColor(R.color.background_color_inverse))
+ imageView.setBackgroundColor(resources.getColor(R.color.preview_image_background))
imageView.visibility = View.VISIBLE
}
}
diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt
index 1f892cf18122..511d3c506acc 100644
--- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt
@@ -60,6 +60,7 @@ import androidx.media3.ui.PlayerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors
+import com.ionos.annotation.IonosCustomization
import com.nextcloud.client.account.User
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.client.di.Injectable
@@ -185,6 +186,9 @@ class PreviewMediaActivity :
}
}
+ @IonosCustomization("Remove default window insets paddings")
+ override fun isDefaultWindowInsetsHandlingEnabled() = false
+
private fun sendAudioSessionReleaseBroadcast() {
val intent = Intent(BackgroundPlayerService.RELEASE_MEDIA_SESSION_BROADCAST_ACTION).apply {
setPackage(packageName)
@@ -249,6 +253,7 @@ class PreviewMediaActivity :
private fun isFileVideo(): Boolean = MimeTypeUtil.isVideo(file)
+ @IonosCustomization("System bar colors")
private fun configureSystemBars() {
updateActionBarTitleAndHomeButton(file)
@@ -269,13 +274,17 @@ class PreviewMediaActivity :
?.apply { setTint(Color.WHITE) }
)
- it.setBackgroundDrawable(ColorDrawable(Color.BLACK))
+ it.setBackgroundDrawable(ColorDrawable(getColor(R.color.exo_bottom_bar_background)))
+
+ viewThemeUtils.platform.themeStatusBar(this, getColor(R.color.transparent))
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ window.isNavigationBarContrastEnforced = false
+ }
+
+ } else {
+ viewThemeUtils.platform.themeStatusBar(this)
}
}
-
- viewThemeUtils.platform.themeStatusBar(
- this
- )
}
private fun showProgressLayout() {
@@ -290,6 +299,7 @@ class PreviewMediaActivity :
binding.emptyView.emptyListView.visibility = View.VISIBLE
}
+ @IonosCustomization("ui bugfix")
private fun setErrorMessage(headline: String, @StringRes message: Int) {
binding.emptyView.run {
emptyListViewHeadline.text = headline
@@ -297,8 +307,7 @@ class PreviewMediaActivity :
emptyListIcon.setImageResource(R.drawable.file_movie)
emptyListViewText.visibility = View.VISIBLE
emptyListIcon.visibility = View.VISIBLE
-
- hideProgressLayout()
+ binding.progress.visibility = View.GONE
}
}
@@ -306,15 +315,9 @@ class PreviewMediaActivity :
binding.imagePreview.setImageDrawable(genericThumbnail())
}
+ @IonosCustomization("No generic thumbnail")
private fun genericThumbnail(): Drawable? {
- val result = AppCompatResources.getDrawable(this, R.drawable.logo)
- result?.let {
- if (!resources.getBoolean(R.bool.is_branded_client)) {
- DrawableCompat.setTint(it, resources.getColor(R.color.primary, this.theme))
- }
- }
-
- return result
+ return null
}
override fun onSaveInstanceState(outState: Bundle) {
@@ -490,10 +493,12 @@ class PreviewMediaActivity :
}
}
+ @IonosCustomization("Insets for media controller")
private fun applyWindowInsets() {
val playerView = binding.exoplayerView
val exoControls = playerView.findViewById(R.id.exo_bottom_bar)
val exoProgress = playerView.findViewById(R.id.exo_progress)
+ val exoControlsHeight = exoControls.layoutParams.height
val progressBottomMargin = exoProgress.marginBottom
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets ->
@@ -502,22 +507,21 @@ class PreviewMediaActivity :
.displayCutout()
)
- binding.materialToolbar.updateLayoutParams {
- topMargin = insets.top
- }
exoControls.updateLayoutParams {
- bottomMargin = insets.bottom
+ height = insets.bottom + exoControlsHeight
}
exoProgress.updateLayoutParams {
bottomMargin = insets.bottom + progressBottomMargin
}
exoControls.updatePadding(left = insets.left, right = insets.right)
+ binding.audioControllerView.updatePadding(left = insets.left, right = insets.right, bottom = insets.bottom)
exoProgress.updatePadding(left = insets.left, right = insets.right)
- binding.materialToolbar.updatePadding(left = insets.left, right = insets.right)
+ binding.materialToolbar.updatePadding(left = insets.left, top = insets.top, right = insets.right)
WindowInsetsCompat.CONSUMED
}
}
+ @IonosCustomization("Hide cantrolls animation fix")
private fun setupVideoView() {
initWindowInsetsController()
val type = WindowInsetsCompat.Type.systemBars()
@@ -533,6 +537,12 @@ class PreviewMediaActivity :
windowInsetsController.hide(type)
supportActionBar!!.hide()
}
+ val exoControls = it.findViewById(R.id.exo_bottom_bar)
+ if (it.isControllerFullyVisible) {
+ exoControls.getChildAt(0)?.visibility = View.VISIBLE
+ } else {
+ exoControls.getChildAt(0)?.visibility = View.INVISIBLE
+ }
}
)
it.player = videoPlayer
@@ -711,16 +721,19 @@ class PreviewMediaActivity :
}
@Suppress("TooGenericExceptionCaught")
+ @IonosCustomization("Better UX")
private fun playVideo() {
setupVideoView()
- if (file.isDown) {
- prepareVideoPlayer(file.storageUri)
- } else {
- try {
- LoadStreamUrl(this, user, clientFactory).execute(file.localId)
- } catch (e: Exception) {
- Log_OC.e(TAG, "Loading stream url for Video not possible: $e")
+ if(videoPlayer?.currentMediaItem == null) {
+ if (file.isDown) {
+ prepareVideoPlayer(file.storageUri)
+ } else {
+ try {
+ LoadStreamUrl(this, user, clientFactory).execute(file.localId)
+ } catch (e: Exception) {
+ Log_OC.e(TAG, "Loading stream url for Video not possible: $e")
+ }
}
}
}
diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextStringFragment.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextStringFragment.java
index b9aa3f092a5f..74e5c528e49e 100644
--- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextStringFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextStringFragment.java
@@ -17,6 +17,7 @@
import android.view.ViewGroup;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
+import com.ionos.annotation.IonosCustomization;
import com.nextcloud.android.lib.richWorkspace.RichWorkspaceDirectEditingRemoteOperation;
import com.nextcloud.utils.extensions.FileExtensionsKt;
import com.owncloud.android.R;
@@ -72,6 +73,7 @@ public void onSaveInstanceState(@NonNull Bundle outState) {
}
@Override
+ @IonosCustomization
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
@@ -85,7 +87,6 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
fabMain.setOnClickListener(v -> edit());
fabMain.setImageResource(R.drawable.ic_edit);
- viewThemeUtils.material.themeFAB(fabMain);
return view;
}
diff --git a/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.kt b/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.kt
index ebb94da68f90..abf00825333a 100644
--- a/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.kt
@@ -27,7 +27,9 @@ import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.android.material.button.MaterialButton
import com.google.android.material.snackbar.Snackbar
+import com.ionos.annotation.IonosCustomization
import com.nextcloud.client.account.CurrentAccountProvider
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.network.ClientFactory
@@ -295,9 +297,10 @@ class TrashbinActivity :
onBackPressedCallback.isEnabled = !isRoot
}
+ @IonosCustomization
override fun onSortingOrderChosen(selection: FileSortOrder?) {
- val sortButton = findViewById(R.id.sort_button)
- sortButton.setText(DisplayUtils.getSortOrderStringId(selection))
+ val sortButton = findViewById(R.id.sort_button)
+ sortButton.setIconResource(DisplayUtils.getSortOrderIconRes(selection))
trashbinListAdapter?.setSortOrder(selection)
}
diff --git a/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java b/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java
index b70853136846..fec79a43c25d 100644
--- a/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java
+++ b/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java
@@ -56,6 +56,8 @@
import com.caverock.androidsvg.SVG;
import com.elyeproj.loaderviewlibrary.LoaderImageView;
import com.google.android.material.snackbar.Snackbar;
+import com.ionos.annotation.IonosCustomization;
+import com.ionos.utils.IonosBuildHelper;
import com.nextcloud.client.account.CurrentAccountProvider;
import com.nextcloud.client.account.User;
import com.nextcloud.client.network.ClientFactory;
@@ -102,6 +104,7 @@
import java.util.Map;
import java.util.TimeZone;
+import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
@@ -478,6 +481,7 @@ public static void setAvatar(@NonNull User user, @NonNull String userId, AvatarG
* @param resources reference for density information
* @param callContext which context is called to set the generated avatar
*/
+ @IonosCustomization
public static void setAvatar(@NonNull User user,
@NonNull String userId,
String displayName,
@@ -486,30 +490,45 @@ public static void setAvatar(@NonNull User user,
Resources resources,
Object callContext,
Context context) {
- if (callContext instanceof View) {
- ((View) callContext).setContentDescription(String.valueOf(user.toPlatformAccount().hashCode()));
+ if (callContext instanceof View v) {
+ v.setContentDescription(String.valueOf(user.toPlatformAccount().hashCode()));
}
- ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context);
+ if (IonosBuildHelper.isIonosBuild()) {
+ Drawable avatar = ResourcesCompat.getDrawable(resources, R.drawable.account_circle_white, null);
+ listener.avatarGenerated(avatar, callContext);
+ return;
+ }
final String accountName = user.getAccountName();
String serverName = accountName.substring(accountName.lastIndexOf('@') + 1);
- String eTag = arbitraryDataProvider.getValue(userId + "@" + serverName, ThumbnailsCacheManager.AVATAR);
- String avatarKey = "a_" + userId + "_" + serverName + "_" + eTag;
-
- // first show old one
- Drawable avatar = BitmapUtils.bitmapToCircularBitmapDrawable(resources,
- ThumbnailsCacheManager.getBitmapFromDiskCache(avatarKey));
-
- // if no one exists, show colored icon with initial char
- if (avatar == null) {
- try {
- avatar = TextDrawable.createAvatarByUserId(displayName, avatarRadius);
- } catch (Exception e) {
- Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e);
- avatar = ResourcesCompat.getDrawable(resources,
- R.drawable.account_circle_white,
- null);
+ Drawable avatar;
+
+ if (userId.isEmpty()) {
+ avatar = ContextCompat.getDrawable(context, R.drawable.ic_link);
+ if (avatar != null) {
+ int tintColor = ContextCompat.getColor(context, R.color.icon_on_nc_grey);
+ avatar.setTint(tintColor);
+ }
+ } else {
+ ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context);
+ String eTag = arbitraryDataProvider.getValue(userId + "@" + serverName, ThumbnailsCacheManager.AVATAR);
+ String avatarKey = "a_" + userId + "_" + serverName + "_" + eTag;
+
+ // first show old one
+ avatar = BitmapUtils.bitmapToCircularBitmapDrawable(resources,
+ ThumbnailsCacheManager.getBitmapFromDiskCache(avatarKey));
+
+ // if no one exists, show colored icon with initial char
+ if (avatar == null) {
+ try {
+ avatar = TextDrawable.createAvatarByUserId(displayName, avatarRadius);
+ } catch (Exception e) {
+ Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e);
+ avatar = ResourcesCompat.getDrawable(resources,
+ R.drawable.account_circle_white,
+ null);
+ }
}
}
@@ -837,6 +856,25 @@ static public void openSortingOrderDialogFragment(FragmentManager supportFragmen
}
}
+ @IonosCustomization
+ public static @DrawableRes int getSortOrderIconRes(FileSortOrder sortOrder) {
+ switch (sortOrder.name) {
+ case SORT_Z_TO_A_ID:
+ return R.drawable.ic_alphabetical_desc;
+ case SORT_NEW_TO_OLD_ID:
+ return R.drawable.ic_modification_desc;
+ case SORT_OLD_TO_NEW_ID:
+ return R.drawable.ic_modification_asc;
+ case SORT_BIG_TO_SMALL_ID:
+ return R.drawable.ic_size_desc;
+ case SORT_SMALL_TO_BIG_ID:
+ return R.drawable.ic_size_asc;
+ case SORT_A_TO_Z_ID:
+ default:
+ return R.drawable.ic_alphabetical_asc;
+ }
+ }
+
public static String getDateByPattern(long timestamp, String pattern) {
return getDateByPattern(timestamp, null, pattern);
}
diff --git a/app/src/main/java/com/owncloud/android/utils/DrawableUtil.kt b/app/src/main/java/com/owncloud/android/utils/DrawableUtil.kt
index fcd2434c1be2..9a2b6555fcf4 100644
--- a/app/src/main/java/com/owncloud/android/utils/DrawableUtil.kt
+++ b/app/src/main/java/com/owncloud/android/utils/DrawableUtil.kt
@@ -7,10 +7,11 @@
*/
package com.owncloud.android.utils
-import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
+import android.view.Gravity
import androidx.core.graphics.drawable.DrawableCompat
+import com.ionos.annotation.IonosCustomization
object DrawableUtil {
@@ -20,28 +21,12 @@ object DrawableUtil {
return drawable
}
+ @IonosCustomization
fun addDrawableAsOverlay(backgroundDrawable: Drawable, overlayDrawable: Drawable): LayerDrawable {
- val containerDrawable = LayerDrawable(arrayOf(backgroundDrawable, overlayDrawable))
- 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
+ return LayerDrawable(arrayOf(backgroundDrawable, overlayDrawable)).apply {
+ setLayerSize(1, overlayDrawable.intrinsicWidth, overlayDrawable.intrinsicHeight)
+ setLayerGravity(1, Gravity.CENTER)
+ }
}
}
diff --git a/app/src/main/java/com/owncloud/android/utils/DrawerMenuUtil.java b/app/src/main/java/com/owncloud/android/utils/DrawerMenuUtil.java
index 0591b0d0c4d0..8ab5daab1594 100644
--- a/app/src/main/java/com/owncloud/android/utils/DrawerMenuUtil.java
+++ b/app/src/main/java/com/owncloud/android/utils/DrawerMenuUtil.java
@@ -10,6 +10,7 @@
import android.content.res.Resources;
import android.view.Menu;
+import com.ionos.annotation.IonosCustomization;
import com.nextcloud.client.account.User;
import com.owncloud.android.R;
import com.owncloud.android.lib.resources.status.OCCapability;
@@ -77,6 +78,11 @@ public static void setupHomeMenuItem(Menu menu, Resources resources) {
}
}
+ @IonosCustomization
+ public static void removePersonalFiles(Menu menu) {
+ removeMenuItem(menu, R.id.nav_personal_files);
+ }
+
private static void removeMenuItem(Menu menu, int... menuIds) {
if (menuIds != null) {
for (int menuId : menuIds) {
diff --git a/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java b/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java
index 5e9493f67190..613d377d6a96 100644
--- a/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java
+++ b/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java
@@ -16,6 +16,7 @@
import android.net.Uri;
import android.webkit.MimeTypeMap;
+import com.ionos.annotation.IonosCustomization;
import com.nextcloud.android.common.ui.theme.utils.ColorRole;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.OCFile;
@@ -88,6 +89,7 @@ private MimeTypeUtil() {
* @param filename Name, with extension.
* @return Drawable of an image resource.
*/
+ @IonosCustomization("Custom icon tint")
public static Drawable getFileTypeIcon(String mimetype,
String filename,
Context context,
@@ -99,8 +101,8 @@ public static Drawable getFileTypeIcon(String mimetype,
return null;
}
- if (R.drawable.file_zip == iconId) {
- viewThemeUtils.platform.tintDrawable(context, icon, ColorRole.PRIMARY);
+ if (R.drawable.file_image != iconId && R.drawable.file_movie != iconId && R.drawable.file_sound != iconId) {
+ icon.setTint(context.getColor(R.color.filelist_file_icon_color));
}
return icon;
@@ -144,14 +146,16 @@ public static int getFileTypeIconId(String mimetype, String filename) {
return determineIconIdByMimeTypeList(possibleMimeTypes);
}
+ @IonosCustomization("Custom icon tint")
public static Drawable getDefaultFolderIcon(Context context, ViewThemeUtils viewThemeUtils) {
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.folder);
assert(drawable != null);
- viewThemeUtils.platform.tintDrawable(context, drawable, ColorRole.PRIMARY);
+ drawable.setTint(context.getColor(R.color.filelist_file_icon_color));
return drawable;
}
+ @IonosCustomization("Remove custom overlay color for dark mode")
public static LayerDrawable getFolderIcon(boolean isDarkModeActive, Integer overlayIconId, Context context, ViewThemeUtils viewThemeUtils) {
Drawable folderDrawable = getDefaultFolderIcon(context, viewThemeUtils);
assert(folderDrawable != null);
@@ -165,10 +169,6 @@ public static LayerDrawable getFolderIcon(boolean isDarkModeActive, Integer over
Drawable overlayDrawable = ContextCompat.getDrawable(context, overlayIconId);
assert(overlayDrawable != null);
- if (isDarkModeActive) {
- overlayDrawable = DrawableUtil.INSTANCE.changeColor(overlayDrawable, R.color.dark);
- }
-
return DrawableUtil.INSTANCE.addDrawableAsOverlay(folderDrawable, overlayDrawable);
}
diff --git a/app/src/main/java/com/owncloud/android/utils/WebViewUtil.kt b/app/src/main/java/com/owncloud/android/utils/WebViewUtil.kt
index 2983ab54657c..b01224217948 100644
--- a/app/src/main/java/com/owncloud/android/utils/WebViewUtil.kt
+++ b/app/src/main/java/com/owncloud/android/utils/WebViewUtil.kt
@@ -10,16 +10,98 @@ package com.owncloud.android.utils
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
+import android.content.pm.PackageManager
import android.net.Proxy
+import android.net.Uri
import android.text.TextUtils
import android.util.ArrayMap
import android.util.Log
import android.webkit.WebView
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.owncloud.android.R
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
import java.io.PrintWriter
import java.io.StringWriter
-class WebViewUtil {
+class WebViewUtil(private val context: Context) {
+
+ private val packageName = "com.google.android.webview"
+
+ fun checkWebViewVersion() {
+ if (!isWebViewVersionValid()) {
+ showUpdateDialog()
+ }
+ }
+
+ private fun isWebViewVersionValid(): Boolean {
+ val currentWebViewVersion = getCurrentWebViewMajorVersion() ?: return true
+ val minSupportedWebViewVersion: String = getMinimumSupportedMajorWebViewVersion()
+ return currentWebViewVersion.toInt() >= minSupportedWebViewVersion.toInt()
+ }
+
+ private fun showUpdateDialog() {
+ val builder = MaterialAlertDialogBuilder(context)
+ .setTitle(context.getString(R.string.webview_version_check_alert_dialog_title))
+ .setMessage(context.getString(R.string.webview_version_check_alert_dialog_message))
+ .setCancelable(false)
+ .setPositiveButton(
+ context.getString(R.string.webview_version_check_alert_dialog_positive_button_title)
+ ) { _, _ ->
+ redirectToAndroidSystemWebViewStorePage()
+ }
+
+ val dialog = builder.create()
+ dialog.show()
+ }
+
+ private fun redirectToAndroidSystemWebViewStorePage() {
+ val uri = Uri.parse("market://details?id=$packageName")
+ val intent = Intent(Intent.ACTION_VIEW, uri)
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+
+ try {
+ context.startActivity(intent)
+ } catch (e: android.content.ActivityNotFoundException) {
+ redirectToPlayStoreWebsiteForAndroidSystemWebView()
+ }
+ }
+
+ private fun redirectToPlayStoreWebsiteForAndroidSystemWebView() {
+ val playStoreWebUri = Uri.parse("https://play.google.com/store/apps/details?id=$packageName")
+ val webIntent = Intent(Intent.ACTION_VIEW, playStoreWebUri)
+ context.startActivity(webIntent)
+ }
+
+ private fun getCurrentWebViewMajorVersion(): String? {
+ val pm: PackageManager = context.packageManager
+
+ return try {
+ val pi = pm.getPackageInfo("com.google.android.webview", 0)
+ val fullVersion = pi.versionName ?: return null
+
+ // Split the version string by "." and get the first part
+ val versionParts = fullVersion.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }
+ .toTypedArray()
+
+ if (versionParts.isNotEmpty()) {
+ versionParts[0]
+ } else {
+ null
+ }
+ } catch (e: PackageManager.NameNotFoundException) {
+ null
+ }
+ }
+
+ /**
+ * Ideally we should fetch from database, reading actual value
+ * from PlayStore not feasible due to frequently api changes made by
+ * Google
+ *
+ */
+ private fun getMinimumSupportedMajorWebViewVersion(): String {
+ return "118"
+ }
/**
* From https://stackoverflow.com/a/18453384
diff --git a/app/src/main/java/com/owncloud/android/utils/theme/ViewThemeUtils.kt b/app/src/main/java/com/owncloud/android/utils/theme/ViewThemeUtils.kt
index 5a09ea8d69a5..9dc03e23636e 100644
--- a/app/src/main/java/com/owncloud/android/utils/theme/ViewThemeUtils.kt
+++ b/app/src/main/java/com/owncloud/android/utils/theme/ViewThemeUtils.kt
@@ -7,6 +7,12 @@
*/
package com.owncloud.android.utils.theme
+import com.ionos.annotation.IonosCustomization
+import com.ionos.utils.IonosAndroidViewThemeUtils
+import com.ionos.utils.IonosAndroidXViewThemeUtils
+import com.ionos.utils.IonosDialogViewThemeUtils
+import com.ionos.utils.IonosFilesSpecificViewThemeUtils
+import com.ionos.utils.IonosMaterialViewThemeUtils
import com.nextcloud.android.common.ui.color.ColorUtil
import com.nextcloud.android.common.ui.theme.MaterialSchemes
import com.nextcloud.android.common.ui.theme.ViewThemeUtilsBase
@@ -19,24 +25,31 @@ import javax.inject.Inject
/**
* Child fields intentionally constructed instead of injected in order to reuse schemes for performance
*/
+@IonosCustomization
class ViewThemeUtils @Inject constructor(
schemes: MaterialSchemes,
colorUtil: ColorUtil
) : ViewThemeUtilsBase(schemes) {
+ private val platformDelegate = AndroidViewThemeUtils(schemes, colorUtil)
+ private val androidxDelegate = AndroidXViewThemeUtils(schemes, platformDelegate)
+
@JvmField
- val platform = AndroidViewThemeUtils(schemes, colorUtil)
+ val platform = IonosAndroidViewThemeUtils(platformDelegate)
@JvmField
- val material = MaterialViewThemeUtils(schemes, colorUtil)
+ val material = IonosMaterialViewThemeUtils(MaterialViewThemeUtils(schemes, colorUtil))
@JvmField
- val androidx = AndroidXViewThemeUtils(schemes, platform)
+ val androidx = IonosAndroidXViewThemeUtils(androidxDelegate)
@JvmField
- val dialog = DialogViewThemeUtils(schemes)
+ val dialog = IonosDialogViewThemeUtils(DialogViewThemeUtils(schemes))
@JvmField
- val files = FilesSpecificViewThemeUtils(schemes, colorUtil, platform, androidx)
+ val files = IonosFilesSpecificViewThemeUtils(
+ schemes,
+ FilesSpecificViewThemeUtils(schemes, colorUtil, platformDelegate, androidxDelegate),
+ )
class Factory @Inject constructor(
private val schemesProvider: MaterialSchemesProvider,
diff --git a/app/src/main/res/anim/blink.xml b/app/src/main/res/anim/blink.xml
new file mode 100644
index 000000000000..9a7ff11850a3
--- /dev/null
+++ b/app/src/main/res/anim/blink.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/app/src/main/res/anim/fade_in.xml b/app/src/main/res/anim/fade_in.xml
new file mode 100644
index 000000000000..a21744ff2499
--- /dev/null
+++ b/app/src/main/res/anim/fade_in.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/anim/fade_out.xml b/app/src/main/res/anim/fade_out.xml
new file mode 100644
index 000000000000..a2b5526cb1ec
--- /dev/null
+++ b/app/src/main/res/anim/fade_out.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/color/fab_background_tint.xml b/app/src/main/res/color/fab_background_tint.xml
new file mode 100644
index 000000000000..debb631f48e9
--- /dev/null
+++ b/app/src/main/res/color/fab_background_tint.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/color/filled_button_bg_color.xml b/app/src/main/res/color/filled_button_bg_color.xml
new file mode 100644
index 000000000000..518c975d874c
--- /dev/null
+++ b/app/src/main/res/color/filled_button_bg_color.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/color/filled_button_text_color.xml b/app/src/main/res/color/filled_button_text_color.xml
new file mode 100644
index 000000000000..6eef0fe45d18
--- /dev/null
+++ b/app/src/main/res/color/filled_button_text_color.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/color/outlined_button_bg_color.xml b/app/src/main/res/color/outlined_button_bg_color.xml
new file mode 100644
index 000000000000..3f66ee2be899
--- /dev/null
+++ b/app/src/main/res/color/outlined_button_bg_color.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/color/outlined_button_stroke_color.xml b/app/src/main/res/color/outlined_button_stroke_color.xml
new file mode 100644
index 000000000000..0c25161bc46c
--- /dev/null
+++ b/app/src/main/res/color/outlined_button_stroke_color.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/color/outlined_button_text_color.xml b/app/src/main/res/color/outlined_button_text_color.xml
new file mode 100644
index 000000000000..2140ebe47a18
--- /dev/null
+++ b/app/src/main/res/color/outlined_button_text_color.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/color/text_button_text_color.xml b/app/src/main/res/color/text_button_text_color.xml
new file mode 100644
index 000000000000..c40c98ad77f3
--- /dev/null
+++ b/app/src/main/res/color/text_button_text_color.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable-v33/player_ic_notification_audio.xml b/app/src/main/res/drawable-v33/player_ic_notification_audio.xml
new file mode 100644
index 000000000000..ef8c993797fd
--- /dev/null
+++ b/app/src/main/res/drawable-v33/player_ic_notification_audio.xml
@@ -0,0 +1,21 @@
+
+
+
+ -
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable-v33/player_ic_notification_video.xml b/app/src/main/res/drawable-v33/player_ic_notification_video.xml
new file mode 100644
index 000000000000..12c140c6c1ca
--- /dev/null
+++ b/app/src/main/res/drawable-v33/player_ic_notification_video.xml
@@ -0,0 +1,21 @@
+
+
+
+ -
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/background_splash.xml b/app/src/main/res/drawable/background_splash.xml
new file mode 100644
index 000000000000..e27a0271888a
--- /dev/null
+++ b/app/src/main/res/drawable/background_splash.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/grid_mode_item_background.xml b/app/src/main/res/drawable/grid_mode_item_background.xml
new file mode 100644
index 000000000000..a35817881607
--- /dev/null
+++ b/app/src/main/res/drawable/grid_mode_item_background.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/grid_mode_selected_item_background.xml b/app/src/main/res/drawable/grid_mode_selected_item_background.xml
new file mode 100644
index 000000000000..88b9877084b8
--- /dev/null
+++ b/app/src/main/res/drawable/grid_mode_selected_item_background.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_data_protection.xml b/app/src/main/res/drawable/ic_data_protection.xml
new file mode 100644
index 000000000000..72c7887423de
--- /dev/null
+++ b/app/src/main/res/drawable/ic_data_protection.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_file_action_cancel_sync.xml b/app/src/main/res/drawable/ic_file_action_cancel_sync.xml
new file mode 100644
index 000000000000..c7f0db0ca5d3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_file_action_cancel_sync.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_file_action_edit.xml b/app/src/main/res/drawable/ic_file_action_edit.xml
new file mode 100644
index 000000000000..e2bcc65afb5c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_file_action_edit.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_file_action_lock_file.xml b/app/src/main/res/drawable/ic_file_action_lock_file.xml
new file mode 100644
index 000000000000..eaeef744157c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_file_action_lock_file.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_file_action_move_or_copy.xml b/app/src/main/res/drawable/ic_file_action_move_or_copy.xml
new file mode 100644
index 000000000000..388986ce246c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_file_action_move_or_copy.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_file_action_open_file_with.xml b/app/src/main/res/drawable/ic_file_action_open_file_with.xml
new file mode 100644
index 000000000000..388986ce246c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_file_action_open_file_with.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_file_action_set_encrypted.xml b/app/src/main/res/drawable/ic_file_action_set_encrypted.xml
new file mode 100644
index 000000000000..b7d8b3c97021
--- /dev/null
+++ b/app/src/main/res/drawable/ic_file_action_set_encrypted.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_file_action_stream_media.xml b/app/src/main/res/drawable/ic_file_action_stream_media.xml
new file mode 100644
index 000000000000..be328e566a66
--- /dev/null
+++ b/app/src/main/res/drawable/ic_file_action_stream_media.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_file_action_sync_file.xml b/app/src/main/res/drawable/ic_file_action_sync_file.xml
new file mode 100644
index 000000000000..7054f44c69be
--- /dev/null
+++ b/app/src/main/res/drawable/ic_file_action_sync_file.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_file_action_unlock_file.xml b/app/src/main/res/drawable/ic_file_action_unlock_file.xml
new file mode 100644
index 000000000000..8b84787751ec
--- /dev/null
+++ b/app/src/main/res/drawable/ic_file_action_unlock_file.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_file_action_unset_encrypted.xml b/app/src/main/res/drawable/ic_file_action_unset_encrypted.xml
new file mode 100644
index 000000000000..9a17c0e2b9dd
--- /dev/null
+++ b/app/src/main/res/drawable/ic_file_action_unset_encrypted.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_folder_all_share_types.xml b/app/src/main/res/drawable/ic_folder_all_share_types.xml
new file mode 100644
index 000000000000..d892b5fde64a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_folder_all_share_types.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_shared_all_types.xml b/app/src/main/res/drawable/ic_shared_all_types.xml
new file mode 100644
index 000000000000..a4c282d3521e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_shared_all_types.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/player_ic_audio.xml b/app/src/main/res/drawable/player_ic_audio.xml
new file mode 100644
index 000000000000..96cfb9d2a11f
--- /dev/null
+++ b/app/src/main/res/drawable/player_ic_audio.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/player_ic_close.xml b/app/src/main/res/drawable/player_ic_close.xml
new file mode 100644
index 000000000000..ee43a7f8a820
--- /dev/null
+++ b/app/src/main/res/drawable/player_ic_close.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/player_ic_notification_audio.xml b/app/src/main/res/drawable/player_ic_notification_audio.xml
new file mode 100644
index 000000000000..aec4a2da0a57
--- /dev/null
+++ b/app/src/main/res/drawable/player_ic_notification_audio.xml
@@ -0,0 +1,15 @@
+
+
+
+ -
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/player_ic_notification_video.xml b/app/src/main/res/drawable/player_ic_notification_video.xml
new file mode 100644
index 000000000000..0eb1d0d71bce
--- /dev/null
+++ b/app/src/main/res/drawable/player_ic_notification_video.xml
@@ -0,0 +1,15 @@
+
+
+
+ -
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/player_ic_pause.xml b/app/src/main/res/drawable/player_ic_pause.xml
new file mode 100644
index 000000000000..0caa2063b795
--- /dev/null
+++ b/app/src/main/res/drawable/player_ic_pause.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/player_ic_play.xml b/app/src/main/res/drawable/player_ic_play.xml
new file mode 100644
index 000000000000..593cc62cf301
--- /dev/null
+++ b/app/src/main/res/drawable/player_ic_play.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/player_ic_repeat.xml b/app/src/main/res/drawable/player_ic_repeat.xml
new file mode 100644
index 000000000000..933ce5c46673
--- /dev/null
+++ b/app/src/main/res/drawable/player_ic_repeat.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/player_ic_shuffle.xml b/app/src/main/res/drawable/player_ic_shuffle.xml
new file mode 100644
index 000000000000..fdba83301593
--- /dev/null
+++ b/app/src/main/res/drawable/player_ic_shuffle.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/player_ic_skip_next.xml b/app/src/main/res/drawable/player_ic_skip_next.xml
new file mode 100644
index 000000000000..7f56ee4c0ead
--- /dev/null
+++ b/app/src/main/res/drawable/player_ic_skip_next.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/player_ic_skip_previous.xml b/app/src/main/res/drawable/player_ic_skip_previous.xml
new file mode 100644
index 000000000000..7259194fd868
--- /dev/null
+++ b/app/src/main/res/drawable/player_ic_skip_previous.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/player_ic_video.xml b/app/src/main/res/drawable/player_ic_video.xml
new file mode 100644
index 000000000000..ca3ca3a8a6ba
--- /dev/null
+++ b/app/src/main/res/drawable/player_ic_video.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/player_progress_drawable.xml b/app/src/main/res/drawable/player_progress_drawable.xml
new file mode 100644
index 000000000000..ce05f0fa28c2
--- /dev/null
+++ b/app/src/main/res/drawable/player_progress_drawable.xml
@@ -0,0 +1,25 @@
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/player_progress_thumb.xml b/app/src/main/res/drawable/player_progress_thumb.xml
new file mode 100644
index 000000000000..13b1910c2ac6
--- /dev/null
+++ b/app/src/main/res/drawable/player_progress_thumb.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/rounded_rect_4dp.xml b/app/src/main/res/drawable/rounded_rect_4dp.xml
new file mode 100644
index 000000000000..1952e3c9f856
--- /dev/null
+++ b/app/src/main/res/drawable/rounded_rect_4dp.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout-land/account_setup.xml b/app/src/main/res/layout-land/account_setup.xml
index 8fe6b05b3035..c75a890fdba1 100644
--- a/app/src/main/res/layout-land/account_setup.xml
+++ b/app/src/main/res/layout-land/account_setup.xml
@@ -14,7 +14,8 @@
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:orientation="horizontal"
- android:padding="@dimen/standard_padding">
+ android:padding="@dimen/standard_padding"
+ android:visibility="invisible">
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_data_protection_detail_page.xml b/app/src/main/res/layout/activity_data_protection_detail_page.xml
new file mode 100644
index 000000000000..459f10840155
--- /dev/null
+++ b/app/src/main/res/layout/activity_data_protection_detail_page.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_data_protection_overview_page.xml b/app/src/main/res/layout/activity_data_protection_overview_page.xml
new file mode 100644
index 000000000000..7363d67193dd
--- /dev/null
+++ b/app/src/main/res/layout/activity_data_protection_overview_page.xml
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_edit_image.xml b/app/src/main/res/layout/activity_edit_image.xml
index 4b11869ee501..a67156844e45 100644
--- a/app/src/main/res/layout/activity_edit_image.xml
+++ b/app/src/main/res/layout/activity_edit_image.xml
@@ -10,13 +10,13 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/black">
+ android:background="@color/edit_image_background">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/circle_shimmer.xml b/app/src/main/res/layout/circle_shimmer.xml
new file mode 100644
index 000000000000..c9e0674556c4
--- /dev/null
+++ b/app/src/main/res/layout/circle_shimmer.xml
@@ -0,0 +1,17 @@
+
+
+
diff --git a/app/src/main/res/layout/dialog_choose_account.xml b/app/src/main/res/layout/dialog_choose_account.xml
index 66e05389de49..53e7b9fa2693 100644
--- a/app/src/main/res/layout/dialog_choose_account.xml
+++ b/app/src/main/res/layout/dialog_choose_account.xml
@@ -46,10 +46,12 @@
android:textAlignment="textStart"
android:textAllCaps="false"
android:textColor="@color/fontAppbar"
+ android:visibility="gone"
app:icon="@drawable/ic_edit"
app:iconGravity="start"
app:iconPadding="22dp"
- app:iconTint="@color/fontAppbar" />
+ app:iconTint="@color/fontAppbar"
+ app:ionosCustomization="" />
diff --git a/app/src/main/res/layout/empty_list.xml b/app/src/main/res/layout/empty_list.xml
index 5f1aea457a05..a0d1da1af082 100644
--- a/app/src/main/res/layout/empty_list.xml
+++ b/app/src/main/res/layout/empty_list.xml
@@ -43,6 +43,7 @@
android:paddingTop="@dimen/standard_padding"
android:paddingBottom="@dimen/standard_half_padding"
android:text="@string/file_list_loading"
+ android:textColor="@color/text_color"
android:textSize="26sp" />
diff --git a/app/src/main/res/layout/file_details_share_public_link_add_new_item.xml b/app/src/main/res/layout/file_details_share_public_link_add_new_item.xml
index 466cdcddc3d3..9efada5357dd 100644
--- a/app/src/main/res/layout/file_details_share_public_link_add_new_item.xml
+++ b/app/src/main/res/layout/file_details_share_public_link_add_new_item.xml
@@ -19,9 +19,8 @@
android:layout_gravity="center_vertical"
android:layout_marginStart="@dimen/standard_margin"
android:layout_marginEnd="@dimen/standard_margin"
- android:background="@drawable/round_bgnd"
android:contentDescription="@string/share"
- android:padding="@dimen/standard_half_padding"
+ android:scaleType="centerInside"
android:src="@drawable/shared_via_link" />
+ android:scaleType="centerInside"
+ android:src="@drawable/account_circle_white" />
diff --git a/app/src/main/res/layout/file_details_sharing_fragment.xml b/app/src/main/res/layout/file_details_sharing_fragment.xml
index adb3a65bf77e..febc93d0d0f3 100644
--- a/app/src/main/res/layout/file_details_sharing_fragment.xml
+++ b/app/src/main/res/layout/file_details_sharing_fragment.xml
@@ -12,182 +12,195 @@
android:layout_height="match_parent"
android:paddingTop="@dimen/standard_eight_padding">
-
+ android:layout_height="match_parent"
+ android:layout_below="@id/appbar">
-
-
+ android:visibility="gone"
+ tools:visibility="visible"
+ android:orientation="vertical">
-
+
-
-
+ android:paddingRight="@dimen/standard_padding">
+ android:textSize="@dimen/two_line_primary_text_size" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+ android:text="@string/internal_shares"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
-
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
-
-
-
+
+
+
+
-
+
+
+
-
-
diff --git a/app/src/main/res/layout/file_details_sharing_process_fragment.xml b/app/src/main/res/layout/file_details_sharing_process_fragment.xml
index 14af3dff721c..1a628cb70909 100644
--- a/app/src/main/res/layout/file_details_sharing_process_fragment.xml
+++ b/app/src/main/res/layout/file_details_sharing_process_fragment.xml
@@ -48,6 +48,7 @@
android:id="@+id/share_process_permission_read_only"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:textColor="@color/text_color"
android:minHeight="@dimen/minimum_size_for_touchable_area"
android:text="@string/link_share_view_only" />
@@ -55,6 +56,7 @@
android:id="@+id/share_process_permission_upload_editing"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:textColor="@color/text_color"
android:minHeight="@dimen/minimum_size_for_touchable_area"
android:text="@string/link_share_allow_upload_and_editing" />
@@ -62,6 +64,7 @@
android:id="@+id/share_process_permission_file_drop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:textColor="@color/text_color"
android:minHeight="@dimen/minimum_size_for_touchable_area"
android:text="@string/link_share_file_drop" />
@@ -82,6 +85,7 @@
android:layout_height="wrap_content"
android:minHeight="@dimen/minimum_size_for_touchable_area"
android:text="@string/allow_resharing"
+ android:textColor="@color/text_color"
android:visibility="gone"
tools:visibility="visible" />
@@ -90,7 +94,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/minimum_size_for_touchable_area"
- android:text="@string/share_no_password_title"/>
+ android:text="@string/share_no_password_title"
+ android:textColor="@color/text_color" />
+ android:text="@string/share_no_expiration_date_label"
+ android:textColor="@color/text_color" />
@@ -187,7 +194,8 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/standard_half_margin"
android:minHeight="@dimen/minimum_size_for_touchable_area"
- android:text="@string/link_name" />
+ android:text="@string/link_name"
+ android:textColor="@color/text_color" />
+ app:cornerRadius="@dimen/button_corner_radius"
+ app:ionosCustomization="Adjust the height to match the height of the Next button" />
+ android:theme="@style/Theme.Dialog.IconButton.Filled"
+ android:textColor="@color/filled_button_text_color"
+ app:cornerRadius="@dimen/button_corner_radius"
+ app:ionosCustomization="Limit max lines number to 2" />
diff --git a/app/src/main/res/layout/file_details_sharing_shimmer.xml b/app/src/main/res/layout/file_details_sharing_shimmer.xml
new file mode 100644
index 000000000000..f679386e4a1b
--- /dev/null
+++ b/app/src/main/res/layout/file_details_sharing_shimmer.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/file_thumbnail.xml b/app/src/main/res/layout/file_thumbnail.xml
index 6f318d47e9b3..d143e9c1b661 100644
--- a/app/src/main/res/layout/file_thumbnail.xml
+++ b/app/src/main/res/layout/file_thumbnail.xml
@@ -28,9 +28,10 @@
diff --git a/app/src/main/res/layout/files_folder_picker.xml b/app/src/main/res/layout/files_folder_picker.xml
index f1d4f53ebb5d..ddbdc84a720e 100644
--- a/app/src/main/res/layout/files_folder_picker.xml
+++ b/app/src/main/res/layout/files_folder_picker.xml
@@ -51,7 +51,7 @@
@@ -31,6 +32,8 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/standard_margin"
android:layout_marginBottom="@dimen/standard_margin"
+ android:textStyle="bold"
+ app:ionosCustomization="bold text"
android:textAppearance="?android:attr/textAppearanceLarge"
tools:text="2016" />
diff --git a/app/src/main/res/layout/gallery_simple_item.xml b/app/src/main/res/layout/gallery_simple_item.xml
new file mode 100644
index 000000000000..1ac8a32e55ac
--- /dev/null
+++ b/app/src/main/res/layout/gallery_simple_item.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/app/src/main/res/layout/grid_item.xml b/app/src/main/res/layout/grid_item.xml
index e7f2a6d8b661..6f659afbd906 100644
--- a/app/src/main/res/layout/grid_item.xml
+++ b/app/src/main/res/layout/grid_item.xml
@@ -166,6 +166,15 @@
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_dots_vertical" />
+
+
diff --git a/app/src/main/res/layout/ionos_item_media.xml b/app/src/main/res/layout/ionos_item_media.xml
new file mode 100644
index 000000000000..d5b59dac84c2
--- /dev/null
+++ b/app/src/main/res/layout/ionos_item_media.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/list_item.xml b/app/src/main/res/layout/list_item.xml
index d275c2b82932..081aef9a85c4 100644
--- a/app/src/main/res/layout/list_item.xml
+++ b/app/src/main/res/layout/list_item.xml
@@ -212,6 +212,13 @@
+
+
+
+
diff --git a/app/src/main/res/layout/log_entry_list_item.xml b/app/src/main/res/layout/log_entry_list_item.xml
index 969711013c5b..ce6cf8f44fc0 100644
--- a/app/src/main/res/layout/log_entry_list_item.xml
+++ b/app/src/main/res/layout/log_entry_list_item.xml
@@ -20,6 +20,7 @@
android:ellipsize="end"
android:lines="1"
android:textStyle="bold"
+ android:textColor="@color/text_color"
tools:text="@tools:sample/lorem/random" />
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/player_audio_file_fragment.xml b/app/src/main/res/layout/player_audio_file_fragment.xml
new file mode 100644
index 000000000000..f1eddb8cc50e
--- /dev/null
+++ b/app/src/main/res/layout/player_audio_file_fragment.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/player_audio_view.xml b/app/src/main/res/layout/player_audio_view.xml
new file mode 100644
index 000000000000..af114e694258
--- /dev/null
+++ b/app/src/main/res/layout/player_audio_view.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/player_control_view.xml b/app/src/main/res/layout/player_control_view.xml
new file mode 100644
index 000000000000..d12e0685db0d
--- /dev/null
+++ b/app/src/main/res/layout/player_control_view.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/player_pager.xml b/app/src/main/res/layout/player_pager.xml
new file mode 100644
index 000000000000..6f440fc8becc
--- /dev/null
+++ b/app/src/main/res/layout/player_pager.xml
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/player_video_file_fragment.xml b/app/src/main/res/layout/player_video_file_fragment.xml
new file mode 100644
index 000000000000..800c1dcc8ff2
--- /dev/null
+++ b/app/src/main/res/layout/player_video_file_fragment.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/player_video_view.xml b/app/src/main/res/layout/player_video_view.xml
new file mode 100644
index 000000000000..209764136693
--- /dev/null
+++ b/app/src/main/res/layout/player_video_view.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/search_users_groups_layout.xml b/app/src/main/res/layout/search_users_groups_layout.xml
index f0b30be55872..21f1237cf328 100644
--- a/app/src/main/res/layout/search_users_groups_layout.xml
+++ b/app/src/main/res/layout/search_users_groups_layout.xml
@@ -30,7 +30,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/searchView"
- android:hint="@string/share_search"
+ android:hint="@string/ionos_share_search"
style="@style/ownCloud.SearchView"/>
+ app:icon="@drawable/ic_share" />
+ app:icon="@drawable/ic_link" />
diff --git a/app/src/main/res/layout/settings_privacy_switchers.xml b/app/src/main/res/layout/settings_privacy_switchers.xml
new file mode 100644
index 000000000000..8aa656803d02
--- /dev/null
+++ b/app/src/main/res/layout/settings_privacy_switchers.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/share_list_item_shimmer.xml b/app/src/main/res/layout/share_list_item_shimmer.xml
new file mode 100644
index 000000000000..b1d162ecfdd7
--- /dev/null
+++ b/app/src/main/res/layout/share_list_item_shimmer.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/simple_file_list_actions_bottom_sheet_fragment.xml b/app/src/main/res/layout/simple_file_list_actions_bottom_sheet_fragment.xml
new file mode 100644
index 000000000000..88b1e407a71c
--- /dev/null
+++ b/app/src/main/res/layout/simple_file_list_actions_bottom_sheet_fragment.xml
@@ -0,0 +1,208 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/simple_spinner_item.xml b/app/src/main/res/layout/simple_spinner_item.xml
new file mode 100644
index 000000000000..accb287cbdde
--- /dev/null
+++ b/app/src/main/res/layout/simple_spinner_item.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/app/src/main/res/layout/storage_path_item.xml b/app/src/main/res/layout/storage_path_item.xml
index 2b506d49cede..3415ebc58674 100644
--- a/app/src/main/res/layout/storage_path_item.xml
+++ b/app/src/main/res/layout/storage_path_item.xml
@@ -15,6 +15,9 @@
android:gravity="center|start"
android:paddingBottom="@dimen/standard_half_padding"
android:text="@string/menu_item_sort_by_name_z_a"
+ android:textColor="@color/text_color"
app:icon="@drawable/ic_user"
app:iconPadding="@dimen/standard_padding"
+ app:iconTint="@color/default_icon_color"
+ app:ionosCustomization="Custom color"
tools:text="DCIM" />
diff --git a/app/src/main/res/layout/synced_folders_settings_layout.xml b/app/src/main/res/layout/synced_folders_settings_layout.xml
index d0e0e423c9b5..d800222c1003 100644
--- a/app/src/main/res/layout/synced_folders_settings_layout.xml
+++ b/app/src/main/res/layout/synced_folders_settings_layout.xml
@@ -431,7 +431,7 @@
@@ -454,7 +454,7 @@
android:id="@+id/btnPositive"
android:layout_width="wrap_content"
android:text="@string/common_save"
- style="@style/Widget.Material3.Button.TonalButton"
+ style="@style/Theme.Dialog.IconButton.Filled.Tonal"
android:layout_height="wrap_content"
android:layout_weight="1"/>
diff --git a/app/src/main/res/layout/upload_files_layout.xml b/app/src/main/res/layout/upload_files_layout.xml
index 4012193eb6a9..2c4209c22e3d 100644
--- a/app/src/main/res/layout/upload_files_layout.xml
+++ b/app/src/main/res/layout/upload_files_layout.xml
@@ -78,7 +78,7 @@
android:layout_marginEnd="@dimen/standard_half_margin"
android:layout_weight="1"
android:text="@string/common_cancel"
- style="@style/OutlinedButton"
+ style="@style/Theme.Dialog.Button.OutlinedButton"
app:cornerRadius="@dimen/button_corner_radius" />
diff --git a/app/src/main/res/layout/user_info_layout.xml b/app/src/main/res/layout/user_info_layout.xml
index 5a7d442c24ee..c1ca59544861 100644
--- a/app/src/main/res/layout/user_info_layout.xml
+++ b/app/src/main/res/layout/user_info_layout.xml
@@ -62,6 +62,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/userinfo_icon"
app:layout_constraintTop_toTopOf="@id/userinfo_icon"
+ app:layout_constraintBottom_toTopOf="@id/userinfo_username"
tools:text="John Doe" />
diff --git a/app/src/main/res/layout/view_authorization_method.xml b/app/src/main/res/layout/view_authorization_method.xml
new file mode 100644
index 000000000000..51ea5477db49
--- /dev/null
+++ b/app/src/main/res/layout/view_authorization_method.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/activity_folder_picker.xml b/app/src/main/res/menu/activity_folder_picker.xml
index 3e6fdcebbf2a..21a6ab87f424 100644
--- a/app/src/main/res/menu/activity_folder_picker.xml
+++ b/app/src/main/res/menu/activity_folder_picker.xml
@@ -23,6 +23,6 @@
android:icon="@drawable/ic_action_create_dir"
android:orderInCategory="1"
android:title="@string/actionbar_mkdir"
- app:iconTint="?attr/colorOnSurface"
+ app:ionosCustomization="Remove tint"
app:showAsAction="ifRoom"/>
diff --git a/app/src/main/res/menu/activity_receive_external_files.xml b/app/src/main/res/menu/activity_receive_external_files.xml
index 4c22d95237bd..ed4a7894823e 100644
--- a/app/src/main/res/menu/activity_receive_external_files.xml
+++ b/app/src/main/res/menu/activity_receive_external_files.xml
@@ -27,7 +27,7 @@
android:icon="@drawable/ic_action_create_dir"
android:orderInCategory="1"
android:title="@string/actionbar_mkdir"
- app:iconTint="?attr/colorOnSurface"
+ app:ionosCustomization="Remove tint"
app:showAsAction="never"/>
diff --git a/app/src/main/res/values-de/ionos-strings.xml b/app/src/main/res/values-de/ionos-strings.xml
new file mode 100644
index 000000000000..84c570a2cee9
--- /dev/null
+++ b/app/src/main/res/values-de/ionos-strings.xml
@@ -0,0 +1,32 @@
+
+
+
+
+ Sie müssen sich über den Browser anmelden
+ Willkommen in ihrem Cloud-Speicher
+ Anmelden
+ Abbrechen
+ Wiederholen
+ Zustimmen
+ Diese Anwendung verwendet Cookies und ähnliche Technologien. Durch Klicken auf Zustimmen akzeptieren Sie die Verarbeitung und auch die Weitergabe Ihrer Daten an Dritte. Weitere Informationen, auch zur Datenverarbeitung durch Drittanbieter, finden Sie in den Einstellungen und in unserer Datenschutzhinweise Sie können die Nutzung der Tools Ablehnen oder Ihre Auswahl jederzeit über Ihre Einstellungen anpassen.
+ Diese Daten helfen uns, die App-Nutzung für Sie zu optimieren und Systemabstürze und Fehler schneller zu identifizieren.
+ Analyse der Datenerfassung zur bedarfsgerechten Gestaltung
+ Einstellungen
+ Die Erfassung dieser Daten ist notwendig, um wesentliche Funktionen der App nutzen zu können.
+ Erforderliche Datenerfassung
+ Zur Optimierung unserer App erfassen wir anonymisierte Daten. Hierzu nutzen wir Softwarelösungen verschiedener Partner. Wir möchten Ihnen volle Transparenz und Entscheidungsgewalt über die Verarbeitung und Erfassung Ihrer anonymisierten Nutzungsdaten geben. Ihre Einstellungen können Sie auch später jederzeit unter dem Menüpunkt Datenschutz ändern. Bitte beachten Sie jedoch, dass die Datenerfassung einen erheblichen Beitrag zur Optimierung dieser App leistet und Sie diese Optimierungen durch die Unterbindung der Datenübermittlung verhindern.
+ Einstellungen speichern
+ Datenschutz-Einstellungen
+ Berechtigungen
+ Berechtigungen
+ %1$s braucht die Erlaubnis, auf Ihre Dateien zuzugreifen, um besser zu funktionieren. Sie können den Zugriff erlauben oder verweigern.
+ %1$s braucht die Erlaubnis, auf Ihre Dateien zuzugreifen, um besser zu funktionieren. Sie können den Zugriff erlauben oder verweigern.
+ Name oder E-Mail Adresse...
+ Verwendeter Speicherplatz
+ Filter
+
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 635d9181e989..35b099284034 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -917,6 +917,7 @@
Streamen mit…
Internes Streamen nicht möglich
Laden Sie sich den Inhalt herunter oder nutzen Sie eine externe App.
+ Strikter Modus: keine HTTP-Verbindung erlaubt!
Jahr/Monat/Tag
Jahr/Monat
Jahr
@@ -1108,6 +1109,9 @@
Überprüfe gespeicherte Anmeldeinformationen
Kopiere Datei von privatem Speicher
Das Ändern der Erweiterung kann dazu führen, dass diese Datei in einer anderen Anwendung geöffnet wird
+ Bitte aktualisieren Sie die Android System WebView-App für eine Anmeldung
+ Aktualisieren
+ Android System WebView aktualisieren
Was-gibt\'s-Neues-Bild
Überspringen
Neu in %1$s
diff --git a/app/src/main/res/values-es-rCR/strings.xml b/app/src/main/res/values-es-rCR/strings.xml
new file mode 100644
index 000000000000..97660ca56e6f
--- /dev/null
+++ b/app/src/main/res/values-es-rCR/strings.xml
@@ -0,0 +1,543 @@
+
+
+ %1$s aplicación Android
+ Acerca de
+ versión %1$s
+ Ícono de la cuenta
+ ¡No se encontró la cuenta!
+ Editar
+ Enviar/Compartir
+ Vista de cuadrícula
+ Vista de lista
+ Nueva carpeta
+ Abrir con
+ Buscar
+ Detalles
+ Enviar
+ Configuraciones
+ Ordenar
+ Usuario activo
+ Aún no hay actividades
+ Enviar
+ Enviar liga a…
+ Actividad
+ Agregar a %1$s
+ Permitir volver a compartir
+ Todos
+ Completado
+ Desconocido
+ La cuenta aún no ha sido agregada a este dispositivo
+ Ya existe una cuenta en el dispositivo para el mismo usuario y servidor
+ El usuario ingresado no corresponde con el usuario de esta cuenta
+ Versión del servidor no reconocida
+ Conexión establecida
+ Dirección del servidor https://…
+ El formato de dirección para el servidor es erróneo
+ El servidor no se encontró
+ No hay conexión de red
+ Conexión segura no disponible.
+ La configuración del servidor está mal formada.
+ Autorización no exitosa
+ Acceso denegado por el servidor de autorización
+ La conexión segura se está redirigiendo a través de una ruta insegura.
+ Conexión segura establecida
+ Falla en la inicialización de SSL
+ No fue posible verificar la identidad del servidor SSL
+ Probando conexión
+ El servidor tardó demasiado en responder
+ Intentando iniciar sesión…
+ Nombre de usuario o contraseña incorrecto
+ Error desconocido: %1$s
+ ¡Se presentó un error HTTP desconocido!
+ ¡Se presentó un error desconocido!
+ No fue posible encontrar el servidor
+ %1$s no soporta cuentas múltiples
+ No fue posible establecer la conexión
+ Solo cargar sobre una Wi-Fi no tarificada
+ /CargaAutomática
+ Crear nueva configuración de carpeta personalizada
+ Configurar una carpeta personalizada
+ Avatar
+ Cerrar
+ Deshabilitar
+ Calendario
+ Se presentó un problema al cargar el certificado.
+ Casilla de verificación
+ Elige la carpeta local…
+ Elige la carpeta remota…
+ Texto copiado desde %1$s
+ No se ha recibido texto para copiar al portapapeles
+ Se presentó un error inesperado al copiar al portapapeles
+ Atrás
+ Cancelar
+ Cancelar sincronización
+ Selecciona una cuenta
+ Confirmar
+ Copiar
+ Borrar
+ Error
+ Memoria insuficiente
+ Error desconocido
+ Cargando
+ Siguiente
+ No
+ OK
+ Pendiente
+ Eliminar
+ Renombrar
+ Guardar
+ Enviar
+ Compartir
+ Omitir
+ Sí
+ Probar la versión de desarrollo
+ Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo.
+ foro
+ Ayuda a otros en
+ Contribuye activamente
+ la aplicación
+ Traducir
+ Obtén la liberación de desarrollo directamente
+ Obtén la liberación de desarrollo de la tienda de aplicaciones F-Droid
+ Obtén el candidato a liberación de la tienda de aplicaciones F-Droid
+ Obtén el candidato a liberación de la tienda de Google Play
+ Candidato a lanzamiento
+ El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid.
+ ¿Encontraste una falla? ¿Hay algo raro?
+ Ayúdanos probando
+ Reporta un tema en GitHub
+ ¿Realmente deseas elminiar %1$s?
+ ¿Realmente deseas borrar los elementos seleccionados?
+ ¿Realmente quieres eliminar %1$s y sus contenidos?
+ ¿Reamente deseas eliminar los elementos seleccionados y sus contenidos?
+ Sólo local
+ Ícono de usuario para la lista de contactos
+ No se han otorgado privilegios, nada fue importado.
+ Contactos
+ Respaldar ahora
+ El respaldo está calendarizado y deberá iniciar en breve
+ La importación está calendarizada y deberá iniciar en breve
+ No se encontraron archivos
+ ¡No fue posible encontrar tu último respaldo!
+ Copiado al portapapeles
+ Se presentó un error al intentar copiar este archivo o carpeta
+ No es posible copiar una carpeta dentro de una de sus sub carpetas
+ El archivo ya existe en la carpeta destino
+ Copiar liga
+ Copiar/ mover a la carpeta encriptada no se encuentra soportado por el momento.
+ Crear
+ No fue posible crear la carpeta
+ Nuevo
+ Carpeta nueva
+ Cerrar sesión
+ Deseleccionar todo
+ Nueva versión disponible
+ No hay información disponible.
+ No hay una nueva versión disponible.
+ Cerrar
+ Este algoritmo de verificación no está disponible en tu teléfono
+ Deshabilitar
+ Descartar
+ Terminado
+ No fue posible descargar %1$s
+ La descarga falló, inicia sesión de nuevo
+ Falla en la descarga
+ El archivo ya no se encuentra disponible en el servidor
+ %1$d%% Descargando %2$s
+ Descargando…
+ %1$s descargado
+ Descargado
+ Aún no ha descargado
+ Imagen de fondo para el encabezado de la barra de navegación
+ Actividades
+ Todos los archivos
+ Favoritos
+ Inicio
+ Notificaciones
+ Disponibles sin conexión
+ Modificado recientemente
+ Compartido
+ Papelera
+ Cargas
+ Cerrar sesión
+ %1$s de %2$s usados
+ Carga automática
+ Hablar
+ Establecer como encriptado
+ Configurar la encripción
+ Desencriptando…
+ Cerrar
+ Por favor ingresa la contraseña para desencriptar la llave privada.
+ Esta carpeta no está vacía.
+ Generando nuevas llaves…
+ Las 12 palabras juntas hacer una contraseña muy fuerte, permitiéndote solo a ti ver y hacer uso de tus archivos encriptados. Por favor escríbelo y mantenlo en un lugar seguro.
+ La encripción de punta-a-punta está dehabilitada en el servidor.
+ Anota tu contraseña de encripción de 12 palabras
+ Contraseña…
+ Recuperando llaves…
+ Almacenando llaves
+ Configurar encripción
+ No se pudieron guardar las llaves, por favor vuelve a intentarlo.
+ Se presentó un error al desencriptar. ¿Contraseña equivocada?
+ %1$s no pudo ser copiado a la carpeta local %2$s
+ Error crítico: No se pueden realizar operaciones
+ Cuentas
+ Creado
+ Usuario
+ Trabajos en segundo plano
+ Descargar
+ Cargar
+ Agregar a tus favoritos
+ Hacer favorito
+ Borrar
+ Archivo
+ Carga algún contenido o sincroniza con tus dispositivos.
+ Aún no hay nada marcado como favorito
+ No hay archivos aquí
+ No hay resultados en esta carpeta
+ No hay resultados
+ No hay nada aquí. Puedes agregar una carpeta.
+ No se encontraron archivos modificados dentro de los últimos 7 días
+ ¿Tal vez está dentro de una carpeta diferente?
+ Aún no hay nada compartido
+ carpeta
+ Cargando…
+ No se cuenta con una aplicación que maneje este tipo de archivo.
+ hace algunos segundos
+ Verificando el destino…
+ Limpiando…
+ La carpeta de datos ya existe. Selecciona una de las siguientes:
+ La carpeta Nextcloud ya existe
+ Se necesita más espacio
+ No fue posible leer el archivo fuente
+ No fue posible escribir al archivo destino
+ Se presentó una falla durante la migración
+ Se presentó una falla al actualizar el índice
+ Moviendo los datos…
+ Terminado
+ Reemplazar
+ Preparando la migración…
+ Restaurando la configuración de la cuenta…
+ Guardando la configuración de la cuenta…
+ ¡No se puede leer la carpeta fuente!
+ Actualizando índice…
+ Usar
+ Aguardando sincronización completa…
+ Archivo no encontrado
+ El archivo no pudo ser sincronizado. Mostrando la última versión disponible.
+ Renombrar
+ Detalles
+ Descargar
+ Exportar
+ El archivo se renombró com %1$s durante la carga
+ Sincronizar
+ No se ha seleccionado algún archivo
+ El nombre del archivo no puede estar en blanco.
+ Caracteres inválidos: / \\ < > : \" | ? *
+ El nombre del archivo contiene al menos un carácter inválido
+ Nombre del archivo
+ Crear
+ No hay carpetas aquí
+ Seleccionar
+ Mover
+ No se te permite %s
+ para copiar este archivo
+ para crear este archivo
+ para borrar este archivo
+ para mover este archivo
+ para renombrar este archivo
+ Cargando archivos…
+ Algunos archivos no se pudieron mover
+ Local: %1$s
+ Mover todos
+ Remoto: %1$s
+ Se han movido todos los archivos
+ Adelante
+ Nombre
+ Contraseña
+ Cargar archivos sólo con el dispositivo conectado a la toma de corriente
+ /Cargas Automáticas
+ Invisible
+ Liga
+ Permitir carga y edición
+ Permitir carga
+ Vista de lista
+ No hay archivos en esta carpeta
+ No se encontró el archivo en el sistema de archivos local
+ No hay más carpetas.
+ %1$s bitácora de aplicación Android
+ Iniciar sesión
+ Actualizar
+ Cargando...
+ Bitácoras
+ Borrar datos
+ Las configuraciones, base de datos y certificados del servidor de los datos de %1$s serán borrados permanentemente.\n\nLos archivos descargados se mantendrán sin cambios.\n\nEste proceso puede tomar algo de tiempo.
+ Administrar espacio
+ No fue posible leer el archivo de medios
+ El archivo de medios tiene una codificación incorrecta
+ El intento de reproducir el archivo sobrepasó el tiempo de espera
+ El reproductor de medios integrado es incapaz de reproducir el archivo de medios
+ Codec no soportado
+ Botón de avanzar rápido
+ Reproductor de música %1$s
+ Botón de reproducir o pausar
+ Botón de rebobinar
+ %1$s (reproduciendo)
+ Más reciente primero
+ Más antiguo primero
+ A - Z
+ Z - A
+ Más grande primero
+ Más pequeño primero
+ Más
+ Se presentó un error al intentar mover este archivo o carpeta
+ No es posible mover una carpeta dentro de una de sus sub carpetas
+ El archivo ya existe en la carpeta de destino
+ Se presentó un error durante la conexión al servidor
+ foto
+ Muestra el progreso de la descarga
+ Descargas
+ Muestra el progreso de la sincronización de archivos y los resultados
+ Sincronizar archivo
+ Progreso del reproductor de música
+ Reproductor de medios
+ Mostrar las notificaciones push enviadas por el servidor: Menciones en comentarios, recepción de nuevos elementos compartidos remotos, anuncios publicados por el administrador, etc.
+ Notificaciones push
+ Muestra el progreso de la carga
+ Cargas
+ No hay notificaciones
+ Por favor verifica más tarde.
+ 1 hora
+ Ingresa tu código de seguridad
+ El código de seguridad será solicitado cada vez que inicie la aplicación
+ Por favor ingresa tu código de seguridad
+ Los códigos de seguridad no son iguales
+ Por favor reingresa el código de seguridad
+ Elimina tu código de seguirdad
+ Código de seguirdad eliminado
+ Código de seguridad almacenado
+ Código de seguridad incorrecto
+ Se requieren permisos adicionales para cargar y descargar archivos.
+ No se encontró una aplicación con la cual establecer la imagen
+ 389 KB
+ marcadordeposición.txt
+ 12:23:45
+ Este es un marcador de posición
+ 2012/05/18 12:23 PM
+ Borrado
+ mantenido en la carpeta original
+ movido a la carpeta de la aplicación
+ Agregar cuenta
+ No ha sido instalado ni F-Droid o Google Play
+ Acerca de
+ Detalles
+ Desarrollo
+ General
+ Más
+ Sincronizar
+ Respaldo diario de tus contactos
+ GNU Licencia Pública General, versión 2
+ Ayuda
+ Exención de responsabilidad
+ El archivo original será…
+ El archivo original será…
+ Usar sub carpetas
+ Licencia
+ Ninguno
+ Administrar cuentas
+ Mostrar archivos ocultos
+ Obtener el código fuente
+ Carpeta local
+ Carpeta remota
+ Tema
+ Oscuro
+ Claro
+ Vista previa de imagen
+ No existe un archivo local a previsualizar
+ No es posible mostrar la imagen
+ Disculpa
+ Privacidad
+ Las notificaciones push están deshabilitadas debido a dependencias de servicios propietarios de Google Play.
+ No hay notificaciones push debido a un inicio de sesión caduco. Por favor vuelve a ingresar a tu cuenta.
+ En este momento las notificaciones push no están disponibles.
+ ¡Prueba %1$s en tu dispositivo!
+ Quiero invitarte a usar %1$s en tu dispositivo\nDescárgalo aquí:%2$s
+ %1$s ó %2$s
+ Volver a cargar
+ Falla al eliminar
+ Eliminar
+ Eliminado
+ Ingresa un nombre nuevo
+ No se pudo renombrar la copia local, intenta con un nombre diferente
+ No fue posible renombrar, el nombre ya está ocupado
+ No se permite volver a compartir
+ No está permitido recompartir
+ No hay una imagen a escala disponible. ¿Descargar la imagen completa?
+ Reintentar
+ Seleccionar todo
+ Enviar
+ Ícono de botón de envío
+ Establecer Como
+ Usar imagen como
+ Compartir
+ Compartiendo
+ Compartir %1$s
+ %1$s (grupo)
+ Compartir liga
+ Debes ingresar una contraseña
+ para compartir este archivo
+ Ingresa una contraseña
+ Establecer fecha de expiración
+ Establecer contraseña
+ Puede editar
+ %1$s (remoto)
+ Configuraciones
+ Compartir liga
+ Compartir con…
+ compartido
+ Ordenar por
+ Ocultar
+ Detalles
+ La identidad del servidor no pudo ser verificada
+ País:
+ Nombre común:
+ Ubicación:
+ Organización:
+ Unidad organizacional:
+ Estado:
+ Huella digital:
+ Emitido por:
+ Firma:
+ Algoritmo:
+ Emitido para:
+ Validez:
+ De:
+ Para:
+ - No hay información acerca del error
+ No fue posible guardar el certificado
+ El certificado no puede ser mostrado.
+ ¿Quieres confiar en este certificado de todas formas?
+ - El certificado del servidor expiró
+ - El certificado del servidor no es de confianza
+ - Las fechas del certificado del servidor están en el futuro
+ - La URL no corresponde con el nombre del servidor en el certificado
+ Mensaje de estado
+ Predeterminado
+ Descargas
+ \"%1$s\" ha sido compartido contigo
+ %1$s ha compartido \"%2$s\" contigo
+ Sincronizar
+ Se encontraron conflictos
+ La carpeta %1$s ya no existe
+ No fue posible sincronizar %1$s
+ Contraseña equivocada para %1$s
+ Falla en archivos mantenidos-en-sincronización.
+ Falla en la sincronización
+ La sincronización falló, inicia sesión de nuevo
+ Los contenidos del archivo ya han sido sincronizados
+ A partir de la versión 1.3.16, los archivos cargados desde este dispositivo serán copiados a la carpeta local %1$s para prevenir pérdidas de datos cuando un archivo se sincroniza entre múltiples cuentas. \n\nDerivado de este cambio, todos los archivos cargados con versiones anteriores de la aplicación fueron copiados a la carpeta %2$s. Sin embargo, un error evitó que se completara esta operación durante la sincronización de la cuenta. Puedes dejar el(los) archivo(s) como está(n) y eliminar la liga a %3$s, o bien, mover el(los) archivo(s) a la carpeta %1$s y mantener la liga a %4$s.\n\nSe enlistan a continuación los archivos locales así como los archivos remotos en %5$s a donde estaban ligados
+ Algunos archivos locales se han perdido
+ Obteniendo la versión más reciente del archivo.
+ Botón de status de sincronización
+ Archivos
+ Botón de configuración
+ Configurar carpetas
+ Las cargas instantáneas se han mejorado por completo. Re-configura tu carga automática desde el menú principal.\n\nDisfruta la nueva y mejorada carga automática.
+ Para %1$s
+ Tipo
+ Etiquetas
+ Probar la conexión del servidor
+ 30 minutos
+ Esta semana
+ Miniatura
+ Hoy
+ Papelera
+ No hay archivos borrados
+ Borrar permanentemente
+ Desestablecer encripción
+ para dejar de compartir este archivo
+ Accede a través de un dominio no de confianza. Por favor consulta la documentación para más información.
+ para actualizar este recurso compartido
+ Borrar cargas fallidas
+ Reintentar cargas fallidas
+ Cargar forma…
+ Cargar contenido de otras aplicaciones
+ Nombre de archivo
+ Tipo de archivo
+ Archivo de acceso directo a Google Maps(%s)
+ Archivo de acceso directo a Internet(%s)
+ Archivo snippet de texto(.txt)
+ Ingresa el nombre y el tipo del archivo a cargar
+ Cargar archivos
+ Botón de cargar elemento
+ Borrar
+ No hay cargas disponibles
+ Carga algún contenido o activa la carga automática
+ El espacio insuficiente evita que se copien los archivos seleccionados dentro de la carpeta %1$s. ¿Te gustaría moverlos ahí en su lugar?
+ Error desconocido
+ Seleccionar
+ Cargar
+ La información recibida no contiene un archivo válido.
+ %1$s no tiene permitido leer un archivo recibido
+ No fue posible copiar el archivo a una carpeta temporal. Por favor intenta enviarlo de nuevo.
+ El archivo seleccionado para cargar no fue encontrado. Por favor verifica si el archivo existe.
+ No hay un archivo a cargar
+ Nombre de la carpeta
+ Selecciona la carpeta de cargas
+ No fue posible cargar %1$s
+ La carga falló, inicia sesión de nuevo
+ Falla en la carga
+ Opción de carga:
+ Mover el archivo a la carpeta %1$s
+ Mantener el archivo en la carpeta de origen
+ Borrar el archivo de la carpeta de origen
+ para cargar a esta carpeta
+ %1$d%% Cargando %2$s
+ Cargando
+ %1$s cargado
+ Salir
+ Ajustes
+ No existen cuentas %1$s en tu dispositivo. Por favor configura una cuenta primero.
+ No se encontró la cuenta
+ Actual
+ Fallido/pendiente de reinicio
+ Cargado
+ Cancelado
+ Esperando para cargar
+ Cargas
+ Cancelado
+ Conflicto
+ Error de conexión
+ Error de credenciales
+ Error de archivo
+ Error de carpeta
+ No se encontró el archivo local
+ Error de permisos
+ El certificado del servidor no es de confianza
+ La aplicación ha sido terminada
+ Completado
+ Error desconocido
+ Virus detectado. ¡La carga no puede ser completada!
+ Esperando a salir de modo de conservación de energía
+ Aguardando la recarga del dispositivo
+ Usuario
+ Dirección
+ Correo electrónico
+ Número telefónico
+ Twitter
+ Sitio web
+ Error al extraer la información del usuario
+ No se ha establecido la información personal
+ Agrega tu nombre, una imagen y detalles de contacto en tu página de perfil.
+ Usuario
+ Descargar
+ Aguarda un momento…
+ Verificando credenciales almacenadas
+ Copiando el archivo desde almacenamiento privado
+ Actualizar
+ Imagen de qué es nuevo
+ Omitir
+ Nuevo en %1$s
+ Descargando archivos…
+ Enviar correo electrónico
+
diff --git a/app/src/main/res/values-es-rDO/strings.xml b/app/src/main/res/values-es-rDO/strings.xml
new file mode 100644
index 000000000000..48de6bf41681
--- /dev/null
+++ b/app/src/main/res/values-es-rDO/strings.xml
@@ -0,0 +1,561 @@
+
+
+ %1$s aplicación Android
+ Acerca de
+ versión %1$s
+ version %1$s, build #%2$s
+ Error al crear la cuenta
+ Ícono de la cuenta
+ ¡No se encontró la cuenta!
+ Editar
+ Borrar todas las notificaciones
+ Enviar/Compartir
+ Vista de cuadrícula
+ Vista de lista
+ Nueva carpeta
+ Abrir con
+ Buscar
+ Detalles
+ Enviar
+ Configuraciones
+ Ordenar
+ Usuario activo
+ Aún no hay actividades
+ Aún no hay eventos como adiciones, cambios y acciones.
+ Enviar
+ Enviar liga a…
+ Actividad
+ Añadir otro enlace
+ Agregar nuevo enlace para compartir público
+ Agregar a %1$s
+ Permitir volver a compartir
+ Buscar en %s
+ Todos
+ Completado
+ Desconocido
+ ¡Cuenta asociada no encontrada!
+ Error de acceso: %1$s
+ La cuenta aún no ha sido agregada a este dispositivo
+ Ya existe una cuenta en el dispositivo para el mismo usuario y servidor
+ El usuario ingresado no corresponde con el usuario de esta cuenta
+ Versión del servidor no reconocida
+ Conexión establecida
+ Su servidor no devuelve una identificación de usuario correcta, comuníquese con un administrador.
+ Dirección del servidor https://…
+ El formato de dirección para el servidor es erróneo
+ El servidor no se encontró
+ No hay conexión de red
+ Conexión segura no disponible.
+ La configuración del servidor está mal formada.
+ Autorización no exitosa
+ Acceso denegado por el servidor de autorización
+ La conexión segura se está redirigiendo a través de una ruta insegura.
+ Conexión segura establecida
+ Falla en la inicialización de SSL
+ No fue posible verificar la identidad del servidor SSL
+ Probando conexión
+ El servidor tardó demasiado en responder
+ Intentando iniciar sesión…
+ Nombre de usuario o contraseña incorrecto
+ Error desconocido: %1$s
+ ¡Se presentó un error HTTP desconocido!
+ ¡Se presentó un error desconocido!
+ No fue posible encontrar el servidor
+ %1$s no soporta cuentas múltiples
+ No fue posible establecer la conexión
+ Guardado en el carpeta original, ya que es solo lectura
+ Solo cargar sobre una Wi-Fi no tarificada
+ /CargaAutomática
+ Configurar
+ Crear nueva configuración de carpeta personalizada
+ Configurar una carpeta personalizada
+ Ocultar carpeta
+ Avatar
+ Lejos
+ Cerrar
+ Deshabilitar
+ Favoritos
+ Todos los archivos
+ Calendario
+ Se presentó un problema al cargar el certificado.
+ Casilla de verificación
+ Elige la carpeta local…
+ Elige la carpeta remota…
+ Texto copiado desde %1$s
+ No se ha recibido texto para copiar al portapapeles
+ Se presentó un error inesperado al copiar al portapapeles
+ Atrás
+ Cancelar
+ Cancelar sincronización
+ Selecciona una cuenta
+ Confirmar
+ Copiar
+ Borrar
+ Error
+ Memoria insuficiente
+ Error desconocido
+ Cargando
+ Siguiente
+ No
+ OK
+ Pendiente
+ Eliminar
+ Renombrar
+ Guardar
+ Enviar
+ Compartir
+ Omitir
+ Sí
+ Probar la versión de desarrollo
+ Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo.
+ foro
+ Ayuda a otros en
+ Contribuye activamente
+ la aplicación
+ Traducir
+ Obtén la liberación de desarrollo directamente
+ Obtén la liberación de desarrollo de la tienda de aplicaciones F-Droid
+ Obtén el candidato a liberación de la tienda de aplicaciones F-Droid
+ Obtén el candidato a liberación de la tienda de Google Play
+ Candidato a lanzamiento
+ El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid.
+ ¿Encontraste una falla? ¿Hay algo raro?
+ Ayúdanos probando
+ Reporta un tema en GitHub
+ Configurar
+ ¿Realmente deseas elminiar %1$s?
+ ¿Realmente deseas borrar los elementos seleccionados?
+ ¿Realmente quieres eliminar %1$s y sus contenidos?
+ ¿Reamente deseas eliminar los elementos seleccionados y sus contenidos?
+ Sólo local
+ Ícono de usuario para la lista de contactos
+ No se han otorgado privilegios, nada fue importado.
+ Contactos
+ Respaldar ahora
+ El respaldo está calendarizado y deberá iniciar en breve
+ La importación está calendarizada y deberá iniciar en breve
+ No se encontraron archivos
+ ¡No fue posible encontrar tu último respaldo!
+ Copiado al portapapeles
+ Se presentó un error al intentar copiar este archivo o carpeta
+ No es posible copiar una carpeta dentro de una de sus sub carpetas
+ El archivo ya existe en la carpeta destino
+ Copiar liga
+ Copiar/ mover a la carpeta encriptada no se encuentra soportado por el momento.
+ Crear
+ No fue posible crear la carpeta
+ Nuevo
+ Carpeta nueva
+ Cerrar sesión
+ Deseleccionar todo
+ Nueva versión disponible
+ No hay información disponible.
+ No hay una nueva versión disponible.
+ Cerrar
+ Este algoritmo de verificación no está disponible en tu teléfono
+ Deshabilitar
+ Descartar
+ Terminado
+ No fue posible descargar %1$s
+ La descarga falló, inicia sesión de nuevo
+ Falla en la descarga
+ El archivo ya no se encuentra disponible en el servidor
+ %1$d%% Descargando %2$s
+ Descargando…
+ %1$s descargado
+ Descargado
+ Aún no ha descargado
+ Imagen de fondo para el encabezado de la barra de navegación
+ Actividades
+ Todos los archivos
+ Favoritos
+ Inicio
+ Notificaciones
+ Disponibles sin conexión
+ Modificado recientemente
+ Compartido
+ Papelera
+ Cargas
+ Cerrar sesión
+ %1$s de %2$s usados
+ Carga automática
+ Hablar
+ Establecer como encriptado
+ Configurar la encripción
+ Desencriptando…
+ Cerrar
+ Por favor ingresa la contraseña para desencriptar la llave privada.
+ Esta carpeta no está vacía.
+ Generando nuevas llaves…
+ Las 12 palabras juntas hacer una contraseña muy fuerte, permitiéndote solo a ti ver y hacer uso de tus archivos encriptados. Por favor escríbelo y mantenlo en un lugar seguro.
+ La encripción de punta-a-punta está dehabilitada en el servidor.
+ Anota tu contraseña de encripción de 12 palabras
+ Contraseña…
+ Recuperando llaves…
+ Almacenando llaves
+ Configurar encripción
+ No se pudieron guardar las llaves, por favor vuelve a intentarlo.
+ Se presentó un error al desencriptar. ¿Contraseña equivocada?
+ %1$s no pudo ser copiado a la carpeta local %2$s
+ Error crítico: No se pueden realizar operaciones
+ Cuentas
+ Creado
+ Usuario
+ Trabajos en segundo plano
+ Descargar
+ Cargar
+ Agregar a tus favoritos
+ Hacer favorito
+ Borrar
+ Archivo
+ Carga algún contenido o sincroniza con tus dispositivos.
+ Aún no hay nada marcado como favorito
+ No hay archivos aquí
+ No hay resultados en esta carpeta
+ No hay resultados
+ No hay nada aquí. Puedes agregar una carpeta.
+ No se encontraron archivos modificados dentro de los últimos 7 días
+ ¿Tal vez está dentro de una carpeta diferente?
+ Aún no hay nada compartido
+ carpeta
+ Cargando…
+ No se cuenta con una aplicación que maneje este tipo de archivo.
+ hace algunos segundos
+ Verificando el destino…
+ Limpiando…
+ La carpeta de datos ya existe. Selecciona una de las siguientes:
+ La carpeta Nextcloud ya existe
+ Se necesita más espacio
+ No fue posible leer el archivo fuente
+ No fue posible escribir al archivo destino
+ Se presentó una falla durante la migración
+ Se presentó una falla al actualizar el índice
+ Moviendo los datos…
+ Terminado
+ Reemplazar
+ Preparando la migración…
+ Restaurando la configuración de la cuenta…
+ Guardando la configuración de la cuenta…
+ ¡No se puede leer la carpeta fuente!
+ Actualizando índice…
+ Usar
+ Aguardando sincronización completa…
+ Archivo no encontrado
+ El archivo no pudo ser sincronizado. Mostrando la última versión disponible.
+ Renombrar
+ Detalles
+ Descargar
+ Exportar
+ El archivo se renombró com %1$s durante la carga
+ Sincronizar
+ No se ha seleccionado algún archivo
+ El nombre del archivo no puede estar en blanco.
+ Caracteres inválidos: / \\ < > : \" | ? *
+ El nombre del archivo contiene al menos un carácter inválido
+ Nombre del archivo
+ Crear
+ No hay carpetas aquí
+ Seleccionar
+ Mover
+ No se te permite %s
+ para copiar este archivo
+ para crear este archivo
+ para borrar este archivo
+ para mover este archivo
+ para renombrar este archivo
+ Cargando archivos…
+ Algunos archivos no se pudieron mover
+ Local: %1$s
+ Mover todos
+ Remoto: %1$s
+ Se han movido todos los archivos
+ Adelante
+ Nombre
+ Contraseña
+ Cargar archivos sólo con el dispositivo conectado a la toma de corriente
+ /Cargas Automáticas
+ Invisible
+ Liga
+ Permitir carga y edición
+ Permitir carga
+ Vista de lista
+ No hay archivos en esta carpeta
+ No se encontró el archivo en el sistema de archivos local
+ No hay más carpetas.
+ %1$s bitácora de aplicación Android
+ Iniciar sesión
+ Actualizar
+ Cargando...
+ Bitácoras
+ Borrar datos
+ Las configuraciones, base de datos y certificados del servidor de los datos de %1$s serán borrados permanentemente.\n\nLos archivos descargados se mantendrán sin cambios.\n\nEste proceso puede tomar algo de tiempo.
+ Administrar espacio
+ No fue posible leer el archivo de medios
+ El archivo de medios tiene una codificación incorrecta
+ El intento de reproducir el archivo sobrepasó el tiempo de espera
+ El reproductor de medios integrado es incapaz de reproducir el archivo de medios
+ Codec no soportado
+ Botón de avanzar rápido
+ Reproductor de música %1$s
+ Botón de reproducir o pausar
+ Botón de rebobinar
+ %1$s (reproduciendo)
+ Más reciente primero
+ Más antiguo primero
+ A - Z
+ Z - A
+ Más grande primero
+ Más pequeño primero
+ Más
+ Se presentó un error al intentar mover este archivo o carpeta
+ No es posible mover una carpeta dentro de una de sus sub carpetas
+ El archivo ya existe en la carpeta de destino
+ Se presentó un error durante la conexión al servidor
+ foto
+ Muestra el progreso de la descarga
+ Descargas
+ Muestra el progreso de la sincronización de archivos y los resultados
+ Sincronizar archivo
+ Progreso del reproductor de música
+ Reproductor de medios
+ Mostrar las notificaciones push enviadas por el servidor: Menciones en comentarios, recepción de nuevos elementos compartidos remotos, anuncios publicados por el administrador, etc.
+ Notificaciones push
+ Muestra el progreso de la carga
+ Cargas
+ No hay notificaciones
+ Por favor verifica más tarde.
+ 1 hora
+ Ingresa tu código de seguridad
+ El código de seguridad será solicitado cada vez que inicie la aplicación
+ Por favor ingresa tu código de seguridad
+ Los códigos de seguridad no son iguales
+ Por favor reingresa el código de seguridad
+ Elimina tu código de seguirdad
+ Código de seguirdad eliminado
+ Código de seguridad almacenado
+ Código de seguridad incorrecto
+ Se requieren permisos adicionales para cargar y descargar archivos.
+ No se encontró una aplicación con la cual establecer la imagen
+ 389 KB
+ marcadordeposición.txt
+ 12:23:45
+ Este es un marcador de posición
+ 2012/05/18 12:23 PM
+ Borrado
+ mantenido en la carpeta original
+ movido a la carpeta de la aplicación
+ Agregar cuenta
+ No ha sido instalado ni F-Droid o Google Play
+ Acerca de
+ Detalles
+ Desarrollo
+ General
+ Más
+ Sincronizar
+ Respaldo diario de tus contactos
+ GNU Licencia Pública General, versión 2
+ Ayuda
+ Exención de responsabilidad
+ El archivo original será…
+ El archivo original será…
+ Usar sub carpetas
+ Licencia
+ Ninguno
+ Administrar cuentas
+ Mostrar archivos ocultos
+ Obtener el código fuente
+ Carpeta local
+ Carpeta remota
+ Tema
+ Oscuro
+ Claro
+ Vista previa de imagen
+ No existe un archivo local a previsualizar
+ No es posible mostrar la imagen
+ Disculpa
+ Privacidad
+ Las notificaciones push están deshabilitadas debido a dependencias de servicios propietarios de Google Play.
+ No hay notificaciones push debido a un inicio de sesión caduco. Por favor vuelve a ingresar a tu cuenta.
+ En este momento las notificaciones push no están disponibles.
+ ¡Prueba %1$s en tu dispositivo!
+ Quiero invitarte a usar %1$s en tu dispositivo\nDescárgalo aquí:%2$s
+ %1$s ó %2$s
+ Volver a cargar
+ Falla al eliminar
+ Eliminar
+ Eliminado
+ Ingresa un nombre nuevo
+ No se pudo renombrar la copia local, intenta con un nombre diferente
+ No fue posible renombrar, el nombre ya está ocupado
+ No hay una imagen a escala disponible. ¿Descargar la imagen completa?
+ Restaurar
+ Reintentar
+ Seleccionar todo
+ Enviar
+ Ícono de botón de envío
+ Establecer Como
+ Usar imagen como
+ Compartir
+ Compartiendo
+ Compartir %1$s
+ %1$s (grupo)
+ Compartir liga
+ Debes ingresar una contraseña
+ para compartir este archivo
+ Ingresa una contraseña
+ Establecer fecha de expiración
+ Establecer contraseña
+ Puede editar
+ %1$s (remoto)
+ Configuraciones
+ Compartir liga
+ Compartir con…
+ compartido
+ Ordenar por
+ Ocultar
+ Detalles
+ La identidad del servidor no pudo ser verificada
+ País:
+ Nombre común:
+ Ubicación:
+ Organización:
+ Unidad organizacional:
+ Estado:
+ Huella digital:
+ Emitido por:
+ Firma:
+ Algoritmo:
+ Emitido para:
+ Validez:
+ De:
+ Para:
+ - No hay información acerca del error
+ No fue posible guardar el certificado
+ El certificado no puede ser mostrado.
+ ¿Quieres confiar en este certificado de todas formas?
+ - El certificado del servidor expiró
+ - El certificado del servidor no es de confianza
+ - Las fechas del certificado del servidor están en el futuro
+ - La URL no corresponde con el nombre del servidor en el certificado
+ Mensaje de estado
+ Predeterminado
+ Descargas
+ Almacenamiento externo
+ \"%1$s\" ha sido compartido contigo
+ %1$s ha compartido \"%2$s\" contigo
+ Sincronizar
+ Se encontraron conflictos
+ La carpeta %1$s ya no existe
+ No fue posible sincronizar %1$s
+ Contraseña equivocada para %1$s
+ Falla en archivos mantenidos-en-sincronización.
+ Falla en la sincronización
+ La sincronización falló, inicia sesión de nuevo
+ Los contenidos del archivo ya han sido sincronizados
+ A partir de la versión 1.3.16, los archivos cargados desde este dispositivo serán copiados a la carpeta local %1$s para prevenir pérdidas de datos cuando un archivo se sincroniza entre múltiples cuentas. \n\nDerivado de este cambio, todos los archivos cargados con versiones anteriores de la aplicación fueron copiados a la carpeta %2$s. Sin embargo, un error evitó que se completara esta operación durante la sincronización de la cuenta. Puedes dejar el(los) archivo(s) como está(n) y eliminar la liga a %3$s, o bien, mover el(los) archivo(s) a la carpeta %1$s y mantener la liga a %4$s.\n\nSe enlistan a continuación los archivos locales así como los archivos remotos en %5$s a donde estaban ligados
+ Algunos archivos locales se han perdido
+ Obteniendo la versión más reciente del archivo.
+ Botón de status de sincronización
+ Archivos
+ Botón de configuración
+ Configurar carpetas
+ Las cargas instantáneas se han mejorado por completo. Re-configura tu carga automática desde el menú principal.\n\nDisfruta la nueva y mejorada carga automática.
+ Para %1$s
+ Tipo
+ Etiquetas
+ Probar la conexión del servidor
+ 30 minutos
+ Esta semana
+ Miniatura
+ Hoy
+ Papelera
+ No hay archivos borrados
+ Borrar permanentemente
+ Desestablecer encripción
+ para dejar de compartir este archivo
+ Accede a través de un dominio no de confianza. Por favor consulta la documentación para más información.
+ para actualizar este recurso compartido
+ Borrar cargas fallidas
+ Reintentar cargas fallidas
+ Cargar forma…
+ Cargar contenido de otras aplicaciones
+ Foto
+ Nombre de archivo
+ Tipo de archivo
+ Archivo de acceso directo a Google Maps(%s)
+ Archivo de acceso directo a Internet(%s)
+ Archivo snippet de texto(.txt)
+ Ingresa el nombre y el tipo del archivo a cargar
+ Cargar archivos
+ Botón de cargar elemento
+ Borrar
+ No hay cargas disponibles
+ Carga algún contenido o activa la carga automática
+ El espacio insuficiente evita que se copien los archivos seleccionados dentro de la carpeta %1$s. ¿Te gustaría moverlos ahí en su lugar?
+ Error desconocido
+ Seleccionar
+ Cargar
+ La información recibida no contiene un archivo válido.
+ %1$s no tiene permitido leer un archivo recibido
+ No fue posible copiar el archivo a una carpeta temporal. Por favor intenta enviarlo de nuevo.
+ El archivo seleccionado para cargar no fue encontrado. Por favor verifica si el archivo existe.
+ No hay un archivo a cargar
+ Nombre de la carpeta
+ Selecciona la carpeta de cargas
+ No fue posible cargar %1$s
+ La carga falló, inicia sesión de nuevo
+ Falla en la carga
+ Opción de carga:
+ Mover el archivo a la carpeta %1$s
+ Mantener el archivo en la carpeta de origen
+ Borrar el archivo de la carpeta de origen
+ para cargar a esta carpeta
+ %1$d%% Cargando %2$s
+ Cargando
+ %1$s cargado
+ Salir
+ Ajustes
+ No existen cuentas %1$s en tu dispositivo. Por favor configura una cuenta primero.
+ No se encontró la cuenta
+ Actual
+ Fallido/pendiente de reinicio
+ Cargado
+ Cancelado
+ Esperando para cargar
+ Cargas
+ Cancelado
+ Conflicto
+ Error de conexión
+ Error de credenciales
+ Error de archivo
+ Error de carpeta
+ No se encontró el archivo local
+ Error de permisos
+ El certificado del servidor no es de confianza
+ La aplicación ha sido terminada
+ Completado
+ Error desconocido
+ Virus detectado. ¡La carga no puede ser completada!
+ Esperando a salir de modo de conservación de energía
+ Aguardando la recarga del dispositivo
+ Usuario
+ Dirección
+ Correo electrónico
+ Número telefónico
+ Twitter
+ Sitio web
+ Error al extraer la información del usuario
+ No se ha establecido la información personal
+ Agrega tu nombre, una imagen y detalles de contacto en tu página de perfil.
+ Usuario
+ Descargar
+ Aguarda un momento…
+ Verificando credenciales almacenadas
+ Copiando el archivo desde almacenamiento privado
+ Actualizar
+ Imagen de qué es nuevo
+ Omitir
+ Nuevo en %1$s
+ Descargando archivos…
+ Enviar correo electrónico
+
diff --git a/app/src/main/res/values-es-rGT/strings.xml b/app/src/main/res/values-es-rGT/strings.xml
new file mode 100644
index 000000000000..c47d5491337e
--- /dev/null
+++ b/app/src/main/res/values-es-rGT/strings.xml
@@ -0,0 +1,544 @@
+
+
+ %1$s aplicación Android
+ Acerca de
+ versión %1$s
+ Ícono de la cuenta
+ ¡No se encontró la cuenta!
+ Editar
+ Enviar/Compartir
+ Vista de cuadrícula
+ Vista de lista
+ Nueva carpeta
+ Abrir con
+ Buscar
+ Detalles
+ Enviar
+ Configuraciones
+ Ordenar
+ Usuario activo
+ Aún no hay actividades
+ Enviar
+ Enviar liga a…
+ Actividad
+ Agregar a %1$s
+ Permitir volver a compartir
+ Todos
+ Completado
+ Desconocido
+ La cuenta aún no ha sido agregada a este dispositivo
+ Ya existe una cuenta en el dispositivo para el mismo usuario y servidor
+ El usuario ingresado no corresponde con el usuario de esta cuenta
+ Versión del servidor no reconocida
+ Conexión establecida
+ Dirección del servidor https://…
+ El formato de dirección para el servidor es erróneo
+ El servidor no se encontró
+ No hay conexión de red
+ Conexión segura no disponible.
+ La configuración del servidor está mal formada.
+ Autorización no exitosa
+ Acceso denegado por el servidor de autorización
+ La conexión segura se está redirigiendo a través de una ruta insegura.
+ Conexión segura establecida
+ Falla en la inicialización de SSL
+ No fue posible verificar la identidad del servidor SSL
+ Probando conexión
+ El servidor tardó demasiado en responder
+ Intentando iniciar sesión…
+ Nombre de usuario o contraseña incorrecto
+ Error desconocido: %1$s
+ ¡Se presentó un error HTTP desconocido!
+ ¡Se presentó un error desconocido!
+ No fue posible encontrar el servidor
+ %1$s no soporta cuentas múltiples
+ No fue posible establecer la conexión
+ Solo cargar sobre una Wi-Fi no tarificada
+ /CargaAutomática
+ Crear nueva configuración de carpeta personalizada
+ Configurar una carpeta personalizada
+ Avatar
+ Cerrar
+ Deshabilitar
+ Calendario
+ Se presentó un problema al cargar el certificado.
+ Casilla de verificación
+ Elige la carpeta local…
+ Elige la carpeta remota…
+ Texto copiado desde %1$s
+ No se ha recibido texto para copiar al portapapeles
+ Se presentó un error inesperado al copiar al portapapeles
+ Atrás
+ Cancelar
+ Cancelar sincronización
+ Selecciona una cuenta
+ Confirmar
+ Copiar
+ Borrar
+ Error
+ Memoria insuficiente
+ Error desconocido
+ Cargando
+ Siguiente
+ No
+ OK
+ Pendiente
+ Eliminar
+ Renombrar
+ Guardar
+ Enviar
+ Compartir
+ Omitir
+ Sí
+ Probar la versión de desarrollo
+ Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo.
+ foro
+ Ayuda a otros en
+ Contribuye activamente
+ la aplicación
+ Traducir
+ Obten la liberación de desarrollo directamente
+ Obten la liberación de desarrollo de la tienda de aplicaciones F-Droid
+ Obten el candidato a liberación de la tienda de aplicaciones F-Droid
+ Obten el candidato a liberación de la tienda de Google Play
+ Candidato a lanzamiento
+ El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid.
+ ¿Encontraste una falla? ¿Hay algo raro?
+ Ayúdanos probando
+ Reporta un tema en GitHub
+ ¿Realmente deseas elminiar %1$s?
+ ¿Realmente deseas borrar los elementos seleccionados?
+ ¿Realmente quieres eliminar %1$s y sus contenidos?
+ ¿Reamente deseas eliminar los elementos seleccionados y sus contenidos?
+ Sólo local
+ Ícono de usuario para la lista de contactos
+ No se han otorgado privilegios, nada fue importado.
+ Contactos
+ Respaldar ahora
+ El respaldo está calendarizado y deberá iniciar en breve
+ La importación está calendarizada y deberá iniciar en breve
+ No se encontraron archivos
+ ¡No fue posible encontrar tu último respaldo!
+ Copiado al portapapeles
+ Se presentó un error al intentar copiar este archivo o carpeta
+ No es posible copiar una carpeta dentro de una de sus sub carpetas
+ El archivo ya existe en la carpeta destino
+ Copiar liga
+ Copiar/ mover a la carpeta encriptada no se encuentra soportado por el momento.
+ Crear
+ No fue posible crear la carpeta
+ Nuevo
+ Carpeta nueva
+ Cerrar sesión
+ Deseleccionar todo
+ Nueva versión disponible
+ No hay información disponible.
+ No hay una nueva versión disponible.
+ Cerrar
+ Este algoritmo de verificación no está disponible en tu teléfono
+ Deshabilitar
+ Descartar
+ Terminado
+ No fue posible descargar %1$s
+ La descarga falló, inicia sesión de nuevo
+ Falla en la descarga
+ El archivo ya no se encuentra disponible en el servidor
+ %1$d%% Descargando %2$s
+ Descargando…
+ %1$s descargado
+ Descargado
+ Aún no ha descargado
+ Imagen de fondo para el encabezado de la barra de navegación
+ Actividades
+ Todos los archivos
+ Favoritos
+ Inicio
+ Notificaciones
+ Disponibles sin conexión
+ Modificado recientemente
+ Compartido
+ Papelera
+ Cargas
+ Cerrar sesión
+ %1$s de %2$s usados
+ Carga automática
+ Hablar
+ Establecer como encriptado
+ Configurar la encripción
+ Decriptando…
+ Cerrar
+ Por favor ingresa la contraseña para decrpitar la llave privada.
+ Esta carpeta no está vacía.
+ Generando nuevas llaves…
+ Las 12 palabras juntas hacer una contraseña muy fuerte, permitiendote solo a ti ver y hacer uso de tus archivos encriptados. Por favor escríbelo y mantenlo en un lugar seguro.
+ La encripción de punta-a-punta está dehabilitada en el servidor.
+ Anota tu contraseña de encripción de 12 palabras
+ Contraseña…
+ Recuperando llaves…
+ Almacenando llaves
+ Configurar encripción
+ No se pudieron guardar las llaves, por favor vuelve a intentarlo.
+ Se presentó un error al decriptar. ¿Contraseña equivocada?
+ %1$s no pudo ser copiado a la carpeta local %2$s
+ Error crítico: No se pueden realizar operaciones
+ Cuentas
+ Creado
+ Ususario
+ Trabajos en segundo plano
+ Descargar
+ Cargar
+ Agregar a tus favoritos
+ Hacer favorito
+ Borrar
+ Archivo
+ Carga algún contenido o sincroniza con tus dispositivos.
+ Aún no hay nada marcado como favorito
+ No hay archivos aquí
+ No hay resultados en esta carpeta
+ No hay resultados
+ No hay nada aquí. Puedes agregar una carpeta.
+ No se encontraron archivos modificados dentro de los últimos 7 días
+ ¿Tal vez está dentro de una carpeta diferente?
+ Aún no hay nada compartido
+ carpeta
+ Cargando…
+ No se cuenta con una aplicación que maneje este tipo de achivo.
+ hace algunos segundos
+ Verificando el destino…
+ Limpiando…
+ La carpeta de datos ya existe. Selecciona una de las siguientes:
+ La carpeta Nextcloud ya existe
+ Se necesita más espacio
+ No fue posible leer el archivo fuente
+ No fue posible escribir al archivo destino
+ Se presentó una falla durante la migración
+ Se presentó una falla al actualizar el índice
+ Moviendo los datos…
+ Terminado
+ Reemplazar
+ Preparando la migración…
+ Restaurando la configuración de la cuenta…
+ Guardando la configuración de la cuenta…
+ ¡No se puede leer la carpeta fuente!
+ Actualizando índice…
+ Usar
+ Agurardando sincronización completa…
+ Archivo no encontrado
+ El archivo no pudo ser sincronizado. Mostrando la última versión disponible.
+ Renombrar
+ Detalles
+ Descargar
+ Exportar
+ El archivo se renombró com %1$s durante la carga
+ Syncronizar
+ No se ha seleccionado algún archivo
+ El nombre del archivo no puede estar en blanco.
+ Caracteres inválidos: / \\ < > : \" | ? *
+ El nombre del archivo contiene al menos un carácter inválido
+ Nombre del archivo
+ Crear
+ No hay carpetas aquí
+ Seleccionar
+ Mover
+ No se te permite %s
+ para copiar este archivo
+ para crear este archivo
+ para borrar este archivo
+ para mover este archivo
+ para renombrar este archivo
+ Cargando archivos…
+ Algunos archivos no se pudieron mover
+ Local: %1$s
+ Mover todos
+ Remoto: %1$s
+ Se han movido todos los archivos
+ Adelante
+ Nombre
+ Contraseña
+ Cargar archivos sólo con el dispositivo conectado a la toma de corriente
+ /Cargas Automáticas
+ Invisible
+ Liga
+ Permitir carga y edición
+ Permitir carga
+ Vista de lista
+ No hay archivos en esta carpeta
+ No se encontró el archivo en el sistema de archivos local
+ No hay más carpetas.
+ %1$s bitácora de aplicación Android
+ Iniciar sesión
+ Actualizar
+ Cargando...
+ Bitácoras
+ Borrar datos
+ Las configuraciones, base de datos y certificados del servior de los datos de %1$s serán borrados permanentemente.\n\nLos archivos descargados se mantendrán sin cambios.\n\nEste proceso puede tomar algo de tiempo.
+ Administrar espacio
+ No fue posible leer el archivo de medios
+ El archivo de medios tiene una codificación incorrecta
+ El intento de reproducir el archivo sobrepasó el tiempo de espera
+ El reproductor de medios integrado es incapaz de reproducir el archivo de medios
+ Codec no soportado
+ Botón de avanzar rápido
+ Reproductor de músca %1$s
+ Botón de reproducir o pausar
+ Botón de rebobinar
+ %1$s (reproduciendo)
+ Más reciente primero
+ Más antiguo primero
+ A - Z
+ Z - A
+ Más grande primero
+ Más pequeño primero
+ Más
+ Se presentó un error al intentar mover este archivo o carpeta
+ No es posible mover una carpeta dentro de una de sus sub carpetas
+ El archivo ya existe en la carpeta de destino
+ Se presentó un error durante la conexión al servidor
+ foto
+ Muestra el progreso de la descarga
+ Descargas
+ Muestra el progreso de la sincronización de archivos y los resultados
+ Sincronizar archivo
+ Progreso del reproductor de música
+ Reproductor de medios
+ Mostrar las notificaciones push enviadas por el servidor: Menciones en comentarios, recepción de nuevos elementos compartidos remotos, anuncios publicados por el administrador, etc.
+ Notificaciones push
+ Muestra el progreso de la carga
+ Cargas
+ No hay notificaciones
+ Por favor verifica más tarde.
+ 1 hora
+ Ingresa tu código de seguridad
+ El código de seguridad será solicitado cada vez que inicie la aplicación
+ Por favor ingresa tu código de seguridad
+ Los códigos de seguridad no son iguales
+ Por favor reingresa el código de seguridad
+ Elimina tu código de seguirdad
+ Código de seguirdad eliminado
+ Código de seguridad almacenado
+ Código de seguridad incorrecto
+ Se requieren permisos adicionales para cargar y descargar archivos.
+ No se encontró una aplicación con la cual establecer la imagen
+ 389 KB
+ marcadordeposición.txt
+ 12:23:45
+ Este es un marcador de posición
+ 2012/05/18 12:23 PM
+ Borrado
+ mantenido en la carpeta original
+ movido a la capreta de la aplicación
+ Agregar cuenta
+ No ha sido instalado ni F-Droid o Google Play
+ Acerca de
+ Detalles
+ Desarrollo
+ General
+ Más
+ Syncronizar
+ Respaldo diario de tus contactos
+ GNU Licencia Pública General, versión 2
+ Ayuda
+ Excención de responsabilidad
+ El archivo original será…
+ El archivo original será…
+ Usar sub carpetas
+ Licencia
+ Ninguno
+ Administrar cuentas
+ Mostrar archivos ocultos
+ Obtener el código fuente
+ Carpeta local
+ Carpeta remota
+ Tema
+ Oscuro
+ Claro
+ Vista previa de imagen
+ No existe un archivo local a previsualizar
+ No es posible mostrar la imagen
+ Disculpa
+ Privacidad
+ Las notificaciones push están deshabilitadas debido a dependencias de servicios propietarios de Google Play.
+ No hay notificaciones push debido a un incio de sesión caduco. Por favor vuelve a ingresar a tu cuenta.
+ En este momento las notificaciones push no están disponibles.
+ ¡Prueba %1$s en tu dispositivo!
+ Quiero invitarte a usar %1$s en tu dispositivo\nDescargalo aquí:%2$s
+ %1$s ó %2$s
+ Volver a cargar
+ Falla al eliminar
+ Eliminar
+ Eliminado
+ Ingresa un nombre nuevo
+ No se pudo renombrar la copia local, intenta con un nombre diferente
+ No fue posible renombrar, el nombre ya esta ocupado
+ No se permite volver a compartir
+ No está permitido recompartir
+ No hay una imagen a escala disponible. ¿Descargar la imagen completa?
+ Reintentar
+ Seleccionar todo
+ Enviar
+ Ícono de botón de envío
+ Establecer Como
+ Usar imagen como
+ Compartir
+ Compartiendo
+ Compartir %1$s
+ %1$s (grupo)
+ Compartir liga
+ Debes ingresar una contraseña
+ para compartir este archivo
+ Ingresa una contraseña
+ Establecer fecha de expiración
+ Establecer contraseña
+ Puede editar
+ %1$s (remoto)
+ Configuraciones
+ Compartir liga
+ Compartir con…
+ compartido
+ Ordenar por
+ Ocultar
+ Detalles
+ La identidad del servidor no pudo ser verificada
+ País:
+ Nombre común:
+ Ubicación:
+ Organización:
+ Unidad organizacional:
+ Estado:
+ Huella digital:
+ Emitido por:
+ Firma:
+ Algoritmo:
+ Emitido para:
+ Validez:
+ De:
+ Para:
+ - No hay información acerca del error
+ No fue posible guardar el certificado
+ El certificado no puede ser mostrado.
+ ¿Quieres confiar en este certificado de todas formas?
+ - El certificado del servidor expiró
+ - El certificado del servidor no es de confianza
+ - Las fechas del certificado del servidor están en el futuro
+ - La URL no corresponde con el nombre del servidor en el certificado
+ Mensaje de estado
+ Predeterminado
+ Descargas
+ \"%1$s\" ha sido compartido contigo
+ %1$s ha compartido \"%2$s\" contigo
+ Syncronizar
+ Se encontraron conflictos
+ La carpeta %1$s ya no existe
+ No fue posible sincronizar %1$s
+ Contraseña equivocada para %1$s
+ Falla en archivos mantenidos-en-sincronización.
+ Falla en la sincronización
+ La sincronización falló, inicia sesión de nuevo
+ Los contenidos del archivo ya han sido sincronizados
+ A partir de la versión 1.3.16, los archivos cargados desde este dispositivo serán copiados a la carpeta local %1$spara prevenir perdidas de datos cuando un archivo se sincroniza entre múltiples cuentas. \n\nDerivado de este cambio, todos los archivos cargados con versiones anteriores de la aplicación fueron copiados a la carpeta %2$s. Sin embargo, un error evitó que se completara esta operación durante la sincronizacion de la cuenta. Puedes dejar el(los) archivo(s) como está(n) y eliminar la liga a %3$s, o bien, mover el(los) archivo(s) a la carpeta%1$s y mantener la liga a %4$s.\n\nSe enlistan a continuación los archivos locales así como los archivos remotos en %5$s a donde estaban ligados
+ Algunos archivos locales se han perdido
+ Obteniendo la versión más reciente del archivo.
+ Botón de status de sincronización
+ Archivos
+ Botón de configuración
+ Configurar carpetas
+ Las cargas instantaneas se han mejorado por completo. Re-configura tu carga automática desde el menú principal.\n\nDisfruta la nueva y mejorada carga automática.
+ Para %1$s
+ Tipo
+ Etiquetas
+ Probar la conexión del servidor
+ 30 minutos
+ Esta semana
+ Miniatura
+ Hoy
+ Papelera
+ No hay archivos borrados
+ Borrar permanentemente
+ Desestablecer encripción
+ para dejar de compartir este archivo
+ Accede a través de un dominio no de confianza. Por favor consulta la documentación para más información.
+ para actualizar este recurso compartido
+ Borrar cargas fallidas
+ Reintentar cargas fallidas
+ Cargar forma…
+ Cargar contenido de otras aplicaciones
+ Foto
+ Nombre de archivo
+ Tipo de archivo
+ Archivo de acceso directo a Google Maps(%s)
+ Archivo de acceso directo a Internet(%s)
+ Archivo snippet de texto(.txt)
+ Ingresa el nombre y el tipo del archivo a cargar
+ Cargar archivos
+ Botón de cargar elemento
+ Borrar
+ No hay cargas disponibles
+ Carga algún contenido o activa la carga automática
+ El espacio insuficiente evita que se copien los archivos seleccionados dentro de la carpeta %1$s. ¿Te gustaría moverlos ahí en su lugar?
+ Error desconocido
+ Seleccionar
+ Cargar
+ La información recibida no contiene un archivo válido.
+ %1$s no tiene permitido leer un archivo recibido
+ No fue posible copiar el archivo a una carpeta temporal. Por favor intenta enviarlo de nuevo.
+ El archivo seleccionado para cargar no fue encontrado. Por favor verifica si el archivo existe.
+ No hay un archivo a cargar
+ Nombre de la carpeta
+ Selecciona la carpeta de cargas
+ No fue posible cargar %1$s
+ La carga falló, inicia sesión de nuevo
+ Falla en la carga
+ Opción de carga:
+ Mover el archivo a la carpeta %1$s
+ Mantener el archivo en la carpeta de origen
+ Borrar el archivo de la carpeta de origen
+ para cargar a esta carpeta
+ %1$d%% Cargando %2$s
+ Cargando
+ %1$s cargado
+ Salir
+ Ajustes
+ No existen cuentas %1$s en tu dispositivo. Por favor configura una cuenta primero.
+ No se encontró la cuenta
+ Actual
+ Fallido/pendiente de reinicio
+ Cargado
+ Cancelado
+ Esperando para cargar
+ Cargas
+ Cancelado
+ Conflicto
+ Error de conexión
+ Error de credenciales
+ Error de archivo
+ Error de carpeta
+ No se encontró el archivo local
+ Error de permisos
+ El certificado del servidor no es de confianza
+ La aplicación ha sido terminada
+ Completado
+ Error desconocido
+ Virus detectado. ¡La carga no puede ser completada!
+ Esperando a salir de modo de conservación de energía
+ Aguardando la recarga del dispositivo
+ Ususario
+ Dirección
+ Correo electrónico
+ Número telefónico
+ Twitter
+ Sitio web
+ Error al extraer la información del usuario
+ No se ha establecido la información personal
+ Agrega tu nombre, una imagen y detalles de contacto en tu página de perfil.
+ Usuario
+ Descargar
+ Agurarda un momento…
+ Verificando credenciales almacenadas
+ Copiando el archivo desde almacenamiento privado
+ Actualizar
+ Imagen de qué es nuevo
+ Omitir
+ Nuevo en %1$s
+ Descargando archivos…
+ Enviar correo electrónico
+
diff --git a/app/src/main/res/values-es-rSV/strings.xml b/app/src/main/res/values-es-rSV/strings.xml
new file mode 100644
index 000000000000..6c80a6436bde
--- /dev/null
+++ b/app/src/main/res/values-es-rSV/strings.xml
@@ -0,0 +1,544 @@
+
+
+ %1$s aplicación Android
+ Acerca de
+ versión %1$s
+ Ícono de la cuenta
+ ¡No se encontró la cuenta!
+ Editar
+ Enviar/Compartir
+ Vista de cuadrícula
+ Vista de lista
+ Nueva carpeta
+ Abrir con
+ Buscar
+ Detalles
+ Enviar
+ Configuraciones
+ Ordenar
+ Usuario activo
+ Aún no hay actividades
+ Enviar
+ Enviar liga a…
+ Actividad
+ Agregar a %1$s
+ Permitir volver a compartir
+ Todos
+ Completado
+ Desconocido
+ La cuenta aún no ha sido agregada a este dispositivo
+ Ya existe una cuenta en el dispositivo para el mismo usuario y servidor
+ El usuario ingresado no corresponde con el usuario de esta cuenta
+ Versión del servidor no reconocida
+ Conexión establecida
+ Dirección del servidor https://…
+ El formato de dirección para el servidor es erróneo
+ El servidor no se encontró
+ No hay conexión de red
+ Conexión segura no disponible.
+ La configuración del servidor está mal formada.
+ Autorización no exitosa
+ Acceso denegado por el servidor de autorización
+ La conexión segura se está redirigiendo a través de una ruta insegura.
+ Conexión segura establecida
+ Falla en la inicialización de SSL
+ No fue posible verificar la identidad del servidor SSL
+ Probando conexión
+ El servidor tardó demasiado en responder
+ Intentando iniciar sesión…
+ Nombre de usuario o contraseña incorrecto
+ Error desconocido: %1$s
+ ¡Se presentó un error HTTP desconocido!
+ ¡Se presentó un error desconocido!
+ No fue posible encontrar el servidor
+ %1$s no soporta cuentas múltiples
+ No fue posible establecer la conexión
+ Solo cargar sobre una Wi-Fi no tarificada
+ /CargaAutomática
+ Crear nueva configuración de carpeta personalizada
+ Configurar una carpeta personalizada
+ Avatar
+ Cerrar
+ Deshabilitar
+ Calendario
+ Se presentó un problema al cargar el certificado.
+ Casilla de verificación
+ Elige la carpeta local…
+ Elige la carpeta remota…
+ Texto copiado desde %1$s
+ No se ha recibido texto para copiar al portapapeles
+ Se presentó un error inesperado al copiar al portapapeles
+ Atrás
+ Cancelar
+ Cancelar sincronización
+ Selecciona una cuenta
+ Confirmar
+ Copiar
+ Borrar
+ Error
+ Memoria insuficiente
+ Error desconocido
+ Cargando
+ Siguiente
+ No
+ OK
+ Pendiente
+ Eliminar
+ Renombrar
+ Guardar
+ Enviar
+ Compartir
+ Omitir
+ Sí
+ Probar la versión de desarrollo
+ Esto incluye todas las últimas funcionalidades y es lo más nuevo. Fallas/errores pueden ocurrir y si es el caso, por favor repórtanoslo.
+ foro
+ Ayuda a otros en
+ Contribuye activamente
+ la aplicación
+ Traducir
+ Obtén la liberación de desarrollo directamente
+ Obtén la liberación de desarrollo de la tienda de aplicaciones F-Droid
+ Obtén el candidato a liberación de la tienda de aplicaciones F-Droid
+ Obtén el candidato a liberación de la tienda de Google Play
+ Candidato a lanzamiento
+ El candidato a lanzamiento (RC) es una foto del lanzamiento más próximo y se espera que sea estable. Hacer preubas en tu configuración individual puede ayudarnos a asegurar ésto. Anótate para probar en la Play store o consulta manualmente la sección de \"Versión\" de F-Droid.
+ ¿Encontraste una falla? ¿Hay algo raro?
+ Ayúdanos probando
+ Reporta un tema en GitHub
+ ¿Realmente deseas elminiar %1$s?
+ ¿Realmente deseas borrar los elementos seleccionados?
+ ¿Realmente quieres eliminar %1$s y sus contenidos?
+ ¿Reamente deseas eliminar los elementos seleccionados y sus contenidos?
+ Sólo local
+ Ícono de usuario para la lista de contactos
+ No se han otorgado privilegios, nada fue importado.
+ Contactos
+ Respaldar ahora
+ El respaldo está calendarizado y deberá iniciar en breve
+ La importación está calendarizada y deberá iniciar en breve
+ No se encontraron archivos
+ ¡No fue posible encontrar tu último respaldo!
+ Copiado al portapapeles
+ Se presentó un error al intentar copiar este archivo o carpeta
+ No es posible copiar una carpeta dentro de una de sus sub carpetas
+ El archivo ya existe en la carpeta destino
+ Copiar liga
+ Copiar/ mover a la carpeta encriptada no se encuentra soportado por el momento.
+ Crear
+ No fue posible crear la carpeta
+ Nuevo
+ Carpeta nueva
+ Cerrar sesión
+ Deseleccionar todo
+ Nueva versión disponible
+ No hay información disponible.
+ No hay una nueva versión disponible.
+ Cerrar
+ Este algoritmo de verificación no está disponible en tu teléfono
+ Deshabilitar
+ Descartar
+ Terminado
+ No fue posible descargar %1$s
+ La descarga falló, inicia sesión de nuevo
+ Falla en la descarga
+ El archivo ya no se encuentra disponible en el servidor
+ %1$d%% Descargando %2$s
+ Descargando…
+ %1$s descargado
+ Descargado
+ Aún no ha descargado
+ Imagen de fondo para el encabezado de la barra de navegación
+ Actividades
+ Todos los archivos
+ Favoritos
+ Inicio
+ Notificaciones
+ Disponibles sin conexión
+ Modificado recientemente
+ Compartido
+ Papelera
+ Cargas
+ Cerrar sesión
+ %1$s de %2$s usados
+ Carga automática
+ Hablar
+ Establecer como encriptado
+ Configurar la encripción
+ Desencriptando…
+ Cerrar
+ Por favor ingresa la contraseña para desencriptar la llave privada.
+ Esta carpeta no está vacía.
+ Generando nuevas llaves…
+ Las 12 palabras juntas hacer una contraseña muy fuerte, permitiéndote solo a ti ver y hacer uso de tus archivos encriptados. Por favor escríbelo y mantenlo en un lugar seguro.
+ La encripción de punta-a-punta está dehabilitada en el servidor.
+ Anota tu contraseña de encripción de 12 palabras
+ Contraseña…
+ Recuperando llaves…
+ Almacenando llaves
+ Configurar encripción
+ No se pudieron guardar las llaves, por favor vuelve a intentarlo.
+ Se presentó un error al desencriptar. ¿Contraseña equivocada?
+ %1$s no pudo ser copiado a la carpeta local %2$s
+ Error crítico: No se pueden realizar operaciones
+ Cuentas
+ Creado
+ Usuario
+ Trabajos en segundo plano
+ Descargar
+ Cargar
+ Agregar a tus favoritos
+ Hacer favorito
+ Borrar
+ Archivo
+ Carga algún contenido o sincroniza con tus dispositivos.
+ Aún no hay nada marcado como favorito
+ No hay archivos aquí
+ No hay resultados en esta carpeta
+ No hay resultados
+ No hay nada aquí. Puedes agregar una carpeta.
+ No se encontraron archivos modificados dentro de los últimos 7 días
+ ¿Tal vez está dentro de una carpeta diferente?
+ Aún no hay nada compartido
+ carpeta
+ Cargando…
+ No se cuenta con una aplicación que maneje este tipo de archivo.
+ hace algunos segundos
+ Verificando el destino…
+ Limpiando…
+ La carpeta de datos ya existe. Selecciona una de las siguientes:
+ La carpeta Nextcloud ya existe
+ Se necesita más espacio
+ No fue posible leer el archivo fuente
+ No fue posible escribir al archivo destino
+ Se presentó una falla durante la migración
+ Se presentó una falla al actualizar el índice
+ Moviendo los datos…
+ Terminado
+ Reemplazar
+ Preparando la migración…
+ Restaurando la configuración de la cuenta…
+ Guardando la configuración de la cuenta…
+ ¡No se puede leer la carpeta fuente!
+ Actualizando índice…
+ Usar
+ Aguardando sincronización completa…
+ Archivo no encontrado
+ El archivo no pudo ser sincronizado. Mostrando la última versión disponible.
+ Renombrar
+ Detalles
+ Descargar
+ Exportar
+ El archivo se renombró com %1$s durante la carga
+ Sincronizar
+ No se ha seleccionado algún archivo
+ El nombre del archivo no puede estar en blanco.
+ Caracteres inválidos: / \\ < > : \" | ? *
+ El nombre del archivo contiene al menos un carácter inválido
+ Nombre del archivo
+ Crear
+ No hay carpetas aquí
+ Seleccionar
+ Mover
+ No se te permite %s
+ para copiar este archivo
+ para crear este archivo
+ para borrar este archivo
+ para mover este archivo
+ para renombrar este archivo
+ Cargando archivos…
+ Algunos archivos no se pudieron mover
+ Local: %1$s
+ Mover todos
+ Remoto: %1$s
+ Se han movido todos los archivos
+ Adelante
+ Nombre
+ Contraseña
+ Cargar archivos sólo con el dispositivo conectado a la toma de corriente
+ /Cargas Automáticas
+ Invisible
+ Liga
+ Permitir carga y edición
+ Permitir carga
+ Vista de lista
+ No hay archivos en esta carpeta
+ No se encontró el archivo en el sistema de archivos local
+ No hay más carpetas.
+ %1$s bitácora de aplicación Android
+ Iniciar sesión
+ Actualizar
+ Cargando...
+ Bitácoras
+ Borrar datos
+ Las configuraciones, base de datos y certificados del servidor de los datos de %1$s serán borrados permanentemente.\n\nLos archivos descargados se mantendrán sin cambios.\n\nEste proceso puede tomar algo de tiempo.
+ Administrar espacio
+ No fue posible leer el archivo de medios
+ El archivo de medios tiene una codificación incorrecta
+ El intento de reproducir el archivo sobrepasó el tiempo de espera
+ El reproductor de medios integrado es incapaz de reproducir el archivo de medios
+ Codec no soportado
+ Botón de avanzar rápido
+ Reproductor de música %1$s
+ Botón de reproducir o pausar
+ Botón de rebobinar
+ %1$s (reproduciendo)
+ Más reciente primero
+ Más antiguo primero
+ A - Z
+ Z - A
+ Más grande primero
+ Más pequeño primero
+ Más
+ Se presentó un error al intentar mover este archivo o carpeta
+ No es posible mover una carpeta dentro de una de sus sub carpetas
+ El archivo ya existe en la carpeta de destino
+ Se presentó un error durante la conexión al servidor
+ foto
+ Muestra el progreso de la descarga
+ Descargas
+ Muestra el progreso de la sincronización de archivos y los resultados
+ Sincronizar archivo
+ Progreso del reproductor de música
+ Reproductor de medios
+ Mostrar las notificaciones push enviadas por el servidor: Menciones en comentarios, recepción de nuevos elementos compartidos remotos, anuncios publicados por el administrador, etc.
+ Notificaciones push
+ Muestra el progreso de la carga
+ Cargas
+ No hay notificaciones
+ Por favor verifica más tarde.
+ 1 hora
+ Ingresa tu código de seguridad
+ El código de seguridad será solicitado cada vez que inicie la aplicación
+ Por favor ingresa tu código de seguridad
+ Los códigos de seguridad no son iguales
+ Por favor reingresa el código de seguridad
+ Elimina tu código de seguirdad
+ Código de seguirdad eliminado
+ Código de seguridad almacenado
+ Código de seguridad incorrecto
+ Se requieren permisos adicionales para cargar y descargar archivos.
+ No se encontró una aplicación con la cual establecer la imagen
+ 389 KB
+ marcadordeposición.txt
+ 12:23:45
+ Este es un marcador de posición
+ 2012/05/18 12:23 PM
+ Borrado
+ mantenido en la carpeta original
+ movido a la carpeta de la aplicación
+ Agregar cuenta
+ No ha sido instalado ni F-Droid o Google Play
+ Acerca de
+ Detalles
+ Desarrollo
+ General
+ Más
+ Sincronizar
+ Respaldo diario de tus contactos
+ GNU Licencia Pública General, versión 2
+ Ayuda
+ Exención de responsabilidad
+ El archivo original será…
+ El archivo original será…
+ Usar sub carpetas
+ Licencia
+ Ninguno
+ Administrar cuentas
+ Mostrar archivos ocultos
+ Obtener el código fuente
+ Carpeta local
+ Carpeta remota
+ Tema
+ Oscuro
+ Claro
+ Vista previa de imagen
+ No existe un archivo local a previsualizar
+ No es posible mostrar la imagen
+ Disculpa
+ Privacidad
+ Las notificaciones push están deshabilitadas debido a dependencias de servicios propietarios de Google Play.
+ No hay notificaciones push debido a un inicio de sesión caduco. Por favor vuelve a ingresar a tu cuenta.
+ En este momento las notificaciones push no están disponibles.
+ ¡Prueba %1$s en tu dispositivo!
+ Quiero invitarte a usar %1$s en tu dispositivo\nDescárgalo aquí:%2$s
+ %1$s ó %2$s
+ Volver a cargar
+ Falla al eliminar
+ Eliminar
+ Eliminado
+ Ingresa un nombre nuevo
+ No se pudo renombrar la copia local, intenta con un nombre diferente
+ No fue posible renombrar, el nombre ya está ocupado
+ No se permite volver a compartir
+ No está permitido recompartir
+ No hay una imagen a escala disponible. ¿Descargar la imagen completa?
+ Reintentar
+ Seleccionar todo
+ Enviar
+ Ícono de botón de envío
+ Establecer Como
+ Usar imagen como
+ Compartir
+ Compartiendo
+ Compartir %1$s
+ %1$s (grupo)
+ Compartir liga
+ Debes ingresar una contraseña
+ para compartir este archivo
+ Ingresa una contraseña
+ Establecer fecha de expiración
+ Establecer contraseña
+ Puede editar
+ %1$s (remoto)
+ Configuraciones
+ Compartir liga
+ Compartir con…
+ compartido
+ Ordenar por
+ Ocultar
+ Detalles
+ La identidad del servidor no pudo ser verificada
+ País:
+ Nombre común:
+ Ubicación:
+ Organización:
+ Unidad organizacional:
+ Estado:
+ Huella digital:
+ Emitido por:
+ Firma:
+ Algoritmo:
+ Emitido para:
+ Validez:
+ De:
+ Para:
+ - No hay información acerca del error
+ No fue posible guardar el certificado
+ El certificado no puede ser mostrado.
+ ¿Quieres confiar en este certificado de todas formas?
+ - El certificado del servidor expiró
+ - El certificado del servidor no es de confianza
+ - Las fechas del certificado del servidor están en el futuro
+ - La URL no corresponde con el nombre del servidor en el certificado
+ Mensaje de estado
+ Predeterminado
+ Descargas
+ \"%1$s\" ha sido compartido contigo
+ %1$s ha compartido \"%2$s\" contigo
+ Sincronizar
+ Se encontraron conflictos
+ La carpeta %1$s ya no existe
+ No fue posible sincronizar %1$s
+ Contraseña equivocada para %1$s
+ Falla en archivos mantenidos-en-sincronización.
+ Falla en la sincronización
+ La sincronización falló, inicia sesión de nuevo
+ Los contenidos del archivo ya han sido sincronizados
+ A partir de la versión 1.3.16, los archivos cargados desde este dispositivo serán copiados a la carpeta local %1$s para prevenir pérdidas de datos cuando un archivo se sincroniza entre múltiples cuentas. \n\nDerivado de este cambio, todos los archivos cargados con versiones anteriores de la aplicación fueron copiados a la carpeta %2$s. Sin embargo, un error evitó que se completara esta operación durante la sincronización de la cuenta. Puedes dejar el(los) archivo(s) como está(n) y eliminar la liga a %3$s, o bien, mover el(los) archivo(s) a la carpeta %1$s y mantener la liga a %4$s.\n\nSe enlistan a continuación los archivos locales así como los archivos remotos en %5$s a donde estaban ligados
+ Algunos archivos locales se han perdido
+ Obteniendo la versión más reciente del archivo.
+ Botón de status de sincronización
+ Archivos
+ Botón de configuración
+ Configurar carpetas
+ Las cargas instantáneas se han mejorado por completo. Re-configura tu carga automática desde el menú principal.\n\nDisfruta la nueva y mejorada carga automática.
+ Para %1$s
+ Tipo
+ Etiquetas
+ Probar la conexión del servidor
+ 30 minutos
+ Esta semana
+ Miniatura
+ Hoy
+ Papelera
+ No hay archivos borrados
+ Borrar permanentemente
+ Desestablecer encripción
+ para dejar de compartir este archivo
+ Accede a través de un dominio no de confianza. Por favor consulta la documentación para más información.
+ para actualizar este recurso compartido
+ Borrar cargas fallidas
+ Reintentar cargas fallidas
+ Cargar forma…
+ Cargar contenido de otras aplicaciones
+ Foto
+ Nombre de archivo
+ Tipo de archivo
+ Archivo de acceso directo a Google Maps(%s)
+ Archivo de acceso directo a Internet(%s)
+ Archivo snippet de texto(.txt)
+ Ingresa el nombre y el tipo del archivo a cargar
+ Cargar archivos
+ Botón de cargar elemento
+ Borrar
+ No hay cargas disponibles
+ Carga algún contenido o activa la carga automática
+ El espacio insuficiente evita que se copien los archivos seleccionados dentro de la carpeta %1$s. ¿Te gustaría moverlos ahí en su lugar?
+ Error desconocido
+ Seleccionar
+ Cargar
+ La información recibida no contiene un archivo válido.
+ %1$s no tiene permitido leer un archivo recibido
+ No fue posible copiar el archivo a una carpeta temporal. Por favor intenta enviarlo de nuevo.
+ El archivo seleccionado para cargar no fue encontrado. Por favor verifica si el archivo existe.
+ No hay un archivo a cargar
+ Nombre de la carpeta
+ Selecciona la carpeta de cargas
+ No fue posible cargar %1$s
+ La carga falló, inicia sesión de nuevo
+ Falla en la carga
+ Opción de carga:
+ Mover el archivo a la carpeta %1$s
+ Mantener el archivo en la carpeta de origen
+ Borrar el archivo de la carpeta de origen
+ para cargar a esta carpeta
+ %1$d%% Cargando %2$s
+ Cargando
+ %1$s cargado
+ Salir
+ Ajustes
+ No existen cuentas %1$s en tu dispositivo. Por favor configura una cuenta primero.
+ No se encontró la cuenta
+ Actual
+ Fallido/pendiente de reinicio
+ Cargado
+ Cancelado
+ Esperando para cargar
+ Cargas
+ Cancelado
+ Conflicto
+ Error de conexión
+ Error de credenciales
+ Error de archivo
+ Error de carpeta
+ No se encontró el archivo local
+ Error de permisos
+ El certificado del servidor no es de confianza
+ La aplicación ha sido terminada
+ Completado
+ Error desconocido
+ Virus detectado. ¡La carga no puede ser completada!
+ Esperando a salir de modo de conservación de energía
+ Aguardando la recarga del dispositivo
+ Usuario
+ Dirección
+ Correo electrónico
+ Número telefónico
+ Twitter
+ Sitio web
+ Error al extraer la información del usuario
+ No se ha establecido la información personal
+ Agrega tu nombre, una imagen y detalles de contacto en tu página de perfil.
+ Usuario
+ Descargar
+ Aguarda un momento…
+ Verificando credenciales almacenadas
+ Copiando el archivo desde almacenamiento privado
+ Actualizar
+ Imagen de qué es nuevo
+ Omitir
+ Nuevo en %1$s
+ Descargando archivos…
+ Enviar correo electrónico
+
diff --git a/app/src/main/res/values-es/ionos-strings.xml b/app/src/main/res/values-es/ionos-strings.xml
new file mode 100644
index 000000000000..d5481ad7a4c0
--- /dev/null
+++ b/app/src/main/res/values-es/ionos-strings.xml
@@ -0,0 +1,32 @@
+
+
+
+
+ Debe iniciar sesión a través del navegador
+ Bienvenido al almacenamiento en la nube
+ Conexión
+ Cancelar
+ Repetir
+ Aceptar
+ Esta aplicación utiliza cookies y tecnologías similares. Al hacer clic en Aceptar, consientes el procesamiento y la transferencia de tus datos a terceros. Encontrarás más información, incluida la relativa al procesamiento de datos por parte de terceros, en los ajustes y en nuestra política de privacidad Puedes rechazar la utilización de estas herramientas reject o modificar tu selección en cualquier momento a través de tus ajustes.
+ Estos datos nos ayudan a optimizar el uso de la aplicación y a identificar los fallos del sistema y los errores más rápidamente.
+ Análisis de la recopilación de datos para un diseño personalizado basado en las necesidades de cada usuario
+ Ajustes
+ La recopilación de estos datos es necesaria para poder utilizar las funciones esenciales de la aplicación.
+ Recopilación necesaria de datos
+ Para optimizar nuestra aplicación, recogemos datos anónimos. Para ello utilizamos soluciones de software de diferentes socios. Nos gustaría ofrecerte una transparencia y una ayuda en la toma de decisiones total sobre el procesamiento y la recopilación de tus datos de uso anonimizados. Posteriormente, puedes cambiar tu configuración en cualquier momento en el punto de menú Protección de datos. Sin embargo, ten en cuenta que la recopilación de datos contribuye de manera significativa a la optimización de esta aplicación y que si detienes el envío de datos impedirás estas optimizaciones.
+ Guardar ajustes
+ Configuración de la protección de datos
+ Permisos de almacenamiento
+ Permisos de almacenamiento
+ %1$s necesita permiso para acceder a sus archivos para una mejor funcionalidad. Puede permitir o denegar el acceso.
+ %1$s necesita permiso para acceder a sus archivos para una mejor funcionalidad. Puede permitir o denegar el acceso.
+ Nombre o dirección de correo electrónico...
+ Almacenamiento utilizado
+ Filtro
+
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 46a02bd98a67..5e3d1841d4aa 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -795,8 +795,8 @@
Por favor, selecciona una plantilla
Escoge plantilla
Mandar
- Enviar recurso compartido
Icono del botón de enviar
+ Enviar compartir
Establecer como
Establecer nota
Usar imagen como
@@ -911,6 +911,7 @@
Retransmisión con...
La retransmisión interna no es posible
Por favor, descarga el medio o usa una app externa
+ Modo estricto: ¡ninguna conexión HTTP permitida!
Año/Mes/Día
Año/Mes
Año
@@ -1101,6 +1102,9 @@
Espera un momento…
Comprobando las credenciales guardadas
Copiando el archivo desde el almacenamiento privado.
+ Por favor actualiza la aplicación WebView de Sistema Android para iniciar sesión
+ Actualizar
+ Actualiza WebView del Sistema Android
Cambiar la extensión puede hacer que este archivo se abra con otra aplicación
Imagen de Qué hay de nuevo
Omitir
diff --git a/app/src/main/res/values-fr/ionos-strings.xml b/app/src/main/res/values-fr/ionos-strings.xml
new file mode 100644
index 000000000000..1eebd00e30aa
--- /dev/null
+++ b/app/src/main/res/values-fr/ionos-strings.xml
@@ -0,0 +1,32 @@
+
+
+
+
+ Un navigateur s\'ouvre pour le login
+ Bienvenue sur votre cloud
+ Connexion
+ Annuler
+ Répéter
+ Accepter
+ Cette application utilise des cookies et des technologies similaires. En cliquant sur Accepter, vous acceptez le traitement et le transfert de vos données à des tiers. Vous trouverez plus d’informations, notamment sur le traitement des données par des tiers, dans les paramètres et dans notre Politique de confidentialité Vous pouvez refuser d\'utiliser les outils Refuser ou modifier votre sélection à tout moment via vos paramètres.
+ Ces données nous aident à optimiser l\'utilisation de l\'application et à identifier plus rapidement les pannes du système et les erreurs.
+ Analyse de la collecte de données pour une conception basée sur les besoins
+ Paramétrages
+ La collecte de ces données est nécessaire afin d\'utiliser les fonctions essentielles de l\'application.
+ Collecte de données nécessaire
+ Pour optimiser notre application, nous recueillons des données anonymes. Pour ce faire, nous utilisons les solutions logicielles de différents partenaires. Nous souhaitons vous offrir une transparence et un pouvoir de décision entiers sur le traitement et la collecte de vos données d\'utilisation anonymes. Vous pouvez également modifier vos paramètres à tout moment par la suite dans le menu Protection des données. Veuillez toutefois noter que la collecte de données contribue de manière significative à l\'optimisation de cette application et que vous empêchez ces optimisations en arrêtant le transfert de données.
+ Sauvegarder les paramètres
+ Paramètres de confidentialité
+ Autorisations de stockage
+ Autorisations de stockage
+ %1$s a besoin d\'une autorisation pour accéder à vos fichiers. Vous pouvez autoriser ou refuser l\'accès.
+ %1$s a besoin d\'une autorisation pour accéder à vos fichiers. Vous pouvez autoriser ou refuser l\'accès.
+ Nom ou adresse e-mail…
+ Stockage occupé
+ Filtre
+
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 6cdd2c29fe1e..fe5a37672f06 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -910,6 +910,7 @@
Diffuser avec…
Diffusion interne impossible
Merci, à la place, de télécharger le média ou d\'utiliser une application externe.
+ Mode strict : aucune connexion HTTP n\'est autorisée !
Année/Mois/Jour
Année/Mois
Année
@@ -1100,6 +1101,9 @@
Veuillez patienter…
Vérification des identifiants enregistrés
Copie du fichier depuis le stockage privé
+ Veuillez mettre à jour l\'application Android System WebView pour vous connecter
+ Mise à jour
+ Mettre à jour Android System WebView
La modification de l\'extension peut entraîner l\'ouverture de ce fichier dans une application différente.
Image quoi de neuf
Ignorer
diff --git a/app/src/main/res/values-it/ionos-strings.xml b/app/src/main/res/values-it/ionos-strings.xml
new file mode 100644
index 000000000000..d4e1ab0e8fac
--- /dev/null
+++ b/app/src/main/res/values-it/ionos-strings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ È necessario effettuare il login tramite il browser
+ Benvenuti nel cloud storage
+ Accedi
+ Annulla
+ Riprova
+ Consenti
+ Questa applicazione utilizza cookie e tecnologie simili. Facendo clic su Accetto, si acconsente al trattamento dei propri dati e al loro trasferimento a terzi. Visitate la nostra Privacy Policy per ulteriori informazioni, anche sul trattamento dei dati da parte di fornitori terzi. Cliccate su Rifiuta per impedire l\'uso dello strumento, oppure modificate la vostra scelta in qualsiasi momento nelle Impostazioni.
+ Questi dati ci aiutano a ottimizzare l\'esperienza dell\'utente e a identificare crash e bug del sistema.
+ Analisi dei dati per una progettazione basata sulla usabilità
+ Impostazioni
+ La raccolta dei dati è necessaria per utilizzare le funzioni chiave dell\'applicazione.
+ Raccolta dei dati necessari
+ Raccogliamo dati anonimizzati per ottimizzare la nostra applicazione. A tale scopo utilizziamo soluzioni software di vari partner. Il nostro intento è quello di offrire la massima trasparenza e di consentire all\'utente di decidere come vengono raccolti e utilizzati i suoi dati anonimizzati. È possibile modificare le impostazioni in qualsiasi momento anche in seguito, alla voce di menu Privacy. Si prega di notare, tuttavia, che la raccolta dei dati contribuisce in modo significativo all\'ottimizzazione di questa applicazione e che l\'utente impedirebbe questi miglioramenti rifiutando il trasferimento dei dati.
+ Salva le impostazioni
+ Impostazioni sulla privacy
+ Autorizzazioni di archiviazione
+ Autorizzazioni di archiviazione
+ %1$s ha bisogno del permesso di accedere ai file per una migliore funzionalità. È possibile consentire o negare l\'accesso.
+
+ %1$s ha bisogno del permesso di accedere ai file per una migliore funzionalità. È possibile consentire o negare l\'accesso.
+ Nome o indirizzo e-mail...
+ Storage usato
+ Filtro
+
diff --git a/app/src/main/res/values-land/ionos-integers.xml b/app/src/main/res/values-land/ionos-integers.xml
new file mode 100644
index 000000000000..69325648f8cb
--- /dev/null
+++ b/app/src/main/res/values-land/ionos-integers.xml
@@ -0,0 +1,11 @@
+
+
+
+
+ 4
+
diff --git a/app/src/main/res/values-land/player-dimens.xml b/app/src/main/res/values-land/player-dimens.xml
new file mode 100644
index 000000000000..b10f63164684
--- /dev/null
+++ b/app/src/main/res/values-land/player-dimens.xml
@@ -0,0 +1,18 @@
+
+
+
+ 56dp
+
+ 8dp
+ 0dp
+ 8dp
+
+ 0dp
+
+ 8dp
+
\ No newline at end of file
diff --git a/app/src/main/res/values-large-land/ionos-integers.xml b/app/src/main/res/values-large-land/ionos-integers.xml
new file mode 100644
index 000000000000..69325648f8cb
--- /dev/null
+++ b/app/src/main/res/values-large-land/ionos-integers.xml
@@ -0,0 +1,11 @@
+
+
+
+
+ 4
+
diff --git a/app/src/main/res/values-large-land/player-dimens.xml b/app/src/main/res/values-large-land/player-dimens.xml
new file mode 100644
index 000000000000..992ff0ac03a9
--- /dev/null
+++ b/app/src/main/res/values-large-land/player-dimens.xml
@@ -0,0 +1,18 @@
+
+
+
+ 56dp
+
+ 24dp
+ 8dp
+ 24dp
+
+ 16dp
+
+ 16dp
+
\ No newline at end of file
diff --git a/app/src/main/res/values-large/ionos-dimens.xml b/app/src/main/res/values-large/ionos-dimens.xml
new file mode 100644
index 000000000000..364b222d48a8
--- /dev/null
+++ b/app/src/main/res/values-large/ionos-dimens.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+ 36dp
+
+
+ 48dp
+ 128dp
+ 35sp
+ 49sp
+ 32dp
+ 18sp
+ 24sp
+ 24dp
+ 24dp
+ 480dp
+ 16dp
+ 288dp
+ 12dp
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-large/ionos-integers.xml b/app/src/main/res/values-large/ionos-integers.xml
new file mode 100644
index 000000000000..48b14899437c
--- /dev/null
+++ b/app/src/main/res/values-large/ionos-integers.xml
@@ -0,0 +1,11 @@
+
+
+
+
+ 3
+
diff --git a/app/src/main/res/values-large/player-dimens.xml b/app/src/main/res/values-large/player-dimens.xml
new file mode 100644
index 000000000000..339a72a3e6c2
--- /dev/null
+++ b/app/src/main/res/values-large/player-dimens.xml
@@ -0,0 +1,21 @@
+
+
+
+ 64dp
+ 12dp
+
+ 24dp
+ 24dp
+ 48dp
+
+ 48dp
+
+ 40dp
+
+ 72dp
+
\ No newline at end of file
diff --git a/app/src/main/res/values-night/ionos-colors.xml b/app/src/main/res/values-night/ionos-colors.xml
new file mode 100644
index 000000000000..bd0493483bd2
--- /dev/null
+++ b/app/src/main/res/values-night/ionos-colors.xml
@@ -0,0 +1,48 @@
+
+
+ @color/bg_default
+
+ @color/white
+ @color/primary
+ @color/primary_dark
+ @color/disabled_text
+
+ @color/primary
+ @color/white
+
+ @color/curious_blue
+
+ @color/primary
+
+ @color/dodger_blue
+ @color/royal_blue
+ @color/dark_slate_grey
+
+ @color/white
+ @color/white
+ @color/dusk
+
+ @color/transparent
+ @color/ghost_white
+ @color/transparent
+ @color/ghost_white
+ @color/ghost_white
+ @color/dusk
+ @color/ghost_white
+ @color/madison
+ @color/dusk
+
+ @color/curious_blue
+ @color/cornflower
+ @color/dusk
+
+ @color/dark_slate_grey
+ @color/metallic_silver
+ @color/dusk
+ @color/deep_koamaru
+
diff --git a/app/src/main/res/values-night/ionos-scanbot-theme.xml b/app/src/main/res/values-night/ionos-scanbot-theme.xml
new file mode 100644
index 000000000000..709c3f4e2d95
--- /dev/null
+++ b/app/src/main/res/values-night/ionos-scanbot-theme.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/values-nl/ionos-strings.xml b/app/src/main/res/values-nl/ionos-strings.xml
new file mode 100644
index 000000000000..3581428e7a67
--- /dev/null
+++ b/app/src/main/res/values-nl/ionos-strings.xml
@@ -0,0 +1,32 @@
+
+
+
+
+ U moet inloggen via de browser
+ Welkom bij cloud-opslag
+ Aanmelden
+ Afbreken
+ Herhaling
+ Akkoord
+ Deze toepassing gebruikt cookies en vergelijkbare technologie. Door akkoord te gaan, accepteer je de verwerking en de overdracht van je gegevens aan derden. privacybeleid . Je kunt het gebruik van de tools op elk moment weigeren of je keuze aanpassen via je instellingen.
+ Deze gegevens helpen ons om het gebruik van de app voor je te optimaliseren en om systeemcrashes en fouten sneller te identificeren.
+ Dataverzameling voor gebruikersgebaseerde app-optimalisatie
+ Instellingen
+ Het verzamelen van deze gegevens is noodzakelijk om essentiële functies van de app te kunnen gebruiken.
+ Noodzakelijke dataverzameling
+ Om onze app te optimaliseren, verzamelen we anonieme gegevens. Hiervoor gebruiken we softwareoplossingen van verschillende partners. Wij willen je volledige transparantie en beslissingsbevoegdheid geven over de verwerking en verzameling van je geanonimiseerde gebruiksgegevens. Je kunt je instellingen altijd wijzigen via het menu \'Privacy\'. Houd er rekening mee dat het verzamelen van gegevens een aanzienlijke bijdrage levert aan de optimalisatie van deze app. Als je de dataverzameling uitschakelt, zijn deze optimalisaties beperkt of niet meer mogelijk.
+ Instellingen opslaan
+ Privacyinstellingen
+ Opslagmachtigingen
+ Opslagmachtigingen
+ %1$s heeft toestemming nodig om toegang te krijgen tot je bestanden voor betere functionaliteit. Je kunt toegang toestaan of weigeren.
+ %1$s heeft toestemming nodig om toegang te krijgen tot je bestanden voor betere functionaliteit. Je kunt toegang toestaan of weigeren.
+ Naam of e-mailadres...
+ Gebruikte opslag
+ Filter
+
diff --git a/app/src/main/res/values/ionos-attrs.xml b/app/src/main/res/values/ionos-attrs.xml
new file mode 100644
index 000000000000..009589e48d7f
--- /dev/null
+++ b/app/src/main/res/values/ionos-attrs.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/ionos-colors-palette.xml b/app/src/main/res/values/ionos-colors-palette.xml
new file mode 100644
index 000000000000..589d8ea1fdc6
--- /dev/null
+++ b/app/src/main/res/values/ionos-colors-palette.xml
@@ -0,0 +1,37 @@
+
+
+
+
+ #0B2A63
+ #003d8f
+ #02102B
+ #001B41
+ #095BB1
+ #465A75
+ #DBEDF8
+ #718095
+ #1D2D42
+ #F4F7FA
+ #33F4F7FA
+ #1474C4
+ #C36B00
+ #FFAA00
+ #2E4360
+ #97A3B4
+ #DBE2E8
+ #F2F5F8
+ #95CAEB
+ #BCC8D4
+ #0C8A44
+ #BDCDD7
+ #3196D6
+ #586680
+ #FF6159
+ #80000000
+ #2102102B
+
\ No newline at end of file
diff --git a/app/src/main/res/values/ionos-colors.xml b/app/src/main/res/values/ionos-colors.xml
new file mode 100644
index 000000000000..5d46975c966d
--- /dev/null
+++ b/app/src/main/res/values/ionos-colors.xml
@@ -0,0 +1,71 @@
+
+
+ #3672be
+ #d62e2b
+
+ @color/bg_default
+
+ @color/white
+ @color/primary
+ @color/primary_dark
+ @color/disabled_text
+
+ @color/primary
+ @color/black
+
+ @color/dodger_blue
+
+ @color/bg_default
+ ?android:textColorPrimary
+
+ @color/primary
+ @color/background_color_inverse
+ @color/color_transparent
+ @color/black
+ @color/black
+ @color/grey_900
+
+ @color/text_color
+ @color/primary
+ @color/primary
+ @color/disabled_text
+ @color/primary
+ @color/primary
+
+ @color/primary
+ @color/primary
+
+ @color/bg_default
+
+ @color/madison
+ @color/dodger_blue
+ @color/gull_grayapprox
+
+ @color/white
+ @color/white
+ @color/geyser
+
+ @color/transparent
+ @color/navy
+ @color/transparent
+ @color/madison
+ @color/navy
+ @color/gull_grayapprox
+ @color/madison
+ @color/white
+ @color/gull_grayapprox
+
+ @color/dodger_blue
+ @color/royal_blue
+ @color/gull_grayapprox
+
+ @color/ghost_white
+ @color/dusk
+ @color/metallic_silver
+ @color/geyser
+
diff --git a/app/src/main/res/values/ionos-dimens.xml b/app/src/main/res/values/ionos-dimens.xml
new file mode 100644
index 000000000000..77c166fc91cb
--- /dev/null
+++ b/app/src/main/res/values/ionos-dimens.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+ 8dp
+
+
+ 16dp
+ 64dp
+ 18sp
+ 24sp
+ 32dp
+ 14sp
+ 19sp
+ 12dp
+ 24dp
+ 288dp
+ 16dp
+ 288dp
+ 12dp
+
+ 1dp
+ 8dp
+ 14sp
+ 16dp
+
+ 16dp
+ 2dp
+
\ No newline at end of file
diff --git a/app/src/main/res/values/ionos-integers.xml b/app/src/main/res/values/ionos-integers.xml
new file mode 100644
index 000000000000..4529d1a3c362
--- /dev/null
+++ b/app/src/main/res/values/ionos-integers.xml
@@ -0,0 +1,11 @@
+
+
+
+
+ 2
+
diff --git a/app/src/main/res/values/ionos-scanbot-theme.xml b/app/src/main/res/values/ionos-scanbot-theme.xml
new file mode 100644
index 000000000000..3865b99fbe96
--- /dev/null
+++ b/app/src/main/res/values/ionos-scanbot-theme.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/values/ionos-strings.xml b/app/src/main/res/values/ionos-strings.xml
new file mode 100644
index 000000000000..3d16b46808fc
--- /dev/null
+++ b/app/src/main/res/values/ionos-strings.xml
@@ -0,0 +1,32 @@
+
+
+
+
+ You need to login over browser
+ Welcome to the cloud storage
+ Log In
+ Cancel
+ Retry
+ Agree
+ This application uses cookies and similar technologies. By clicking on Agree, you consent to the processing of your data and also to its transfer to third parties. Visit our Privacy Policy for more information, also on data processing by third party providers. Click on Reject to prevent use of the tool, or change your selection at any time in Settings.
+ This data helps us to optimize your user experience and to identify system crashes and bugs.
+ Data analysis for needs-based design
+ Settings
+ Data collection is necessary in order to use key functions of the app.
+ Necessary data collection
+ We collect anonymized data to optimize our app. We use software solutions by various partners for this purpose. Our intention is to offer full transparency and allow you to decide how your anonymized data is collected and used. You can also change your settings at any time later on under the Privacy menu item. Please note, however, that data collection contributes significantly to optimizing this app and that you would prevent these improvements by rejecting the transfer of data.
+ Save Settings
+ Privacy Settings
+ Storage permissions
+ Storage permissions
+ %1$s needs permission to access your files for better functionality. You can allow or deny access.
+ %1$s needs permission to access your files for better functionality. You can allow or deny access.
+ Name or email address…
+ Storage used
+ Filter
+
diff --git a/app/src/main/res/values/ionos-styles.xml b/app/src/main/res/values/ionos-styles.xml
new file mode 100644
index 000000000000..88e4976f7577
--- /dev/null
+++ b/app/src/main/res/values/ionos-styles.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/ionos-themes.xml b/app/src/main/res/values/ionos-themes.xml
new file mode 100644
index 000000000000..4c28d61a9f96
--- /dev/null
+++ b/app/src/main/res/values/ionos-themes.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/player-colors-palette.xml b/app/src/main/res/values/player-colors-palette.xml
new file mode 100644
index 000000000000..ad0acb15f7c8
--- /dev/null
+++ b/app/src/main/res/values/player-colors-palette.xml
@@ -0,0 +1,12 @@
+
+
+
+ #21000000
+ #111111
+ #979797
+
\ No newline at end of file
diff --git a/app/src/main/res/values/player-colors.xml b/app/src/main/res/values/player-colors.xml
new file mode 100644
index 000000000000..e8ee72da7a15
--- /dev/null
+++ b/app/src/main/res/values/player-colors.xml
@@ -0,0 +1,18 @@
+
+
+
+ @color/color_accent
+ @color/player_cod_gray
+ @color/white
+ @color/white
+ @color/white
+ @color/white
+ @color/player_transparent_black
+ @color/player_transparent_black
+ @color/player_dusty_gray
+
\ No newline at end of file
diff --git a/app/src/main/res/values/player-dimens.xml b/app/src/main/res/values/player-dimens.xml
new file mode 100644
index 000000000000..e8d71cc34229
--- /dev/null
+++ b/app/src/main/res/values/player-dimens.xml
@@ -0,0 +1,36 @@
+
+
+
+ 56dp
+ 21sp
+ 4dp
+ 12dp
+
+ 16dp
+ 16dp
+ 16sp
+ 8dp
+ 12sp
+ 32dp
+
+ 350dp
+ 96dp
+
+ 20dp
+ 8dp
+ 32dp
+ 2dp
+ 4dp
+ 10dp
+ 15sp
+
+ 16dp
+ 8dp
+
+ 48dp
+
\ No newline at end of file
diff --git a/app/src/main/res/values/player-strings.xml b/app/src/main/res/values/player-strings.xml
new file mode 100644
index 000000000000..14ec61a922af
--- /dev/null
+++ b/app/src/main/res/values/player-strings.xml
@@ -0,0 +1,14 @@
+
+
+
+ Close
+ %s • %s
+ Modified: %s
+ Source not found
+ Unknown error
+
\ No newline at end of file
diff --git a/app/src/main/res/values/player-styles.xml b/app/src/main/res/values/player-styles.xml
new file mode 100644
index 000000000000..1755d64cffa6
--- /dev/null
+++ b/app/src/main/res/values/player-styles.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 043281dfe407..4ce3e8f816bf 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -11,7 +11,10 @@
Assistant
Media
-
+ Update Android System WebView
+ Please update the Android System WebView app for a login
+ Update
+ Strict mode: no HTTP connection allowed!
%1$s Android app
version %1$s
version %1$s, build #%2$s
@@ -874,6 +877,7 @@
New folder
Virus detected. Upload cannot be completed!
Tags
+ Unable to fetch sharees.
Adding sharee failed
Adding share failed. This file or folder has already been shared with this person or group.
Unsharing failed
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 08ebf4b5f8c7..2adc7ede7a85 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -11,7 +11,7 @@
~ SPDX-FileCopyrightText: 2012 Bartosz Przybylski
~ SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
-->
-
+
@@ -50,6 +52,12 @@
- @color/bg_default
- @style/Widget.App.TextInputLayout
- @dimen/dialogBorderRadius
+
+ - @style/Widget.App.Snackbar
+ - @style/Widget.App.SnackbarButton
+ - @style/Widget.App.SnackbarTextView
+
+ - @drawable/ic_arrow_back
@@ -97,7 +105,7 @@
- #FFFFFF
-
@@ -112,7 +120,7 @@
- @color/bg_default
-
@@ -227,7 +235,7 @@
- @color/black
- @style/Theme.ownCloud.Overlay.ActionBar
- @style/ToolbarStyle.Overflow
- - @style/ThemeOverlay.App.BottomSheetDialog
+ - @style/Generic.ThemeOverlay.App.BottomSheetDialog
+
diff --git a/scanbot/src/main/res/values-nl/strings.xml b/scanbot/src/main/res/values-nl/strings.xml
new file mode 100644
index 000000000000..6eefb8bc06d4
--- /dev/null
+++ b/scanbot/src/main/res/values-nl/strings.xml
@@ -0,0 +1,58 @@
+
+
+
+
+ Automatisch
+ Geen document
+ Slecht licht
+ Te onrustige achtergrond
+ Niet bewegen
+ Perspectief
+ Blijf dichter bij het document
+ Flash
+ Importeren
+ Afbreken
+ Fout bij het aanmaken van het document
+ Document detecteren
+ scan_%1$tY%1$tm%1$td-%1$tH%1$tM%1$tS
+ Wijzigingen gaan verloren
+ Document scannen
+ Er is een fout opgetreden. Probeer het later nog eens.
+ Kan bestand niet importeren %s
+ Filter toepassen op alle beelden
+ Zwart & Wit
+ Kleur
+ Grijswaarden
+ Magic Color
+ Magic Text
+ Geen filter
+ Solliciteer voor alle
+ Uitvallen
+ Verwijderen
+ Filter
+ Herschikken
+ Draaien
+ %d van %d
+ Vasthouden en slepen om pagina\'s te herschikken
+ Camera-toestemming nodig om code te scannen
+ Er is niet genoeg opslagruimte beschikbaar op het apparaat om foto\'s te kunnen maken. Maak meer ruimte vrij.
+ Niet genoeg ruimte
+ De smartphone heeft niet genoeg opslagruimte.
+ OK
+ Document verwerken ...
+ Verwerken...
+ Pagina\'s herschikken
+ Kader opnieuw instellen
+ Opslaan
+ Behalve als
+ Bestandsnaam:
+ Bestandstype:
+ Ongeldige bestandsnaam
+ Opslaglocatie:
+ Er is een fout opgetreden. Probeer het later nog eens.
+
diff --git a/scanbot/src/main/res/values/attrs.xml b/scanbot/src/main/res/values/attrs.xml
new file mode 100644
index 000000000000..078446388b23
--- /dev/null
+++ b/scanbot/src/main/res/values/attrs.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/scanbot/src/main/res/values/colors-palette.xml b/scanbot/src/main/res/values/colors-palette.xml
new file mode 100644
index 000000000000..43b859132093
--- /dev/null
+++ b/scanbot/src/main/res/values/colors-palette.xml
@@ -0,0 +1,26 @@
+
+
+
+
+ #FFFFFF
+ #000000
+ #333333
+ #999999
+ #dcdcdc
+ #EBEEEF
+ #222222
+ #00FFFFFF
+ #0D000000
+ #55FF8800
+ #ccFF8800
+ #a0c8e8
+ #D9555555
+ #8C0077BB
+ #0077BB
+ #003d8f
+
\ No newline at end of file
diff --git a/scanbot/src/main/res/values/dimens.xml b/scanbot/src/main/res/values/dimens.xml
new file mode 100644
index 000000000000..447b7f44876c
--- /dev/null
+++ b/scanbot/src/main/res/values/dimens.xml
@@ -0,0 +1,58 @@
+
+
+
+
+ 4dp
+ 8dp
+ 12dp
+ 16dp
+ 24dp
+
+ 18sp
+
+ 1dp
+
+ 56dp
+ 48dp
+
+ 2dp
+ 48dp
+
+ 48dp
+ 10dp
+ 16dp
+ 36dp
+
+ 64dp
+
+ 20dp
+ 20dp
+ 30dp
+
+ 4dp
+ 4dp
+ 24dp
+ 8dp
+ 11sp
+ 5dp
+ 16dp
+ 40dp
+
+ 50dp
+ 250dp
+ 15dp
+ 20sp
+ 20dp
+ 10dp
+ 75dp
+ 18sp
+
+ 480dp
+
+ 20sp
+
\ No newline at end of file
diff --git a/scanbot/src/main/res/values/preference_keys.xml b/scanbot/src/main/res/values/preference_keys.xml
new file mode 100644
index 000000000000..def3f6a319f8
--- /dev/null
+++ b/scanbot/src/main/res/values/preference_keys.xml
@@ -0,0 +1,14 @@
+
+
+
+
+ preference_scanbot_license_key
+
+ preference_aes_gsm_key
+ preference_aes_gsm_initialization_vector
+
\ No newline at end of file
diff --git a/scanbot/src/main/res/values/strings.xml b/scanbot/src/main/res/values/strings.xml
new file mode 100644
index 000000000000..a0d5478008c0
--- /dev/null
+++ b/scanbot/src/main/res/values/strings.xml
@@ -0,0 +1,58 @@
+
+
+
+
+ Automatic
+ No Document
+ Poor light
+ Background too noisy
+ Don\'t move
+ Perspective
+ Move closer
+ Flash
+ Import
+ Cancel
+ Error while creating document
+ Detect document
+ scan_%1$tY%1$tm%1$td-%1$tH%1$tM%1$tS
+ Progress will be lost
+ Scan document
+ An error occurred. Please try again later.
+ Can\'t import file %s
+ Applying filter for all images
+ Black & White
+ Color
+ Grayscale
+ Magic Color
+ Magic Text
+ No filter
+ Apply for all
+ Crop
+ Delete
+ Filter
+ Rearrange
+ Rotate
+ %d of %d
+ Hold and drag to rearrange pages
+ Need use camera permissions to be granted to scan code
+ Not enough space on device for taking photo. Please, clear storage.
+ Not enough space
+ The smartphone does not have enough storage space available.
+ OK
+ Processing document…
+ Processing…
+ Rearrange pages
+ Reset borders
+ Save
+ Save as
+ File name:
+ File type:
+ Invalid file name
+ Save location:
+ An error occurred. Please try again later
+
diff --git a/scanbot/src/main/res/values/styles.xml b/scanbot/src/main/res/values/styles.xml
new file mode 100644
index 000000000000..a0f2eb4ff932
--- /dev/null
+++ b/scanbot/src/main/res/values/styles.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/scanbot/src/main/res/values/theme.xml b/scanbot/src/main/res/values/theme.xml
new file mode 100644
index 000000000000..fee2f03abf28
--- /dev/null
+++ b/scanbot/src/main/res/values/theme.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
diff --git a/scanbot/src/main/res/values/untranslatable_strings.xml b/scanbot/src/main/res/values/untranslatable_strings.xml
new file mode 100644
index 000000000000..7d520ab5d97c
--- /dev/null
+++ b/scanbot/src/main/res/values/untranslatable_strings.xml
@@ -0,0 +1,14 @@
+
+
+
+
+ JPG
+ PDF
+ PDF (OCR)
+ PNG
+
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 352229bd93d9..51ff9b3b9410 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -8,6 +8,7 @@ rootProject.name = 'Nextcloud'
include ':app'
include ':appscan'
+include ':scanbot'
//includeBuild('../android-common') {
// dependencySubstitution {
diff --git a/src/versionDev/fastlane/metadata/android/en-US/changelogs/30300052.txt b/src/versionDev/fastlane/metadata/android/en-US/changelogs/30300052.txt
new file mode 100644
index 000000000000..897d3a8d6721
--- /dev/null
+++ b/src/versionDev/fastlane/metadata/android/en-US/changelogs/30300052.txt
@@ -0,0 +1,7 @@
+## 3.30.0 RC2 (September 10, 2024)
+
+- Bugfixes
+
+Minimum: NC 16 Server, Android 7.0 Nougat
+
+For a full list, please see https://github.com/nextcloud/android/milestone/94
\ No newline at end of file
diff --git a/src/versionDev/fastlane/metadata/android/en-US/changelogs/30300052.txt.license b/src/versionDev/fastlane/metadata/android/en-US/changelogs/30300052.txt.license
new file mode 100644
index 000000000000..dd29b5040ecd
--- /dev/null
+++ b/src/versionDev/fastlane/metadata/android/en-US/changelogs/30300052.txt.license
@@ -0,0 +1,2 @@
+# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
\ No newline at end of file