diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 40940c1b394e..5026fd90b430 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -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 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 274e40c61746..7f7abf93f42e 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -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 diff --git a/app/build.gradle b/app/build.gradle index aca51f6e447f..0636f73d3674 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java b/app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java index 313713c47f7b..6e24fffa8e8f 100644 --- a/app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java +++ b/app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java @@ -143,6 +143,9 @@ 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 @@ protected void onCreate(Bundle savedInstanceState) { } 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 @@ private void anonymouslyPostLoginRequest(String url) { 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 @@ private void anonymouslyPostLoginRequest(String url) { thread.start(); } - private final ActivityResultLauncher 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 @@ protected void onDestroy() { mOperationsServiceBinder = null; } + Log_OC.d(TAG, "AuthenticatorActivity onDestroy called"); + super.onDestroy(); } @@ -1039,6 +1050,7 @@ private void initLoginInfoView() { cancelButton.setOnClickListener(v -> { loginFlowExecutorService.shutdown(); + ProcessLifecycleOwner.get().getLifecycle().removeObserver(lifecycleEventObserver); recreate(); }); } @@ -1638,7 +1650,7 @@ public void onServiceDisconnected(ComponentName component) { private boolean isRedirectedToTheDefaultBrowser = false; private void poolLogin(PlainClient client) { - loginFlowExecutorService.scheduleAtFixedRate(() -> { + loginFlowExecutorService.scheduleWithFixedDelay(() -> { if (!isLoginProcessCompleted) { performLoginFlowV2(client); } @@ -1694,6 +1706,7 @@ private void completeLoginFlow(String response, int status) { checkOcServer(); loginFlowExecutorService.shutdown(); + ProcessLifecycleOwner.get().getLifecycle().removeObserver(lifecycleEventObserver); } /** diff --git a/app/src/main/java/com/owncloud/android/ui/components/CustomViewPager.java b/app/src/main/java/com/owncloud/android/ui/components/CustomViewPager.java deleted file mode 100644 index d3ce4a5f512b..000000000000 --- a/app/src/main/java/com/owncloud/android/ui/components/CustomViewPager.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2018 Tobias Kaminsky - * 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; - } -} diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java index 0121fd6d2dc3..0b9dd92b523e 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java @@ -59,7 +59,7 @@ 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 @@ @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 @@ private void initViewPager(User user) { 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 @@ private void initViewPager(User user) { 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 @@ private void initViewPager(User user) { 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 void onStart() { 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 void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationRe 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 @@ private void observeWorkerState() { 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 @@ protected void onResume() { 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 void requestForDownload(OCFile file, String downloadBehaviour) { * * @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 void onPageSelected(int position) { } } - // 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. - * + *

* 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 @@ private void previewNewImage(Intent intent) { 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 @@ private void previewNewImage(Intent intent) { 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 = getUser(); + + if (user.isPresent()) { + initViewPager(user.get()); + int newPosition = previewImagePagerAdapter.getFilePosition(file); + if (newPosition >= 0) { + viewPager.setCurrentItem(newPosition); + } } } } @@ -502,19 +485,11 @@ public void toggleFullScreen() { 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); diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java index fd4a7f9a425a..59ac9f8418e3 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java @@ -81,6 +81,7 @@ 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 void onActivityCreated(Bundle savedInstanceState) { 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; } diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java deleted file mode 100644 index 1d39872b8515..000000000000 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2023 Alper Ozturk - * SPDX-FileCopyrightText: 2018 Tobias Kaminsky - * SPDX-FileCopyrightText: 2020 Chris Narkiewicz - * SPDX-FileCopyrightText: 2015 ownCloud Inc. - * SPDX-FileCopyrightText: 2013 David A. Velasco - * 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 mImageFiles; - private User user; - private Set mObsoleteFragments; - private Set mObsoletePositions; - private Set mDownloadErrors; - private FileDataStorageManager mStorageManager; - private SparseArray 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); - } -} diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.kt new file mode 100644 index 000000000000..ac5128fc5874 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.kt @@ -0,0 +1,207 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2023 Alper Ozturk + * SPDX-FileCopyrightText: 2018 Tobias Kaminsky + * SPDX-FileCopyrightText: 2020 Chris Narkiewicz + * SPDX-FileCopyrightText: 2015 ownCloud Inc. + * SPDX-FileCopyrightText: 2013 David A. Velasco + * 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 = mutableListOf() + private val user: User + private val mObsoleteFragments: MutableSet + private val mObsoletePositions: MutableSet + private val mDownloadErrors: MutableSet + private val mStorageManager: FileDataStorageManager + private val mCachedFragments: SparseArray + + /** + * 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 + } +} diff --git a/app/src/main/java/com/owncloud/android/utils/FileUtil.java b/app/src/main/java/com/owncloud/android/utils/FileUtil.java index 09f64481d911..b378f564fe7d 100644 --- a/app/src/main/java/com/owncloud/android/utils/FileUtil.java +++ b/app/src/main/java/com/owncloud/android/utils/FileUtil.java @@ -12,11 +12,6 @@ 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 @@ Long getCreationTimestamp(File file) { return null; } } - - public static Path walkFileTree(Path start, FileVisitor 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; - } } diff --git a/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java b/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java index 941771b11873..9b7242e8f645 100644 --- a/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java +++ b/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java @@ -36,6 +36,7 @@ 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 @@ private static void insertCustomFolderIntoDB(Path path, 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 FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { @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; } diff --git a/app/src/main/res/layout/preview_image_activity.xml b/app/src/main/res/layout/preview_image_activity.xml index a51e50a07841..35746487f5f1 100644 --- a/app/src/main/res/layout/preview_image_activity.xml +++ b/app/src/main/res/layout/preview_image_activity.xml @@ -10,16 +10,17 @@ ~ SPDX-FileCopyrightText: 2013 David A. Velasco ~ SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only) --> - - + android:layout_height="match_parent" /> + android:animateLayoutChanges="false"> %1$s aplicación Android Acerca de versión %1$s + versión %1$s, build #%2$s + Fallo al crear la cuenta Ícono de la cuenta ¡No se encontró la cuenta! Editar + Borrar todas las notificaciones + Vaciar papelera de reciclaje Enviar/Compartir Vista de cuadrícula Vista de lista + Restaurar contactos y calendario Nueva carpeta + Mover o copiar Abrir con Buscar Detalles diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 35bc52075406..86b33da09a7a 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -40,6 +40,7 @@ すべて テキストを入力 失敗 + スケジュール済 完了 不明 入力 diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index 080f09398c09..e3b70d2f7df4 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -157,6 +157,7 @@ Deaktivēt Atmest Noraidīt paziņojumu + Vairāki attēli Pabeigts Nevarēja lejupielādēt %1$s Lejupielāde neizdevās, piesakieties vēlreiz. @@ -383,6 +384,8 @@ Sūtīt Saglabāt kā Izmantot attēlu kā + Iestatīt stāvokli + Iestatīt stāvokļa ziņojumu Dalīties Koplietošana Beidzas %1$s @@ -437,6 +440,7 @@ Statusa ziņojums Noklusējuma Lejupielādes + Attēli Gads \"%1$s\" ir bijis kopīgots ar jums %1$s koplietots \"%2$s\" ar tevi diff --git a/app/src/main/res/values-sk-rSK/strings.xml b/app/src/main/res/values-sk-rSK/strings.xml index 5b1b219f4d05..7e773a88bd4a 100644 --- a/app/src/main/res/values-sk-rSK/strings.xml +++ b/app/src/main/res/values-sk-rSK/strings.xml @@ -34,6 +34,8 @@ Pridaj do %1$s Rozšírené nastavenia Povoliť sprístupňovanie ďalej + Základná URL + Názov proxy servera Brána proxy Zobrazí jeden widget z hlavného panela Hľadať v %s diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 32399fadd697..870c2133c31f 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -136,6 +136,7 @@ + @@ -10441,6 +10442,14 @@ + + + + + + + + diff --git a/scripts/analysis/lint-results.txt b/scripts/analysis/lint-results.txt index fa64b20338d3..9531ef5b263b 100644 --- a/scripts/analysis/lint-results.txt +++ b/scripts/analysis/lint-results.txt @@ -1,2 +1,2 @@ DO NOT TOUCH; GENERATED BY DRONE - Lint Report: 3 errors and 72 warnings + Lint Report: 3 errors and 71 warnings