mirror of
https://github.com/nextcloud/android.git
synced 2024-12-19 15:33:00 +03:00
Merge remote-tracking branch 'origin/master' into dev
This commit is contained in:
commit
4ee92c635d
21 changed files with 375 additions and 310 deletions
|
@ -126,6 +126,10 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
// used for f-droid
|
||||
generic {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -205,7 +205,7 @@ public class PreviewImageActivity extends FileActivity implements
|
|||
}
|
||||
return true;
|
||||
} else {
|
||||
return onOptionsItemSelected(item);
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -884,6 +884,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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue