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

This commit is contained in:
Tobias Kaminsky 2024-05-22 03:36:50 +02:00
commit 5fb326df15
19 changed files with 315 additions and 399 deletions

View file

@ -39,7 +39,7 @@ jobs:
with:
swap-size-gb: 10
- name: Initialize CodeQL
uses: github/codeql-action/init@b7cec7526559c32f1616476ff32d17ba4c59b2d6 # v3.25.5
uses: github/codeql-action/init@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6
with:
languages: ${{ matrix.language }}
- name: Set up JDK 17
@ -53,4 +53,4 @@ jobs:
echo "org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" > "$HOME/.gradle/gradle.properties"
./gradlew assembleDebug
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@b7cec7526559c32f1616476ff32d17ba4c59b2d6 # v3.25.5
uses: github/codeql-action/analyze@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6

View file

@ -42,6 +42,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@b7cec7526559c32f1616476ff32d17ba4c59b2d6 # v3.25.5
uses: github/codeql-action/upload-sarif@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6
with:
sarif_file: results.sarif

View file

@ -303,7 +303,7 @@ dependencies {
implementation 'commons-io:commons-io:2.16.1'
implementation 'org.greenrobot:eventbus:3.3.1'
implementation 'com.googlecode.ez-vcard:ez-vcard:0.12.1'
implementation 'org.lukhnos:nnio:0.3'
implementation 'org.lukhnos:nnio:0.3.1'
implementation 'org.bouncycastle:bcpkix-jdk18on:1.78.1'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.github.nextcloud-deps:sectioned-recyclerview:0.6.1'

View file

@ -143,6 +143,9 @@ import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.ProcessLifecycleOwner;
import de.cotech.hw.fido.WebViewFidoBridge;
import de.cotech.hw.fido.ui.FidoDialogOptions;
import de.cotech.hw.fido2.WebViewWebauthnBridge;
@ -360,10 +363,18 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
}
initServerPreFragment(savedInstanceState);
ProcessLifecycleOwner.get().getLifecycle().addObserver(lifecycleEventObserver);
// webViewUtil.checkWebViewVersion();
}
private final LifecycleEventObserver lifecycleEventObserver = ((lifecycleOwner, event) -> {
if (event == Lifecycle.Event.ON_START && token != null) {
Log_OC.d(TAG, "Start poolLogin");
poolLogin(clientFactory.createPlainClient());
}
});
private void deleteCookies() {
try {
CookieSyncManager.createInstance(this);
@ -403,7 +414,8 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
String loginUrl = login;
runOnUiThread(() -> {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(loginUrl));
loginFlowResultLauncher.launch(intent);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
});
token = jsonObject.getAsJsonObject("poll").get("token").getAsString();
@ -412,9 +424,6 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
thread.start();
}
private final ActivityResultLauncher<Intent> loginFlowResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), result -> poolLogin(clientFactory.createPlainClient()));
private static String getWebLoginUserAgent() {
return Build.MANUFACTURER.substring(0, 1).toUpperCase(Locale.getDefault()) +
Build.MANUFACTURER.substring(1).toLowerCase(Locale.getDefault()) + " " + Build.MODEL + " (Android)";
@ -828,6 +837,8 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
mOperationsServiceBinder = null;
}
Log_OC.d(TAG, "AuthenticatorActivity onDestroy called");
super.onDestroy();
}
@ -1039,6 +1050,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
cancelButton.setOnClickListener(v -> {
loginFlowExecutorService.shutdown();
ProcessLifecycleOwner.get().getLifecycle().removeObserver(lifecycleEventObserver);
recreate();
});
}
@ -1638,7 +1650,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
private boolean isRedirectedToTheDefaultBrowser = false;
private void poolLogin(PlainClient client) {
loginFlowExecutorService.scheduleAtFixedRate(() -> {
loginFlowExecutorService.scheduleWithFixedDelay(() -> {
if (!isLoginProcessCompleted) {
performLoginFlowV2(client);
}
@ -1694,6 +1706,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
checkOcServer();
loginFlowExecutorService.shutdown();
ProcessLifecycleOwner.get().getLifecycle().removeObserver(lifecycleEventObserver);
}
/**

View file

@ -1,47 +0,0 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2018 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
*/
package com.owncloud.android.ui.components;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.ViewPager;
public class CustomViewPager extends ViewPager {
public CustomViewPager(@NonNull Context context) {
super(context);
}
public CustomViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent ev) {
try {
return super.onTouchEvent(ev);
} catch (IllegalArgumentException ex) {
// no logging as this might flood log and we cannot do anything
}
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
try {
return super.onInterceptTouchEvent(ev);
} catch (IllegalArgumentException ex) {
// no logging as this might flood log and we cannot do anything
}
return false;
}
}

View file

@ -59,7 +59,7 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.viewpager.widget.ViewPager;
import androidx.viewpager2.widget.ViewPager2;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
@ -68,7 +68,6 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
public class PreviewImageActivity extends FileActivity implements
FileFragment.ContainerActivity,
ViewPager.OnPageChangeListener,
OnRemoteOperationListener,
Injectable {
@ -78,13 +77,12 @@ public class PreviewImageActivity extends FileActivity implements
private static final String KEY_SYSTEM_VISIBLE = "TRUE";
private OCFile livePhotoFile;
private ViewPager viewPager;
private ViewPager2 viewPager;
private PreviewImagePagerAdapter previewImagePagerAdapter;
private int savedPosition;
private boolean hasSavedPosition;
private boolean requestWaitingForBinder;
private DownloadFinishReceiver downloadFinishReceiver;
private UploadFinishReceiver uploadFinishReceiver;
private View fullScreenAnchorView;
private boolean isDownloadWorkStarted = false;
@ -158,7 +156,7 @@ public class PreviewImageActivity extends FileActivity implements
if (virtualFolderType != null && virtualFolderType != VirtualFolderType.NONE) {
VirtualFolderType type = (VirtualFolderType) virtualFolderType;
previewImagePagerAdapter = new PreviewImagePagerAdapter(getSupportFragmentManager(),
previewImagePagerAdapter = new PreviewImagePagerAdapter(this,
type,
user,
getStorageManager());
@ -168,11 +166,11 @@ public class PreviewImageActivity extends FileActivity implements
if (parentFolder == null) {
// should not be necessary
parentFolder = getStorageManager().getFileByPath(OCFile.ROOT_PATH);
parentFolder = getStorageManager().getFileByEncryptedRemotePath(OCFile.ROOT_PATH);
}
previewImagePagerAdapter = new PreviewImagePagerAdapter(
getSupportFragmentManager(),
this,
livePhotoFile,
parentFolder,
user,
@ -185,11 +183,16 @@ public class PreviewImageActivity extends FileActivity implements
viewPager = findViewById(R.id.fragmentPager);
int position = hasSavedPosition ? savedPosition : previewImagePagerAdapter.getFilePosition(getFile());
position = position >= 0 ? position : 0;
position = Math.max(position, 0);
viewPager.setAdapter(previewImagePagerAdapter);
viewPager.addOnPageChangeListener(this);
viewPager.setCurrentItem(position);
viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
selectPage(position);
}
});
viewPager.setCurrentItem(position, false);
if (position == 0 && !getFile().isDown()) {
// this is necessary because mViewPager.setCurrentItem(0) just after setting the
@ -247,7 +250,7 @@ public class PreviewImageActivity extends FileActivity implements
if (file != null) {
/// Refresh the activity according to the Account and OCFile set
setFile(file); // reset after getting it fresh from storageManager
getSupportActionBar().setTitle(getFile().getFileName());
updateActionBarTitle(getFile().getFileName());
//if (!stateWasRecovered) {
initViewPager(optionalUser.get());
//}
@ -271,15 +274,16 @@ public class PreviewImageActivity extends FileActivity implements
super.onRemoteOperationFinish(operation, result);
if (operation instanceof RemoveFileOperation) {
// initialize the pager with the new file list
initViewPager(getUser().get());
if (viewPager.getAdapter().getCount() > 0) {
// Trigger page reselection, to update the title
onPageSelected(viewPager.getCurrentItem());
} else {
// Last file has been deleted, so finish the activity
int deletePosition = viewPager.getCurrentItem();
int nextPosition = deletePosition > 0 ? deletePosition - 1 : 0;
if (previewImagePagerAdapter.getItemCount() <= 1) {
finish();
return;
}
viewPager.setCurrentItem(nextPosition, true);
previewImagePagerAdapter.delete(deletePosition);
} else if (operation instanceof SynchronizeFileOperation) {
onSynchronizeFileOperationFinish(result);
}
@ -301,7 +305,7 @@ public class PreviewImageActivity extends FileActivity implements
requestWaitingForBinder = false;
Log_OC.d(TAG, "Simulating reselection of current page after connection " +
"of download binder");
onPageSelected(viewPager.getCurrentItem());
selectPage(viewPager.getCurrentItem());
}
} else {
Log_OC.d(TAG, "Download worker stopped");
@ -328,7 +332,7 @@ public class PreviewImageActivity extends FileActivity implements
IntentFilter downloadIntentFilter = new IntentFilter(FileDownloadWorker.Companion.getDownloadFinishMessage());
localBroadcastManager.registerReceiver(downloadFinishReceiver, downloadIntentFilter);
uploadFinishReceiver = new UploadFinishReceiver();
UploadFinishReceiver uploadFinishReceiver = new UploadFinishReceiver();
IntentFilter uploadIntentFilter = new IntentFilter(FileUploadWorker.Companion.getUploadFinishMessage());
localBroadcastManager.registerReceiver(uploadFinishReceiver, uploadIntentFilter);
}
@ -384,8 +388,7 @@ public class PreviewImageActivity extends FileActivity implements
*
* @param position Position index of the new selected page
*/
@Override
public void onPageSelected(int position) {
public void selectPage(int position) {
savedPosition = position;
hasSavedPosition = true;
@ -405,45 +408,21 @@ public class PreviewImageActivity extends FileActivity implements
}
}
// Update ActionBar title
if (currentFile != null) {
if (getSupportActionBar() != null) {
getSupportActionBar().setTitle(currentFile.getFileName());
}
updateActionBarTitle(currentFile.getFileName());
setDrawerIndicatorEnabled(false);
}
}
/**
* Called when the scroll state changes. Useful for discovering when the user begins dragging,
* when the pager is automatically settling to the current page. when it is fully stopped/idle.
*
* @param state The new scroll state (SCROLL_STATE_IDLE, _DRAGGING, _SETTLING
*/
@Override
public void onPageScrollStateChanged(int state) {
// not used at the moment
}
/**
* This method will be invoked when the current page is scrolled, either as part of a
* programmatically initiated smooth scroll or a user initiated touch scroll.
*
* @param position Position index of the first page currently being displayed.
* Page position+1 will be visible if positionOffset is
* nonzero.
* @param positionOffset Value from [0, 1) indicating the offset from the page
* at position.
* @param positionOffsetPixels Value in pixels indicating the offset from position.
*/
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// not used at the moment
public void updateActionBarTitle(String title) {
if (getSupportActionBar() != null) {
getSupportActionBar().setTitle(title);
}
}
/**
* Class waiting for broadcast events from the {@link FileDownloadWorker} service.
*
* <p>
* Updates the UI when a download is started or finished, provided that it is relevant for the
* folder displayed in the gallery.
*/
@ -465,8 +444,9 @@ public class PreviewImageActivity extends FileActivity implements
String accountName = intent.getStringExtra(FileDownloadWorker.EXTRA_ACCOUNT_NAME);
String downloadedRemotePath = intent.getStringExtra(FileDownloadWorker.EXTRA_REMOTE_PATH);
String downloadBehaviour = intent.getStringExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR);
if (getAccount().name.equals(accountName) && downloadedRemotePath != null) {
OCFile file = getStorageManager().getFileByPath(downloadedRemotePath);
OCFile file = getStorageManager().getFileByEncryptedRemotePath(downloadedRemotePath);
boolean downloadWasFine = intent.getBooleanExtra(FileDownloadWorker.EXTRA_DOWNLOAD_RESULT, false);
if (EditImageActivity.OPEN_IMAGE_EDITOR.equals(downloadBehaviour)) {
@ -476,16 +456,19 @@ public class PreviewImageActivity extends FileActivity implements
if (position >= 0) {
if (downloadWasFine) {
previewImagePagerAdapter.updateFile(position, file);
} else {
previewImagePagerAdapter.updateWithDownloadError(position);
}
previewImagePagerAdapter.notifyDataSetChanged(); // will trigger the creation of new fragments
previewImagePagerAdapter.notifyItemChanged(position);
} else if (downloadWasFine) {
initViewPager(getUser().get());
int newPosition = previewImagePagerAdapter.getFilePosition(file);
if (newPosition >= 0) {
viewPager.setCurrentItem(newPosition);
Optional<User> user = getUser();
if (user.isPresent()) {
initViewPager(user.get());
int newPosition = previewImagePagerAdapter.getFilePosition(file);
if (newPosition >= 0) {
viewPager.setCurrentItem(newPosition);
}
}
}
}
@ -502,19 +485,11 @@ public class PreviewImageActivity extends FileActivity implements
if (visible) {
hideSystemUI(fullScreenAnchorView);
// actionBar.hide(); // propagated through
// OnSystemUiVisibilityChangeListener()
} else {
showSystemUI(fullScreenAnchorView);
// actionBar.show(); // propagated through
// OnSystemUiVisibilityChangeListener()
}
}
public void switchToFullScreen() {
hideSystemUI(fullScreenAnchorView);
}
public void startImageEditor(OCFile file) {
if (file.isDown()) {
Intent editImageIntent = new Intent(this, EditImageActivity.class);

View file

@ -81,6 +81,7 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.fragment.app.FragmentTransaction;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import pl.droidsonroids.gif.GifDrawable;
@ -127,7 +128,7 @@ public class PreviewImageFragment extends FileFragment implements Injectable {
* This method hides to client objects the need of doing the construction in two steps.
*
* @param imageFile An {@link OCFile} to preview as an image in the fragment
* @param ignoreFirstSavedState Flag to work around an unexpected behaviour of {@link FragmentStatePagerAdapter} ;
* @param ignoreFirstSavedState Flag to work around an unexpected behaviour of {@link FragmentStateAdapter} ;
* TODO better solution
*/
public static PreviewImageFragment newInstance(@NonNull OCFile imageFile,
@ -232,8 +233,17 @@ public class PreviewImageFragment extends FileFragment implements Injectable {
if (savedInstanceState != null) {
if (!ignoreFirstSavedState) {
OCFile file = BundleExtensionsKt.getParcelableArgument(savedInstanceState, EXTRA_FILE, OCFile.class);
if (file == null) {
return;
}
setFile(file);
binding.image.setScale(Math.min(binding.image.getMaximumScale(), savedInstanceState.getFloat(EXTRA_ZOOM)));
try {
binding.image.setScale(Math.min(binding.image.getMaximumScale(), savedInstanceState.getFloat(EXTRA_ZOOM)));
} catch (IllegalArgumentException e) {
Log_OC.d(TAG, "Error caught at setScale: " + e);
}
} else {
ignoreFirstSavedState = false;
}

View file

@ -1,246 +0,0 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 Alper Ozturk <alper_ozturk@proton.me>
* SPDX-FileCopyrightText: 2018 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-FileCopyrightText: 2015 ownCloud Inc.
* SPDX-FileCopyrightText: 2013 David A. Velasco <dvelasco@solidgear.es>
* SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
*/
package com.owncloud.android.ui.preview;
import android.util.SparseArray;
import android.view.ViewGroup;
import com.nextcloud.client.account.User;
import com.nextcloud.client.preferences.AppPreferences;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.VirtualFolderType;
import com.owncloud.android.ui.fragment.FileFragment;
import com.owncloud.android.utils.FileSortOrder;
import com.owncloud.android.utils.FileStorageUtils;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.media3.common.util.UnstableApi;
/**
* Adapter class that provides Fragment instances
*/
public class PreviewImagePagerAdapter extends FragmentStatePagerAdapter {
private OCFile selectedFile;
private List<OCFile> mImageFiles;
private User user;
private Set<Object> mObsoleteFragments;
private Set<Integer> mObsoletePositions;
private Set<Integer> mDownloadErrors;
private FileDataStorageManager mStorageManager;
private SparseArray<FileFragment> mCachedFragments;
/**
* Constructor
*
* @param fragmentManager {@link FragmentManager} instance that will handle the {@link Fragment}s provided by the
* adapter.
* @param parentFolder Folder where images will be searched for.
* @param storageManager Bridge to database.
*/
public PreviewImagePagerAdapter(FragmentManager fragmentManager,
OCFile selectedFile,
OCFile parentFolder,
User user,
FileDataStorageManager storageManager,
boolean onlyOnDevice,
AppPreferences preferences) {
super(fragmentManager);
if (parentFolder == null) {
throw new IllegalArgumentException("NULL parent folder");
}
if (storageManager == null) {
throw new IllegalArgumentException("NULL storage manager");
}
this.user = user;
this.selectedFile = selectedFile;
mStorageManager = storageManager;
mImageFiles = mStorageManager.getFolderImages(parentFolder, onlyOnDevice);
FileSortOrder sortOrder = preferences.getSortOrderByFolder(parentFolder);
mImageFiles = sortOrder.sortCloudFiles(mImageFiles);
mObsoleteFragments = new HashSet<>();
mObsoletePositions = new HashSet<>();
mDownloadErrors = new HashSet<>();
mCachedFragments = new SparseArray<>();
}
/**
* Constructor
*
* @param fragmentManager {@link FragmentManager} instance that will handle the {@link Fragment}s provided by the
* adapter.
* @param type Type of virtual folder, e.g. favorite or photos
* @param storageManager Bridge to database.
*/
public PreviewImagePagerAdapter(FragmentManager fragmentManager,
VirtualFolderType type,
User user,
FileDataStorageManager storageManager) {
super(fragmentManager);
if (fragmentManager == null) {
throw new IllegalArgumentException("NULL FragmentManager instance");
}
if (type == null) {
throw new IllegalArgumentException("NULL parent folder");
}
if (type == VirtualFolderType.NONE) {
throw new IllegalArgumentException("NONE virtual folder type");
}
if (storageManager == null) {
throw new IllegalArgumentException("NULL storage manager");
}
this.user = user;
mStorageManager = storageManager;
if (type == VirtualFolderType.GALLERY) {
mImageFiles = mStorageManager.getAllGalleryItems();
mImageFiles = FileStorageUtils.sortOcFolderDescDateModifiedWithoutFavoritesFirst(mImageFiles);
} else {
mImageFiles = mStorageManager.getVirtualFolderContent(type, true);
}
mObsoleteFragments = new HashSet<>();
mObsoletePositions = new HashSet<>();
mDownloadErrors = new HashSet<>();
mCachedFragments = new SparseArray<>();
}
/**
* Returns the image files handled by the adapter.
*
* @return OCFile desired image or null if position is not in adapter
*/
@Nullable
public OCFile getFileAt(int position) {
try {
return mImageFiles.get(position);
} catch (IndexOutOfBoundsException exception) {
return null;
}
}
private void addVideoOfLivePhoto(OCFile file) {
file.livePhotoVideo = selectedFile;
}
@NonNull
@OptIn(markerClass = UnstableApi.class)
public Fragment getItem(int i) {
OCFile file = getFileAt(i);
Fragment fragment;
if (file == null) {
fragment = PreviewImageErrorFragment.newInstance();
} else if (file.isDown()) {
fragment = PreviewImageFragment.newInstance(file, mObsoletePositions.contains(i), false);
} else {
addVideoOfLivePhoto(file);
if (mDownloadErrors.remove(i)) {
fragment = FileDownloadFragment.newInstance(file, user, true);
((FileDownloadFragment) fragment).setError(true);
} else {
if (file.isEncrypted()) {
fragment = FileDownloadFragment.newInstance(file, user, mObsoletePositions.contains(i));
} else if (PreviewMediaFragment.canBePreviewed(file)) {
fragment = PreviewMediaFragment.newInstance(file, user, 0, false, file.livePhotoVideo != null);
} else {
fragment = PreviewImageFragment.newInstance(file, mObsoletePositions.contains(i), true);
}
}
}
mObsoletePositions.remove(i);
return fragment;
}
public int getFilePosition(OCFile file) {
return mImageFiles.indexOf(file);
}
@Override
public int getCount() {
return mImageFiles.size();
}
@Override
public CharSequence getPageTitle(int position) {
OCFile file = getFileAt(position);
if (file != null) {
return file.getFileName();
} else {
return "";
}
}
public void updateFile(int position, OCFile file) {
FileFragment fragmentToUpdate = mCachedFragments.get(position);
if (fragmentToUpdate != null) {
mObsoleteFragments.add(fragmentToUpdate);
}
mObsoletePositions.add(position);
mImageFiles.set(position, file);
}
public void updateWithDownloadError(int position) {
FileFragment fragmentToUpdate = mCachedFragments.get(position);
if (fragmentToUpdate != null) {
mObsoleteFragments.add(fragmentToUpdate);
}
mDownloadErrors.add(position);
}
@Override
public int getItemPosition(@NonNull Object object) {
if (mObsoleteFragments.remove(object)) {
return POSITION_NONE;
}
return super.getItemPosition(object);
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
Object fragment = super.instantiateItem(container, position);
mCachedFragments.put(position, (FileFragment) fragment);
return fragment;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
mCachedFragments.remove(position);
super.destroyItem(container, position, object);
}
public boolean pendingErrorAt(int position) {
return mDownloadErrors.contains(position);
}
}

View file

@ -0,0 +1,207 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 Alper Ozturk <alper_ozturk@proton.me>
* SPDX-FileCopyrightText: 2018 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-FileCopyrightText: 2015 ownCloud Inc.
* SPDX-FileCopyrightText: 2013 David A. Velasco <dvelasco@solidgear.es>
* SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
*/
package com.owncloud.android.ui.preview
import android.util.SparseArray
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.nextcloud.client.account.User
import com.nextcloud.client.preferences.AppPreferences
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.datamodel.VirtualFolderType
import com.owncloud.android.ui.fragment.FileFragment
import com.owncloud.android.utils.FileStorageUtils
/**
* Adapter class that provides Fragment instances
*/
class PreviewImagePagerAdapter : FragmentStateAdapter {
private var selectedFile: OCFile? = null
private var imageFiles: MutableList<OCFile> = mutableListOf()
private val user: User
private val mObsoleteFragments: MutableSet<Any>
private val mObsoletePositions: MutableSet<Int>
private val mDownloadErrors: MutableSet<Int>
private val mStorageManager: FileDataStorageManager
private val mCachedFragments: SparseArray<FileFragment>
/**
* Constructor
*
* @param fragmentActivity [FragmentActivity] instance that will handle the [Fragment]s provided by the
* adapter.
* @param parentFolder Folder where images will be searched for.
* @param storageManager Bridge to database.
*/
@Suppress("LongParameterList")
constructor(
fragmentActivity: FragmentActivity,
selectedFile: OCFile?,
parentFolder: OCFile?,
user: User,
storageManager: FileDataStorageManager,
onlyOnDevice: Boolean,
preferences: AppPreferences
) : super(fragmentActivity) {
requireNotNull(parentFolder) { "NULL parent folder" }
this.user = user
this.selectedFile = selectedFile
mStorageManager = storageManager
imageFiles = mStorageManager.getFolderImages(parentFolder, onlyOnDevice)
val sortOrder = preferences.getSortOrderByFolder(parentFolder)
imageFiles = sortOrder.sortCloudFiles(imageFiles.toMutableList()).toMutableList()
mObsoleteFragments = HashSet()
mObsoletePositions = HashSet()
mDownloadErrors = HashSet()
mCachedFragments = SparseArray()
}
/**
* Constructor
*
* @param fragmentActivity [FragmentActivity] instance that will handle the [Fragment]s provided by the
* adapter.
* @param type Type of virtual folder, e.g. favorite or photos
* @param storageManager Bridge to database.
*/
constructor(
fragmentActivity: FragmentActivity,
type: VirtualFolderType?,
user: User,
storageManager: FileDataStorageManager
) : super(fragmentActivity) {
requireNotNull(type) { "NULL parent folder" }
require(type != VirtualFolderType.NONE) { "NONE virtual folder type" }
this.user = user
mStorageManager = storageManager
if (type == VirtualFolderType.GALLERY) {
imageFiles = mStorageManager.allGalleryItems
imageFiles = FileStorageUtils.sortOcFolderDescDateModifiedWithoutFavoritesFirst(imageFiles)
} else {
imageFiles = mStorageManager.getVirtualFolderContent(type, true)
}
mObsoleteFragments = HashSet()
mObsoletePositions = HashSet()
mDownloadErrors = HashSet()
mCachedFragments = SparseArray()
}
fun delete(position: Int) {
if (position < 0 || position >= imageFiles.size) {
return
}
mCachedFragments[position]?.let {
mObsoleteFragments.add(it)
}
mObsoletePositions.add(position)
imageFiles.removeAt(position)
mDownloadErrors.remove(position)
mCachedFragments.remove(position)
notifyItemRemoved(position)
}
/**
* Returns the image files handled by the adapter.
*
* @return OCFile desired image or null if position is not in adapter
*/
@Suppress("TooGenericExceptionCaught")
fun getFileAt(position: Int): OCFile? {
return try {
imageFiles[position]
} catch (exception: IndexOutOfBoundsException) {
null
}
}
override fun getItemId(position: Int): Long {
return imageFiles[position].hashCode().toLong()
}
private fun addVideoOfLivePhoto(file: OCFile) {
file.livePhotoVideo = selectedFile
}
fun getItem(i: Int): Fragment {
val file = getFileAt(i)
val fragment: Fragment
if (file == null) {
fragment = PreviewImageErrorFragment.newInstance()
} else if (file.isDown) {
fragment = PreviewImageFragment.newInstance(file, mObsoletePositions.contains(i), false)
} else {
addVideoOfLivePhoto(file)
if (mDownloadErrors.remove(i)) {
fragment = FileDownloadFragment.newInstance(file, user, true)
(fragment as FileDownloadFragment).setError(true)
} else {
fragment = if (file.isEncrypted) {
FileDownloadFragment.newInstance(file, user, mObsoletePositions.contains(i))
} else if (PreviewMediaFragment.canBePreviewed(file)) {
PreviewMediaFragment.newInstance(file, user, 0, false, file.livePhotoVideo != null)
} else {
PreviewImageFragment.newInstance(file, mObsoletePositions.contains(i), true)
}
}
}
mObsoletePositions.remove(i)
return fragment
}
fun getFilePosition(file: OCFile): Int {
return imageFiles.indexOf(file)
}
fun updateFile(position: Int, file: OCFile) {
val fragmentToUpdate = mCachedFragments[position]
if (fragmentToUpdate != null) {
mObsoleteFragments.add(fragmentToUpdate)
}
mObsoletePositions.add(position)
imageFiles[position] = file
}
fun updateWithDownloadError(position: Int) {
val fragmentToUpdate = mCachedFragments[position]
if (fragmentToUpdate != null) {
mObsoleteFragments.add(fragmentToUpdate)
}
mDownloadErrors.add(position)
}
fun pendingErrorAt(position: Int): Boolean {
return mDownloadErrors.contains(position)
}
override fun createFragment(position: Int): Fragment {
return getItem(position)
}
override fun getItemCount(): Int {
return imageFiles.size
}
}

View file

@ -12,11 +12,6 @@ import android.text.TextUtils;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.files.UploadFileRemoteOperation;
import org.lukhnos.nnio.file.FileVisitResult;
import org.lukhnos.nnio.file.FileVisitor;
import org.lukhnos.nnio.file.Path;
import org.lukhnos.nnio.file.impl.FileBasedPathImpl;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@ -68,19 +63,4 @@ public final class FileUtil {
return null;
}
}
public static Path walkFileTree(Path start, FileVisitor<? super Path> visitor) throws IOException {
if (org.lukhnos.nnio.file.Files.isDirectory(start)) {
org.lukhnos.nnio.file.FileVisitResult preVisitDirectoryResult = visitor.preVisitDirectory(start, null);
if (preVisitDirectoryResult == FileVisitResult.CONTINUE) {
for (File child : start.toFile().listFiles()) {
walkFileTree(FileBasedPathImpl.get(child), visitor);
}
}
visitor.postVisitDirectory(start, null);
} else {
visitor.visitFile(start, new org.lukhnos.nnio.file.attribute.BasicFileAttributes(start.toFile()));
}
return start;
}
}

View file

@ -36,6 +36,7 @@ import org.lukhnos.nnio.file.Path;
import org.lukhnos.nnio.file.Paths;
import org.lukhnos.nnio.file.SimpleFileVisitor;
import org.lukhnos.nnio.file.attribute.BasicFileAttributes;
import org.lukhnos.nnio.file.Files;
import java.io.File;
import java.io.IOException;
@ -63,7 +64,7 @@ public final class FilesSyncHelper {
final long enabledTimestampMs = syncedFolder.getEnabledTimestampMs();
try {
FileUtil.walkFileTree(path, new SimpleFileVisitor<>() {
Files.walkFileTree(path, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
File file = path.toFile();
@ -91,7 +92,7 @@ public final class FilesSyncHelper {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
if (syncedFolder.isExcludeHidden() && dir.compareTo(Paths.get(syncedFolder.getLocalPath())) != 0 && dir.toFile().isHidden()) {
return null;
return FileVisitResult.SKIP_SUBTREE;
}
return FileVisitResult.CONTINUE;
}

View file

@ -10,16 +10,17 @@
~ SPDX-FileCopyrightText: 2013 David A. Velasco <dvelasco@solidgear.es>
~ SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
-->
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.owncloud.android.ui.components.CustomViewPager
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/fragmentPager"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
android:layout_height="match_parent" />
<include
layout="@layout/drawer"

View file

@ -15,7 +15,7 @@
android:id="@+id/top"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
android:animateLayoutChanges="false">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/live_photo_indicator"

View file

@ -3,13 +3,19 @@
<string name="about_android">%1$s aplicación Android</string>
<string name="about_title">Acerca de</string>
<string name="about_version">versión %1$s</string>
<string name="about_version_with_build">versión %1$s, build #%2$s</string>
<string name="account_creation_failed">Fallo al crear la cuenta</string>
<string name="account_icon">Ícono de la cuenta</string>
<string name="account_not_found">¡No se encontró la cuenta!</string>
<string name="action_edit">Editar</string>
<string name="action_empty_notifications">Borrar todas las notificaciones</string>
<string name="action_empty_trashbin">Vaciar papelera de reciclaje</string>
<string name="action_send_share">Enviar/Compartir</string>
<string name="action_switch_grid_view">Vista de cuadrícula</string>
<string name="action_switch_list_view">Vista de lista</string>
<string name="actionbar_calendar_contacts_restore">Restaurar contactos y calendario</string>
<string name="actionbar_mkdir">Nueva carpeta</string>
<string name="actionbar_move_or_copy">Mover o copiar</string>
<string name="actionbar_open_with">Abrir con</string>
<string name="actionbar_search">Buscar</string>
<string name="actionbar_see_details">Detalles</string>

View file

@ -40,6 +40,7 @@
<string name="assistant_screen_all_task_type">すべて</string>
<string name="assistant_screen_create_task_alert_dialog_input_field_placeholder">テキストを入力</string>
<string name="assistant_screen_failed_task_text">失敗</string>
<string name="assistant_screen_scheduled_task_status_text">スケジュール済</string>
<string name="assistant_screen_successful_task_text">完了</string>
<string name="assistant_screen_unknown_task_status_text">不明</string>
<string name="assistant_task_detail_screen_input_button_title">入力</string>

View file

@ -157,6 +157,7 @@
<string name="disable_new_media_folder_detection_notifications">Deaktivēt</string>
<string name="dismiss">Atmest</string>
<string name="dismiss_notification_description">Noraidīt paziņojumu</string>
<string name="document_scan_export_dialog_images">Vairāki attēli</string>
<string name="done">Pabeigts</string>
<string name="downloader_download_failed_content">Nevarēja lejupielādēt %1$s</string>
<string name="downloader_download_failed_credentials_error">Lejupielāde neizdevās, piesakieties vēlreiz.</string>
@ -383,6 +384,8 @@
<string name="send">Sūtīt</string>
<string name="set_as">Saglabāt kā</string>
<string name="set_picture_as">Izmantot attēlu kā</string>
<string name="set_status">Iestatīt stāvokli</string>
<string name="set_status_message">Iestatīt stāvokļa ziņojumu</string>
<string name="share">Dalīties</string>
<string name="share_dialog_title">Koplietošana</string>
<string name="share_expiration_date_label">Beidzas %1$s</string>
@ -437,6 +440,7 @@
<string name="status_message">Statusa ziņojums</string>
<string name="storage_description_default">Noklusējuma</string>
<string name="storage_downloads">Lejupielādes</string>
<string name="storage_pictures">Attēli</string>
<string name="sub_folder_rule_year">Gads</string>
<string name="subject_shared_with_you">\"%1$s\" ir bijis kopīgots ar jums</string>
<string name="subject_user_shared_with_you">%1$s koplietots \"%2$s\" ar tevi</string>

View file

@ -34,6 +34,8 @@
<string name="add_to_cloud">Pridaj do %1$s</string>
<string name="advanced_settings">Rozšírené nastavenia</string>
<string name="allow_resharing">Povoliť sprístupňovanie ďalej</string>
<string name="app_config_base_url_title">Základná URL</string>
<string name="app_config_proxy_host_title">Názov proxy servera</string>
<string name="app_config_proxy_port_title">Brána proxy</string>
<string name="app_widget_description">Zobrazí jeden widget z hlavného panela</string>
<string name="appbar_search_in">Hľadať v %s</string>

View file

@ -136,6 +136,7 @@
</trusted-key>
<trusted-key id="4F8FEC6785F611D9A712EA2734918B7D3969D2F5" group="com.google.dagger" name="dagger" version="2.28.3"/>
<trusted-key id="508B736B26AA4F672A10B92C4F91D100EB1F597B" group="org.lukhnos" name="nnio" version="0.3"/>
<trusted-key id="508B736B26AA4F672A10B92C4F91D100EB1F597B" group="org.lukhnos" name="nnio" version="0.3.1"/>
<trusted-key id="517B94F8D0A46317A28D8AB30DA8A5EC02D11EAD" group="net.sf.jopt-simple" name="jopt-simple" version="4.9"/>
<trusted-key id="51B52DC5DD452F92BE342CC2858FC4C4F43856A3" group="xerces" name="xercesImpl" version="2.12.0"/>
<trusted-key id="51BF0A126F41293F40F50A0B8CD7D660AE857DAD" group="com.getkeepsafe.relinker" name="relinker" version="1.4.5"/>
@ -10441,6 +10442,14 @@
<sha256 value="238a7a2002efa66df2cfd313be3ce22a7d206d6ebbfe6b48e0652587830cb2bd" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
</artifact>
</component>
<component group="org.lukhnos" name="nnio" version="0.3.1">
<artifact name="nnio-0.3.1.jar">
<sha256 value="9c411e08406977f2c2027fd8c2790dba1d688a0fca466436a84bf6398943e484" origin="Generated by Gradle"/>
</artifact>
<artifact name="nnio-0.3.1.module">
<sha256 value="ece61075ffbd0c68b66a101689e5e7e8340a73505cab46d231e7c8a4b27aff27" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.mnode.ical4j" name="ical4j" version="3.0.0">
<artifact name="ical4j-3.0.0.jar">
<sha256 value="847ca726fab2b61b95d389a48693f0ca67fcc591a1bccc6f43057157daa433d0" origin="Generated by Gradle"/>

View file

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