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

This commit is contained in:
Tobias Kaminsky 2023-12-07 02:30:58 +01:00
commit 4ee92c635d
21 changed files with 375 additions and 310 deletions

View file

@ -126,6 +126,10 @@ android {
}
}
buildFeatures {
buildConfig = true
}
productFlavors {
// used for f-droid
generic {

View file

@ -86,7 +86,7 @@ class StackRemoteViewsFactory(
val userAccountManager: UserAccountManager,
val clientFactory: ClientFactory,
val intent: Intent,
val widgetRepository: WidgetRepository
private val widgetRepository: WidgetRepository
) : RemoteViewsService.RemoteViewsFactory {
private lateinit var widgetConfiguration: WidgetConfiguration
@ -163,58 +163,20 @@ class StackRemoteViewsFactory(
// we will switch soon to coil and then streamline all of this
// Kotlin cannot catch multiple exception types at same time
@Suppress("NestedBlockDepth", "TooGenericExceptionCaught")
@Suppress("NestedBlockDepth")
private fun createItemView(position: Int): RemoteViews {
return RemoteViews(context.packageName, R.layout.widget_item).apply {
if (widgetItems.isEmpty()) {
return@apply
}
val widgetItem = widgetItems[position]
// icon bitmap/svg
if (widgetItem.iconUrl.isNotEmpty()) {
val glide: FutureTarget<Bitmap>
if (Uri.parse(widgetItem.iconUrl).encodedPath!!.endsWith(".svg")) {
glide = Glide.with(context)
.using(
CustomGlideUriLoader(userAccountManager.user, clientFactory),
InputStream::class.java
)
.from(Uri::class.java)
.`as`(SVGorImage::class.java)
.transcode(SvgOrImageBitmapTranscoder(SVG_SIZE, SVG_SIZE), Bitmap::class.java)
.sourceEncoder(StreamEncoder())
.cacheDecoder(FileToStreamDecoder(SvgOrImageDecoder()))
.decoder(SvgOrImageDecoder())
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.load(Uri.parse(widgetItem.iconUrl))
.into(SVG_SIZE, SVG_SIZE)
} else {
glide = Glide.with(context)
.using(CustomGlideStreamLoader(widgetConfiguration.user.get(), clientFactory))
.load(widgetItem.iconUrl)
.asBitmap()
.into(SVG_SIZE, SVG_SIZE)
}
try {
if (widgetConfiguration.roundIcon) {
setImageViewBitmap(R.id.icon, BitmapUtils.roundBitmap(glide.get()))
} else {
setImageViewBitmap(R.id.icon, glide.get())
}
} catch (e: Exception) {
Log_OC.d(TAG, "Error setting icon", e)
setImageViewResource(R.id.icon, R.drawable.ic_dashboard)
}
loadIcon(widgetItem, this)
}
// text
setTextViewText(R.id.title, widgetItem.title)
if (widgetItem.subtitle.isNotEmpty()) {
setViewVisibility(R.id.subtitle, View.VISIBLE)
setTextViewText(R.id.subtitle, widgetItem.subtitle)
} else {
setViewVisibility(R.id.subtitle, View.GONE)
}
updateTexts(widgetItem, this)
if (widgetItem.link.isNotEmpty()) {
val clickIntent = Intent(Intent.ACTION_VIEW, Uri.parse(widgetItem.link))
@ -223,6 +185,65 @@ class StackRemoteViewsFactory(
}
}
@Suppress("TooGenericExceptionCaught")
private fun loadIcon(widgetItem: DashboardWidgetItem, remoteViews: RemoteViews) {
val isIconSVG = Uri.parse(widgetItem.iconUrl).encodedPath!!.endsWith(".svg")
val source: FutureTarget<Bitmap> = if (isIconSVG) {
loadSVGIcon(widgetItem)
} else {
loadBitmapIcon(widgetItem)
}
try {
val bitmap: Bitmap = if (widgetConfiguration.roundIcon) {
BitmapUtils.roundBitmap(source.get())
} else {
source.get()
}
remoteViews.setImageViewBitmap(R.id.icon, bitmap)
} catch (e: Exception) {
Log_OC.d(TAG, "Error setting icon", e)
remoteViews.setImageViewResource(R.id.icon, R.drawable.ic_dashboard)
}
}
private fun loadSVGIcon(widgetItem: DashboardWidgetItem): FutureTarget<Bitmap> {
return Glide.with(context)
.using(
CustomGlideUriLoader(userAccountManager.user, clientFactory),
InputStream::class.java
)
.from(Uri::class.java)
.`as`(SVGorImage::class.java)
.transcode(SvgOrImageBitmapTranscoder(SVG_SIZE, SVG_SIZE), Bitmap::class.java)
.sourceEncoder(StreamEncoder())
.cacheDecoder(FileToStreamDecoder(SvgOrImageDecoder()))
.decoder(SvgOrImageDecoder())
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.load(Uri.parse(widgetItem.iconUrl))
.into(SVG_SIZE, SVG_SIZE)
}
private fun loadBitmapIcon(widgetItem: DashboardWidgetItem): FutureTarget<Bitmap> {
return Glide.with(context)
.using(CustomGlideStreamLoader(widgetConfiguration.user.get(), clientFactory))
.load(widgetItem.iconUrl)
.asBitmap()
.into(SVG_SIZE, SVG_SIZE)
}
private fun updateTexts(widgetItem: DashboardWidgetItem, remoteViews: RemoteViews) {
remoteViews.setTextViewText(R.id.title, widgetItem.title)
if (widgetItem.subtitle.isNotEmpty()) {
remoteViews.setViewVisibility(R.id.subtitle, View.VISIBLE)
remoteViews.setTextViewText(R.id.subtitle, widgetItem.subtitle)
} else {
remoteViews.setViewVisibility(R.id.subtitle, View.GONE)
}
}
override fun getLoadingView(): RemoteViews? {
return null
}

View file

@ -44,7 +44,8 @@ public enum UploadResult {
OLD_ANDROID_API(18),
SYNC_CONFLICT(19),
CANNOT_CREATE_FILE(20),
LOCAL_STORAGE_NOT_COPIED(21);
LOCAL_STORAGE_NOT_COPIED(21),
QUOTA_EXCEEDED(22);
private final int value;
@ -104,6 +105,8 @@ public enum UploadResult {
return CANNOT_CREATE_FILE;
case 21:
return LOCAL_STORAGE_NOT_COPIED;
case 22:
return QUOTA_EXCEEDED;
}
return UNKNOWN;
}
@ -162,6 +165,8 @@ public enum UploadResult {
return VIRUS_DETECTED;
case CANNOT_CREATE_FILE:
return CANNOT_CREATE_FILE;
case QUOTA_EXCEEDED:
return QUOTA_EXCEEDED;
default:
return UNKNOWN;
}

View file

@ -83,6 +83,7 @@ import java.util.Vector;
import javax.inject.Inject;
import androidx.core.app.NotificationCompat;
import androidx.core.app.ServiceCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import dagger.android.AndroidInjection;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
@ -203,7 +204,11 @@ public class FileDownloader extends Service
Log_OC.d(TAG, "Starting command with id " + startId);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(FOREGROUND_SERVICE_ID, mNotification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC);
ServiceCompat.startForeground(
this,
FOREGROUND_SERVICE_ID,
mNotification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC);
} else {
startForeground(FOREGROUND_SERVICE_ID, mNotification);
}

View file

@ -350,14 +350,18 @@ public class FileDisplayActivity extends FileActivity
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
StoragePermissionDialogFragment fragment = (StoragePermissionDialogFragment) getSupportFragmentManager().findFragmentByTag(PERMISSION_CHOICE_DIALOG_TAG);
if (fragment != null) {
Dialog dialog = fragment.getDialog();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
StoragePermissionDialogFragment fragment =
(StoragePermissionDialogFragment) getSupportFragmentManager()
.findFragmentByTag(PERMISSION_CHOICE_DIALOG_TAG);
if (fragment != null) {
Dialog dialog = fragment.getDialog();
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
getSupportFragmentManager().beginTransaction().remove(fragment).commitNowAllowingStateLoss();
PermissionUtil.requestExternalStoragePermission(this, viewThemeUtils);
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
getSupportFragmentManager().beginTransaction().remove(fragment).commitNowAllowingStateLoss();
PermissionUtil.requestExternalStoragePermission(this, viewThemeUtils);
}
}
}
}

View file

@ -41,10 +41,21 @@ import android.os.Looper;
import android.os.Parcelable;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.view.*;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager.LayoutParams;
import android.widget.*;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.button.MaterialButton;
import com.nextcloud.client.account.User;
@ -66,13 +77,21 @@ import com.owncloud.android.operations.CreateFolderOperation;
import com.owncloud.android.operations.RefreshFolderOperation;
import com.owncloud.android.operations.UploadFileOperation;
import com.owncloud.android.syncadapter.FileSyncAdapter;
import com.owncloud.android.ui.adapter.UploaderAdapter;
import com.owncloud.android.ui.adapter.ReceiveExternalFilesAdapter;
import com.owncloud.android.ui.asynctasks.CopyAndUploadContentUrisTask;
import com.owncloud.android.ui.dialog.*;
import com.owncloud.android.ui.dialog.AccountChooserInterface;
import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
import com.owncloud.android.ui.dialog.MultipleAccountsDialog;
import com.owncloud.android.ui.dialog.SortingOrderDialogFragment;
import com.owncloud.android.ui.fragment.TaskRetainerFragment;
import com.owncloud.android.ui.helpers.FileOperationsHelper;
import com.owncloud.android.ui.helpers.UriUploader;
import com.owncloud.android.utils.*;
import com.owncloud.android.utils.DataHolderUtil;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.ErrorMessageAdapter;
import com.owncloud.android.utils.FileSortOrder;
import com.owncloud.android.utils.MimeType;
import com.owncloud.android.utils.theme.ViewThemeUtils;
import java.io.File;
@ -80,7 +99,12 @@ import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Stack;
import java.util.Vector;
import javax.inject.Inject;
@ -96,6 +120,7 @@ import androidx.core.view.MenuItemCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import static com.owncloud.android.utils.DisplayUtils.openSortingOrderDialogFragment;
@ -103,8 +128,8 @@ import static com.owncloud.android.utils.DisplayUtils.openSortingOrderDialogFrag
* This can be used to upload things to an ownCloud instance.
*/
public class ReceiveExternalFilesActivity extends FileActivity
implements OnItemClickListener, View.OnClickListener, CopyAndUploadContentUrisTask.OnCopyTmpFilesTaskListener,
SortingOrderDialogFragment.OnSortingOrderListener, Injectable, AccountChooserInterface {
implements View.OnClickListener, CopyAndUploadContentUrisTask.OnCopyTmpFilesTaskListener,
SortingOrderDialogFragment.OnSortingOrderListener, Injectable, AccountChooserInterface, ReceiveExternalFilesAdapter.OnItemClickListener {
private static final String TAG = ReceiveExternalFilesActivity.class.getSimpleName();
@ -125,6 +150,7 @@ public class ReceiveExternalFilesActivity extends FileActivity
private OCFile mFile;
private SyncBroadcastReceiver mSyncBroadcastReceiver;
private ReceiveExternalFilesAdapter receiveExternalFilesAdapter;
private boolean mSyncInProgress;
private final static int REQUEST_CODE__SETUP_ACCOUNT = REQUEST_CODE__LAST_SHARED + 1;
@ -273,6 +299,22 @@ public class ReceiveExternalFilesActivity extends FileActivity
populateDirectoryList();
}
@Override
public void selectFile(OCFile file) {
if (file.isFolder()) {
if (file.isEncrypted() &&
!FileOperationsHelper.isEndToEndEncryptionSetup(this, getUser().orElseThrow(IllegalAccessError::new))) {
DisplayUtils.showSnackMessage(this, R.string.e2e_not_yet_setup);
return;
}
startSyncFolderOperation(file);
mParents.push(file.getFileName());
populateDirectoryList();
}
}
public static class DialogNoAccount extends DialogFragment {
@NonNull
@Override
@ -611,39 +653,6 @@ public class ReceiveExternalFilesActivity extends FileActivity
}
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// click on folder in the list
Log_OC.d(TAG, "on item click");
List<OCFile> tmpFiles = getStorageManager().getFolderContent(mFile, false);
tmpFiles = sortFileList(tmpFiles);
if (tmpFiles.isEmpty()) {
return;
}
// filter on dirtype
Vector<OCFile> files = new Vector<>();
files.addAll(tmpFiles);
if (files.size() < position) {
throw new IndexOutOfBoundsException("Incorrect item selected");
}
OCFile ocFile = files.get(position);
if (ocFile.isFolder()) {
if (ocFile.isEncrypted() &&
!FileOperationsHelper.isEndToEndEncryptionSetup(this, getUser().orElseThrow(IllegalAccessError::new))) {
DisplayUtils.showSnackMessage(this, R.string.e2e_not_yet_setup);
return;
}
OCFile folderToEnter = files.get(position);
startSyncFolderOperation(folderToEnter);
mParents.push(folderToEnter.getFileName());
populateDirectoryList();
}
}
@Override
public void onClick(View v) {
// click on button
@ -740,29 +749,10 @@ public class ReceiveExternalFilesActivity extends FileActivity
binding.list.setVisibility(View.GONE);
} else {
mEmptyListContainer.setVisibility(View.GONE);
files = sortFileList(files);
List<Map<String, Object>> data = new LinkedList<>();
for (OCFile f : files) {
Map<String, Object> h = new HashMap<>();
h.put("dirname", f);
data.add(h);
}
UploaderAdapter sa = new UploaderAdapter(this,
data,
R.layout.uploader_list_item_layout,
new String[]{"dirname"},
new int[]{R.id.filename},
getStorageManager(),
getUser().get(),
syncedFolderProvider,
viewThemeUtils);
binding.list.setAdapter(sa);
binding.list.setVisibility(View.VISIBLE);
setupReceiveExternalFilesAdapter(files);
}
MaterialButton btnChooseFolder = binding.uploaderChooseFolder;
viewThemeUtils.material.colorMaterialButtonPrimaryFilled(btnChooseFolder);
btnChooseFolder.setOnClickListener(this);
@ -774,8 +764,6 @@ public class ReceiveExternalFilesActivity extends FileActivity
viewThemeUtils.material.colorMaterialButtonPrimaryOutlined(binding.uploaderCancel);
binding.uploaderCancel.setOnClickListener(this);
binding.list.setOnItemClickListener(this);
sortButton = binding.toolbarLayout.sortButton;
FileSortOrder sortOrder = preferences.getSortOrderByFolder(mFile);
sortButton.setText(DisplayUtils.getSortOrderStringId(sortOrder));
@ -783,6 +771,21 @@ public class ReceiveExternalFilesActivity extends FileActivity
}
}
private void setupReceiveExternalFilesAdapter(List<OCFile> files) {
receiveExternalFilesAdapter = new ReceiveExternalFilesAdapter(files,
this,
getUser().get(),
getStorageManager(),
viewThemeUtils,
syncedFolderProvider,
this);
binding.list.setLayoutManager(new LinearLayoutManager(this));
binding.list.setAdapter(receiveExternalFilesAdapter);
binding.list.setVisibility(View.VISIBLE);
}
protected void setupEmptyList() {
mEmptyListContainer = binding.emptyView.emptyListView;
mEmptyListMessage = binding.emptyView.emptyListViewText;
@ -1016,19 +1019,35 @@ public class ReceiveExternalFilesActivity extends FileActivity
menu.findItem(R.id.action_create_dir).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
}
// tint search event
final MenuItem searchMenuItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchMenuItem);
setupSearchView(menu);
MenuItem newFolderMenuItem = menu.findItem(R.id.action_create_dir);
newFolderMenuItem.setEnabled(mFile.canWrite());
// hacky as no default way is provided
viewThemeUtils.androidx.themeToolbarSearchView(searchView);
return true;
}
private void setupSearchView(Menu menu) {
final MenuItem searchMenuItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchMenuItem);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
receiveExternalFilesAdapter.filter(query);
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
receiveExternalFilesAdapter.filter(newText);
return false;
}
});
viewThemeUtils.androidx.themeToolbarSearchView(searchView);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
boolean retval = true;

View file

@ -178,6 +178,8 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
viewThemeUtils,
syncedFolderProvider);
setHasStableIds(true);
// initialise thumbnails cache on background thread
new ThumbnailsCacheManager.InitDiskCacheTask().execute();
}

View file

@ -0,0 +1,176 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2023 Alper Ozturk
* Copyright (C) 2023 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.adapter
import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import com.nextcloud.client.account.User
import com.owncloud.android.databinding.UploaderListItemLayoutBinding
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.utils.DisplayUtils
import com.owncloud.android.utils.MimeTypeUtil
import com.owncloud.android.utils.theme.ViewThemeUtils
@Suppress("LongParameterList")
class ReceiveExternalFilesAdapter(
private val files: List<OCFile>,
private val context: Context,
private val user: User,
private val storageManager: FileDataStorageManager,
private val viewThemeUtils: ViewThemeUtils,
private val syncedFolderProvider: SyncedFolderProvider,
private val onItemClickListener: OnItemClickListener
) : RecyclerView.Adapter<ReceiveExternalFilesAdapter.ReceiveExternalViewHolder>() {
private var filteredFiles: List<OCFile> = files
interface OnItemClickListener {
fun selectFile(file: OCFile)
}
inner class ReceiveExternalViewHolder(val binding: UploaderListItemLayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
init {
binding.root.setOnClickListener {
val position = bindingAdapterPosition
if (position != RecyclerView.NO_POSITION) {
onItemClickListener.selectFile(filteredFiles[position])
}
}
}
}
@SuppressLint("NotifyDataSetChanged")
fun filter(query: String) {
filteredFiles = if (query.isEmpty()) {
files
} else {
files.filter { file ->
file.fileName.contains(query, ignoreCase = true)
}
}
notifyDataSetChanged()
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ReceiveExternalViewHolder {
val binding = UploaderListItemLayoutBinding
.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false)
return ReceiveExternalViewHolder(binding)
}
override fun onBindViewHolder(viewHolder: ReceiveExternalViewHolder, position: Int) {
val file = filteredFiles[position]
viewHolder.binding.filename.text = file.fileName
viewHolder.binding.lastMod.text = DisplayUtils.getRelativeTimestamp(context, file.modificationTimestamp)
if (!file.isFolder) {
viewHolder.binding.fileSize.text = DisplayUtils.bytesToHumanReadable(file.fileLength)
}
viewHolder.binding.fileSize.visibility = if (file.isFolder) {
View.GONE
} else {
View.VISIBLE
}
viewHolder.binding.fileSeparator.visibility = if (file.isFolder) {
View.GONE
} else {
View.VISIBLE
}
val thumbnailImageView = viewHolder.binding.thumbnail
setupThumbnail(thumbnailImageView, file)
}
private fun setupThumbnail(thumbnailImageView: ImageView, file: OCFile) {
thumbnailImageView.tag = file.fileId
if (file.isFolder) {
setupThumbnailForFolder(thumbnailImageView, file)
} else if (MimeTypeUtil.isImage(file) && file.remoteId != null) {
setupThumbnailForImage(thumbnailImageView, file)
} else {
setupDefaultThumbnail(thumbnailImageView, file)
}
}
private fun setupThumbnailForFolder(thumbnailImageView: ImageView, file: OCFile) {
val isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, user)
val isDarkModeActive = syncedFolderProvider.preferences.isDarkModeEnabled
val overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder)
val icon = MimeTypeUtil.getFileIcon(isDarkModeActive, overlayIconId, context, viewThemeUtils)
thumbnailImageView.setImageDrawable(icon)
}
@Suppress("NestedBlockDepth")
private fun setupThumbnailForImage(thumbnailImageView: ImageView, file: OCFile) {
var thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(file.remoteId.toString())
if (thumbnail != null && !file.isUpdateThumbnailNeeded) {
thumbnailImageView.setImageBitmap(thumbnail)
} else {
// generate new Thumbnail
if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, thumbnailImageView)) {
val task = ThumbnailGenerationTask(thumbnailImageView, storageManager, user)
if (thumbnail == null) {
thumbnail = if (MimeTypeUtil.isVideo(file)) {
ThumbnailsCacheManager.mDefaultVideo
} else {
ThumbnailsCacheManager.mDefaultImg
}
}
val asyncDrawable = AsyncThumbnailDrawable(
context.resources,
thumbnail,
task
)
thumbnailImageView.setImageDrawable(asyncDrawable)
@Suppress("DEPRECATION")
task.execute(ThumbnailGenerationTaskObject(file, file.remoteId))
}
}
}
private fun setupDefaultThumbnail(thumbnailImageView: ImageView, file: OCFile) {
val icon = MimeTypeUtil.getFileTypeIcon(
file.mimeType,
file.fileName,
context,
viewThemeUtils
)
thumbnailImageView.setImageDrawable(icon)
}
override fun getItemCount() = filteredFiles.size
}

View file

@ -711,6 +711,9 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
case LOCAL_STORAGE_NOT_COPIED:
status = parentActivity.getString(R.string.upload_local_storage_not_copied);
break;
case QUOTA_EXCEEDED:
status = parentActivity.getString(R.string.upload_quota_exceeded);
break;
default:
status = parentActivity.getString(R.string.upload_unknown_error);
break;

View file

@ -1,156 +0,0 @@
/*
* ownCloud Android client application
*
* @author Tobias Kaminsky
* Copyright (C) 2016 ownCloud Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.owncloud.android.ui.adapter;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import com.nextcloud.client.account.User;
import com.nextcloud.client.preferences.DarkMode;
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.utils.DisplayUtils;
import com.owncloud.android.utils.MimeTypeUtil;
import com.owncloud.android.utils.theme.ViewThemeUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class UploaderAdapter extends SimpleAdapter {
private final Context mContext;
private final User user;
private final FileDataStorageManager mStorageManager;
private final LayoutInflater inflater;
private final ViewThemeUtils viewThemeUtils;
private SyncedFolderProvider syncedFolderProvider;
public UploaderAdapter(Context context,
List<? extends Map<String, ?>> data,
int resource,
String[] from,
int[] to,
FileDataStorageManager storageManager,
User user,
SyncedFolderProvider syncedFolderProvider,
ViewThemeUtils viewThemeUtils) {
super(context, data, resource, from, to);
this.user = user;
mStorageManager = storageManager;
mContext = context;
this.syncedFolderProvider = syncedFolderProvider;
inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.viewThemeUtils = viewThemeUtils;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
if (convertView == null) {
vi = inflater.inflate(R.layout.uploader_list_item_layout, parent, false);
}
HashMap<String, OCFile> data = (HashMap<String, OCFile>) getItem(position);
OCFile file = data.get("dirname");
TextView filename = vi.findViewById(R.id.filename);
filename.setText(file.getFileName());
ImageView fileIcon = vi.findViewById(R.id.thumbnail);
fileIcon.setTag(file.getFileId());
TextView lastModV = vi.findViewById(R.id.last_mod);
lastModV.setText(DisplayUtils.getRelativeTimestamp(mContext, file.getModificationTimestamp()));
TextView fileSizeV = vi.findViewById(R.id.file_size);
TextView fileSizeSeparatorV = vi.findViewById(R.id.file_separator);
if(!file.isFolder()) {
fileSizeV.setVisibility(View.VISIBLE);
fileSizeSeparatorV.setVisibility(View.VISIBLE);
fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.getFileLength()));
} else {
fileSizeV.setVisibility(View.GONE);
fileSizeSeparatorV.setVisibility(View.GONE);
}
if (file.isFolder()) {
boolean isAutoUploadFolder = SyncedFolderProvider.isAutoUploadFolder(syncedFolderProvider, file, user);
boolean isDarkModeActive = syncedFolderProvider.getPreferences().isDarkModeEnabled();
Integer overlayIconId = file.getFileOverlayIconId(isAutoUploadFolder);
final LayerDrawable icon = MimeTypeUtil.getFileIcon(isDarkModeActive, overlayIconId, mContext, viewThemeUtils);
fileIcon.setImageDrawable(icon);
} else {
// get Thumbnail if file is image
if (MimeTypeUtil.isImage(file) && file.getRemoteId() != null) {
// Thumbnail in Cache?
Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
String.valueOf(file.getRemoteId())
);
if (thumbnail != null && !file.isUpdateThumbnailNeeded()) {
fileIcon.setImageBitmap(thumbnail);
} else {
// generate new Thumbnail
if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, fileIcon)) {
final ThumbnailsCacheManager.ThumbnailGenerationTask task =
new ThumbnailsCacheManager.ThumbnailGenerationTask(fileIcon, mStorageManager, user);
if (thumbnail == null) {
if (MimeTypeUtil.isVideo(file)) {
thumbnail = ThumbnailsCacheManager.mDefaultVideo;
} else {
thumbnail = ThumbnailsCacheManager.mDefaultImg;
}
}
final AsyncThumbnailDrawable asyncDrawable = new AsyncThumbnailDrawable(
mContext.getResources(),
thumbnail,
task
);
fileIcon.setImageDrawable(asyncDrawable);
task.execute(new ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file, file.getRemoteId()));
}
}
} else {
final Drawable icon = MimeTypeUtil.getFileTypeIcon(file.getMimeType(),
file.getFileName(),
mContext,
viewThemeUtils);
fileIcon.setImageDrawable(icon);
}
}
return vi;
}
}

View file

@ -467,7 +467,7 @@ public class ExtendedListFragment extends Fragment implements
private void scrollToPosition(int position) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
if (mRecyclerView != null) {
if (linearLayoutManager != null) {
int visibleItemCount = linearLayoutManager.findLastCompletelyVisibleItemPosition() -
linearLayoutManager.findFirstCompletelyVisibleItemPosition();
linearLayoutManager.scrollToPositionWithOffset(position, (visibleItemCount / 2) * mHeightCell);

View file

@ -445,21 +445,8 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
ShareType.PUBLIC_LINK,
"");
//
// boolean supportsSecureFiledrop = file.isEncrypted() &&
// capabilities.getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_26);
//
// if (publicShares.isEmpty() &&
// containsNoNewPublicShare(adapter.getShares()) &&
// (!file.isEncrypted() || supportsSecureFiledrop)) {
// final OCShare ocShare = new OCShare();
// ocShare.setShareType(ShareType.NEW_PUBLIC_LINK);
// publicShares.add(ocShare);
// } else {
// adapter.removeNewPublicShare();
// }
if (publicShares.isEmpty() && containsNoNewPublicShare(adapter.getShares())) {
if (publicShares.isEmpty() && containsNoNewPublicShare(adapter.getShares()) &&
(!file.isEncrypted() || capabilities.getEndToEndEncryption().isTrue())) {
final OCShare ocShare = new OCShare();
ocShare.setShareType(ShareType.NEW_PUBLIC_LINK);
publicShares.add(ocShare);

View file

@ -205,7 +205,7 @@ public class PreviewImageActivity extends FileActivity implements
}
return true;
} else {
return onOptionsItemSelected(item);
return super.onOptionsItemSelected(item);
}
}

View file

@ -34,10 +34,11 @@
android:layout_height="0dp"
android:layout_weight="1">
<ListView
<androidx.recyclerview.widget.RecyclerView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/bg_default"
android:divider="@color/transparent"
android:dividerHeight="0dip"

View file

@ -884,6 +884,7 @@
<string name="upload_lock_failed">Неуспело закључавање фасцикле</string>
<string name="upload_old_android">Шифровање је могуће само са &gt;= Андроидом 5.0</string>
<string name="upload_query_move_foreign_files">Недостатак простора спречава копирање фајлова у фасциклу %1$s. Желите ли да их преместите тамо?</string>
<string name="upload_quota_exceeded">Прекорачена је квота за складиште</string>
<string name="upload_scan_doc_upload">Скенирање документа камером</string>
<string name="upload_sync_conflict">Сукоб синхронизације. Разрешите га ручно</string>
<string name="upload_unknown_error">Непозната грешка</string>

View file

@ -887,6 +887,7 @@
<string name="upload_lock_failed">锁定文件夹失败</string>
<string name="upload_old_android">加密功能仅适用于安卓 5.0 及以上版本</string>
<string name="upload_query_move_foreign_files">空间不足将阻止将所选文件复制到%1$s文件夹中。 你想把它们移到那里吗?</string>
<string name="upload_quota_exceeded">超出存储限额</string>
<string name="upload_scan_doc_upload">使用相机扫描文档</string>
<string name="upload_sync_conflict">同步时发生异常,请手动同步</string>
<string name="upload_unknown_error">未知错误</string>

View file

@ -233,13 +233,6 @@
<item name="android:textStyle">bold</item>
</style>
<style name="Button.Borderless.Destructive" parent="Widget.Material3.Button.TextButton">
<item name="android:textColor">@color/highlight_textColor_Warning</item>
<item name="android:textAllCaps">false</item>
<item name="android:typeface">sans</item>
<item name="android:textStyle">bold</item>
</style>
<style name="Button.Borderless.Login" parent="Widget.Material3.Button.TextButton">
<item name="android:textColor">@color/fg_inverse</item>
<item name="android:textAllCaps">false</item>

View file

@ -1,6 +1,6 @@
buildscript {
ext {
androidPluginVersion = '8.1.4'
androidPluginVersion = '8.2.0'
appCompatVersion = '1.6.1'
jacoco_version = '0.8.10'
kotlin_version = '1.8.22'

View file

@ -4,7 +4,6 @@ NC_TEST_SERVER_USERNAME=test
NC_TEST_SERVER_PASSWORD=test
android.enableJetifier=true
android.useAndroidX=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false
#android.debug.obsoleteApi=true

View file

@ -1,6 +1,6 @@
#Fri Jan 13 08:21:45 CET 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View file

@ -1,2 +1,2 @@
DO NOT TOUCH; GENERATED BY DRONE
<span class="mdl-layout-title">Lint Report: 74 warnings</span>
<span class="mdl-layout-title">Lint Report: 9 errors and 75 warnings</span>