From 48efcc12fa60c839ab4a57f98c02c83be55636f7 Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Mon, 27 Oct 2025 19:02:50 +0100 Subject: [PATCH 1/7] test(slideshow): Testing deletion for localOnly Testing the case of localOnly files. As the deletion logic differs per scenario, tests for the online scenario will also need to be added. Signed-off-by: Philipp Hasper --- .../test/ConnectivityServiceOfflineMock.kt | 23 +++ .../com/nextcloud/test/LoopFailureHandler.kt | 37 +++++ .../ui/preview/PreviewImageActivityIT.kt | 134 ++++++++++++++++++ 3 files changed, 194 insertions(+) create mode 100644 app/src/androidTest/java/com/nextcloud/test/ConnectivityServiceOfflineMock.kt create mode 100644 app/src/androidTest/java/com/nextcloud/test/LoopFailureHandler.kt create mode 100644 app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewImageActivityIT.kt diff --git a/app/src/androidTest/java/com/nextcloud/test/ConnectivityServiceOfflineMock.kt b/app/src/androidTest/java/com/nextcloud/test/ConnectivityServiceOfflineMock.kt new file mode 100644 index 000000000000..560b94ff6172 --- /dev/null +++ b/app/src/androidTest/java/com/nextcloud/test/ConnectivityServiceOfflineMock.kt @@ -0,0 +1,23 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Philipp Hasper + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.nextcloud.test + +import com.nextcloud.client.network.Connectivity +import com.nextcloud.client.network.ConnectivityService + +/** A mocked connectivity service returning that the device is offline **/ +class ConnectivityServiceOfflineMock : ConnectivityService { + override fun isNetworkAndServerAvailable(callback: ConnectivityService.GenericCallback) { + callback.onComplete(false) + } + + override fun isConnected(): Boolean = false + + override fun isInternetWalled(): Boolean = false + + override fun getConnectivity(): Connectivity = Connectivity.CONNECTED_WIFI +} diff --git a/app/src/androidTest/java/com/nextcloud/test/LoopFailureHandler.kt b/app/src/androidTest/java/com/nextcloud/test/LoopFailureHandler.kt new file mode 100644 index 000000000000..48baf2cec9b0 --- /dev/null +++ b/app/src/androidTest/java/com/nextcloud/test/LoopFailureHandler.kt @@ -0,0 +1,37 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Philipp Hasper + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.nextcloud.test + +import android.content.Context +import android.view.View +import androidx.test.espresso.FailureHandler +import androidx.test.espresso.base.DefaultFailureHandler +import org.hamcrest.Matcher + +/** + * When testing inside of a loop, test failures are hard to attribute. For that, wrap them in an outer + * exception detailing more about the context. + * + * Set the failure handler via + * ``` + * Espresso.setFailureHandler( + * LoopFailureHandler(targetContext, "Test failed in iteration $yourTestIterationCounter") + * ) + * ``` + * and set it back to the default afterwards via + * ``` + * Espresso.setFailureHandler(DefaultFailureHandler(targetContext)) + * ``` + */ +class LoopFailureHandler(targetContext: Context, private val loopMessage: String) : FailureHandler { + private val delegate: FailureHandler = DefaultFailureHandler(targetContext) + + override fun handle(error: Throwable?, viewMatcher: Matcher?) { + // Wrap in additional Exception + delegate.handle(Exception(loopMessage, error), viewMatcher) + } +} diff --git a/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewImageActivityIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewImageActivityIT.kt new file mode 100644 index 000000000000..717fc3fff1c4 --- /dev/null +++ b/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewImageActivityIT.kt @@ -0,0 +1,134 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Philipp Hasper + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.ui.preview + +import androidx.appcompat.widget.ActionBarContainer +import androidx.test.core.app.launchActivity +import androidx.test.espresso.Espresso +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.IdlingRegistry +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.base.DefaultFailureHandler +import androidx.test.espresso.matcher.RootMatchers.isDialog +import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom +import androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.isRoot +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import com.nextcloud.test.ConnectivityServiceOfflineMock +import com.nextcloud.test.LoopFailureHandler +import com.owncloud.android.AbstractOnServerIT +import com.owncloud.android.R +import com.owncloud.android.datamodel.OCFile +import org.hamcrest.Matchers.allOf +import org.junit.Test +import java.io.File + +class PreviewImageActivityIT : AbstractOnServerIT() { + lateinit var testFiles: List + + fun createMockedImageFiles(count: Int, localOnly: Boolean) { + val srcPngFile = getFile("imageFile.png") + testFiles = (0 until count).map { i -> + val pngFile = File(srcPngFile.parent ?: ".", "image$i.png") + srcPngFile.copyTo(pngFile, overwrite = true) + + OCFile("/${pngFile.name}").apply { + storagePath = pngFile.absolutePath + mimeType = "image/png" + modificationTimestamp = 1000000 + permissions = "D" // OCFile.PERMISSION_CAN_DELETE_OR_LEAVE_SHARE. Required for deletion button to show + remoteId = if (localOnly) null else "abc-mocked-remote-id" // mocking the file to be on the server + }.also { + storageManager.saveNewFile(it) + } + } + } + + fun veryImageThenDelete(index: Int) { + val currentFileName = testFiles[index].fileName + Espresso.setFailureHandler( + LoopFailureHandler(targetContext, "Test failed with image file index $index, $currentFileName") + ) + + onView(withId(R.id.image)) + .check(matches(isDisplayed())) + + // Check that the Action Bar shows the file name as title + onView( + allOf( + isDescendantOfA(isAssignableFrom(ActionBarContainer::class.java)), + withText(currentFileName) + ) + ).check(matches(isDisplayed())) + + // Open the Action Bar's overflow menu. + // The official way would be: + // openActionBarOverflowOrOptionsMenu(targetContext) + // But this doesn't find the view. Presumably because Espresso.OVERFLOW_BUTTON_MATCHER looks for the description + // "More options", whereas it actually says "More menu". + // selecting by this would also work: + // onView(withContentDescription("More menu")).perform(ViewActions.click()) + // For now, we identify it by the ID we know it to be + onView(withId(R.id.custom_menu_placeholder_item)).perform(ViewActions.click()) + + // Click the "Remove" button + onView(withText(R.string.common_remove)).perform(ViewActions.click()) + + // Check confirmation dialog and then confirm the deletion by clicking the main button of the dialog + val expectedText = targetContext.getString(R.string.confirmation_remove_file_alert, currentFileName) + onView(withId(android.R.id.message)) + .inRoot(isDialog()) + .check(matches(withText(expectedText))) + + onView(withId(android.R.id.button1)) + .inRoot(isDialog()) + .check(matches(withText(R.string.file_delete))) + .perform(ViewActions.click()) + + Espresso.setFailureHandler(DefaultFailureHandler(targetContext)) + } + + @Test + fun deleteFromSlideshow_localOnly_online() { + // Prepare local test data + val imageCount = 5 + createMockedImageFiles(imageCount, localOnly = true) + + // Launch the activity with the first image + val intent = PreviewImageActivity.previewFileIntent(targetContext, user, testFiles[0]) + launchActivity(intent).use { + onView(isRoot()).check(matches(isDisplayed())) + + for (i in 0 until imageCount) { + veryImageThenDelete(i) + } + } + } + + @Test + fun deleteFromSlideshow_localOnly_offline() { + // Prepare local test data + val imageCount = 5 + createMockedImageFiles(imageCount, localOnly = true) + + // Launch the activity with the first image + val intent = PreviewImageActivity.previewFileIntent(targetContext, user, testFiles[0]) + launchActivity(intent).use { scenario -> + scenario.onActivity { activity -> + activity.connectivityService = ConnectivityServiceOfflineMock() + } + onView(isRoot()).check(matches(isDisplayed())) + + for (i in 0 until imageCount) { + veryImageThenDelete(i) + } + } + } +} From c68bbaab681747a413db71459ef062cc1ce4a03e Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Mon, 15 Dec 2025 18:44:08 +0100 Subject: [PATCH 2/7] fix(slideshow): After file deletion, properly refresh view pager Otherwise, the localOnly file deletion will not update the UI. Signed-off-by: Philipp Hasper --- .../android/ui/dialog/RemoveFilesDialogFragment.kt | 4 ++++ .../owncloud/android/ui/preview/PreviewImageActivity.kt | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt b/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt index c27956afe82e..437cbed483d6 100644 --- a/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt @@ -24,6 +24,7 @@ import com.owncloud.android.datamodel.OCFile import com.owncloud.android.ui.activity.FileActivity import com.owncloud.android.ui.activity.FileDisplayActivity import com.owncloud.android.ui.dialog.ConfirmationDialogFragment.ConfirmationDialogFragmentListener +import com.owncloud.android.ui.preview.PreviewImageActivity import javax.inject.Inject /** @@ -96,6 +97,7 @@ class RemoveFilesDialogFragment : val fileActivity = getTypedActivity(FileActivity::class.java) val fda = getTypedActivity(FileDisplayActivity::class.java) + val pia = getTypedActivity(PreviewImageActivity::class.java) fileActivity?.connectivityService?.isNetworkAndServerAvailable { result -> if (result) { fileActivity.showLoadingDialog(fileActivity.getString(R.string.wait_a_moment)) @@ -110,6 +112,7 @@ class RemoveFilesDialogFragment : if (offlineFiles.isNotEmpty()) { fda?.refreshCurrentDirectory() + pia?.initViewPager() } fileActivity.dismissLoadingDialog() @@ -123,6 +126,7 @@ class RemoveFilesDialogFragment : } fda?.refreshCurrentDirectory() + pia?.initViewPager() } finishActionMode() diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt index ecd7ac821d0c..a0d04ba7bc36 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt @@ -208,7 +208,13 @@ class PreviewImageActivity : } } - private fun updateViewPagerAfterDeletionAndAdvanceForward() { + fun initViewPager() { + if (user.isPresent) { + initViewPager(user.get()) + } + } + + fun updateViewPagerAfterDeletionAndAdvanceForward() { val deletePosition = viewPager?.currentItem ?: return previewImagePagerAdapter?.let { adapter -> val nextPosition = min(deletePosition, adapter.itemCount - 1) From 1e5714e7a610d4a3d8ef63b59ff285c1bc55ffdc Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Thu, 18 Dec 2025 19:47:19 +0100 Subject: [PATCH 3/7] test(slideshow): Testing deletion for remote files The remote + offline test case had to be ignored due to two reasons 1) Broken App behavior - The UX is indeed broken, as from a user perspective, nothing happens with the file when deleting it. The offlineOperation is put on the worker stack, but the user doesn't see anything from it - Even when coming back online, it is completely unreliable when the deletion will be finally done. It might happen 5 or 10 minutes later 2) Broken test mock - The mocked connectivityService doesn't work as expected, because the OfflineOperationsWorker has its own service, and thus might still execute the deletion, but just at an unforseable time during the test execution - see problem 1). Signed-off-by: Philipp Hasper --- .../test/FileRemovedIdlingResource.kt | 51 +++++++ .../ui/preview/PreviewImageActivityIT.kt | 138 ++++++++++++++---- 2 files changed, 160 insertions(+), 29 deletions(-) create mode 100644 app/src/androidTest/java/com/nextcloud/test/FileRemovedIdlingResource.kt diff --git a/app/src/androidTest/java/com/nextcloud/test/FileRemovedIdlingResource.kt b/app/src/androidTest/java/com/nextcloud/test/FileRemovedIdlingResource.kt new file mode 100644 index 000000000000..8d52cc303047 --- /dev/null +++ b/app/src/androidTest/java/com/nextcloud/test/FileRemovedIdlingResource.kt @@ -0,0 +1,51 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Philipp Hasper + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.nextcloud.test + +import androidx.test.espresso.IdlingResource +import com.owncloud.android.datamodel.FileDataStorageManager +import com.owncloud.android.datamodel.OCFile +import java.util.concurrent.atomic.AtomicLong +import java.util.concurrent.atomic.AtomicReference + +/** + * IdlingResource that can be reused to watch the removal of different file ids sequentially. + * + * Use setFileId(fileId) before triggering the deletion. The resource will call the Espresso callback + * once the file no longer exists. Call unregister from IdlingRegistry in @After. + */ +class FileRemovedIdlingResource(private val storageManager: FileDataStorageManager) : IdlingResource { + private var resourceCallback: IdlingResource.ResourceCallback? = null + + // null means "no file set" + private var currentFile = AtomicReference(null) + + override fun getName(): String = "${this::class.java.simpleName}" + + override fun isIdleNow(): Boolean { + val file = currentFile.get() + // If no file set, consider idle. If file set, idle only if it doesn't exist. + val idle = file == null || (!storageManager.fileExists(file.fileId) && !file.exists()) + if (idle && file != null) { + // if we detect it's already removed, notify and clear + resourceCallback?.onTransitionToIdle() + currentFile.set(null) + } + return idle + } + + override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback) { + this.resourceCallback = callback + } + + /** + * Start watching the given file. Call this right before performing the UI action that triggers deletion. + */ + fun setFile(file: OCFile) { + currentFile.set(file) + } +} diff --git a/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewImageActivityIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewImageActivityIT.kt index 717fc3fff1c4..d99f6d0ae6a8 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewImageActivityIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewImageActivityIT.kt @@ -22,20 +22,34 @@ import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import com.nextcloud.test.ConnectivityServiceOfflineMock +import com.nextcloud.test.FileRemovedIdlingResource import com.nextcloud.test.LoopFailureHandler import com.owncloud.android.AbstractOnServerIT import com.owncloud.android.R import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.db.OCUpload +import com.owncloud.android.files.services.NameCollisionPolicy +import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation import org.hamcrest.Matchers.allOf +import org.junit.After +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Ignore import org.junit.Test import java.io.File class PreviewImageActivityIT : AbstractOnServerIT() { - lateinit var testFiles: List + companion object { + private const val REMOTE_FOLDER: String = "/PreviewImageActivityIT/" + } + + var fileRemovedIdlingResource = FileRemovedIdlingResource(storageManager) - fun createMockedImageFiles(count: Int, localOnly: Boolean) { + @Suppress("SameParameterValue") + private fun createLocalMockedImageFiles(count: Int): List { val srcPngFile = getFile("imageFile.png") - testFiles = (0 until count).map { i -> + return (0 until count).map { i -> val pngFile = File(srcPngFile.parent ?: ".", "image$i.png") srcPngFile.copyTo(pngFile, overwrite = true) @@ -44,19 +58,48 @@ class PreviewImageActivityIT : AbstractOnServerIT() { mimeType = "image/png" modificationTimestamp = 1000000 permissions = "D" // OCFile.PERMISSION_CAN_DELETE_OR_LEAVE_SHARE. Required for deletion button to show - remoteId = if (localOnly) null else "abc-mocked-remote-id" // mocking the file to be on the server }.also { storageManager.saveNewFile(it) } } } - fun veryImageThenDelete(index: Int) { - val currentFileName = testFiles[index].fileName + /** + * Create image files and upload them to the connected server. + * + * This function relies on the images not existing beforehand, as AbstractOnServerIT#deleteAllFilesOnServer() + * should clean up. If it does fail, likely because that clean up didn't work and there are leftovers from + * a previous run + * @param count Number of files to create + * @param folder Parent folder to which to upload. Must start and end with a slash + */ + private fun createAndUploadImageFiles(count: Int, folder: String = REMOTE_FOLDER): List { + val srcPngFile = getFile("imageFile.png") + return (0 until count).map { i -> + val pngFile = File(srcPngFile.parent ?: ".", "image$i.png") + srcPngFile.copyTo(pngFile, overwrite = true) + + val ocUpload = OCUpload( + pngFile.absolutePath, + folder + pngFile.name, + account.name + ).apply { + nameCollisionPolicy = NameCollisionPolicy.OVERWRITE + } + uploadOCUpload(ocUpload) + + fileDataStorageManager.getFileByDecryptedRemotePath(folder + pngFile.name)!! + } + } + + private fun veryImageThenDelete(testFile: OCFile) { Espresso.setFailureHandler( - LoopFailureHandler(targetContext, "Test failed with image file index $index, $currentFileName") + LoopFailureHandler(targetContext, "Test failed with image file ${testFile.fileName}") ) + assertTrue(testFile.exists()) + assertTrue(testFile.fileExists()) + onView(withId(R.id.image)) .check(matches(isDisplayed())) @@ -64,7 +107,7 @@ class PreviewImageActivityIT : AbstractOnServerIT() { onView( allOf( isDescendantOfA(isAssignableFrom(ActionBarContainer::class.java)), - withText(currentFileName) + withText(testFile.fileName) ) ).check(matches(isDisplayed())) @@ -82,7 +125,7 @@ class PreviewImageActivityIT : AbstractOnServerIT() { onView(withText(R.string.common_remove)).perform(ViewActions.click()) // Check confirmation dialog and then confirm the deletion by clicking the main button of the dialog - val expectedText = targetContext.getString(R.string.confirmation_remove_file_alert, currentFileName) + val expectedText = targetContext.getString(R.string.confirmation_remove_file_alert, testFile.fileName) onView(withId(android.R.id.message)) .inRoot(isDialog()) .check(matches(withText(expectedText))) @@ -92,43 +135,80 @@ class PreviewImageActivityIT : AbstractOnServerIT() { .check(matches(withText(R.string.file_delete))) .perform(ViewActions.click()) + // Register the idling resource to wait for successful deletion + fileRemovedIdlingResource.setFile(testFile) + + // Wait for idle, then verify that the file is gone. Somehow waitForIdleSync() doesn't work and we need onIdle() + Espresso.onIdle() + assertFalse("test file still exists: ${testFile.fileName}", testFile.exists()) + Espresso.setFailureHandler(DefaultFailureHandler(targetContext)) } - @Test - fun deleteFromSlideshow_localOnly_online() { + @Before + fun bringUp() { + IdlingRegistry.getInstance().register(fileRemovedIdlingResource) + } + + @After + fun tearDown() { + IdlingRegistry.getInstance().unregister(fileRemovedIdlingResource) + } + + private fun testDeleteFromSlideshow_impl(localOnly: Boolean, offline: Boolean) { // Prepare local test data val imageCount = 5 - createMockedImageFiles(imageCount, localOnly = true) + val testFiles = if (localOnly) { + createLocalMockedImageFiles( + imageCount + ) + } else { + createAndUploadImageFiles(imageCount) + } // Launch the activity with the first image val intent = PreviewImageActivity.previewFileIntent(targetContext, user, testFiles[0]) - launchActivity(intent).use { + launchActivity(intent).use { scenario -> + if (offline) { + scenario.onActivity { activity -> + activity.connectivityService = ConnectivityServiceOfflineMock() + } + } onView(isRoot()).check(matches(isDisplayed())) - for (i in 0 until imageCount) { - veryImageThenDelete(i) + for (testFile in testFiles) { + veryImageThenDelete(testFile) + assertTrue( + "Test file still exists on the server: ${testFile.remotePath}", + ExistenceCheckRemoteOperation(testFile.remotePath, true).execute(client).isSuccess + ) } } } + @Test + fun deleteFromSlideshow_localOnly_online() { + testDeleteFromSlideshow_impl(localOnly = true, offline = false) + } + @Test fun deleteFromSlideshow_localOnly_offline() { - // Prepare local test data - val imageCount = 5 - createMockedImageFiles(imageCount, localOnly = true) + testDeleteFromSlideshow_impl(localOnly = true, offline = true) + } - // Launch the activity with the first image - val intent = PreviewImageActivity.previewFileIntent(targetContext, user, testFiles[0]) - launchActivity(intent).use { scenario -> - scenario.onActivity { activity -> - activity.connectivityService = ConnectivityServiceOfflineMock() - } - onView(isRoot()).check(matches(isDisplayed())) + @Test + fun deleteFromSlideshow_remote_online() { + testDeleteFromSlideshow_impl(localOnly = false, offline = false) + } - for (i in 0 until imageCount) { - veryImageThenDelete(i) - } - } + @Test + @Ignore( + "Offline deletion is following a different UX and it is also brittle: Deletion might happen 10 minutes later" + ) + fun deleteFromSlideshow_remote_offline() { + // Note: the offline mock doesn't actually do what it is supposed to. The OfflineOperationsWorker uses its + // own connectivityService, which is online, and may still execute the server deletion. + // You'll need to address this, should you activate that test. Otherwise it might not catch all error cases + testDeleteFromSlideshow_impl(localOnly = false, offline = true) } } From e24a3b133c4522ca7bba5387b9cccb4b095e797c Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Mon, 22 Dec 2025 15:27:14 +0100 Subject: [PATCH 4/7] test(slideshow): Test different deletion points The existing test cases varied by configuration: local vs. remote, online vs. offline. This is now extended by also starting at different entry points: beginning, end and middle of the list. Signed-off-by: Philipp Hasper --- .../ui/preview/PreviewImageActivityIT.kt | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewImageActivityIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewImageActivityIT.kt index d99f6d0ae6a8..3ce7a9333b6d 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewImageActivityIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewImageActivityIT.kt @@ -145,18 +145,11 @@ class PreviewImageActivityIT : AbstractOnServerIT() { Espresso.setFailureHandler(DefaultFailureHandler(targetContext)) } - @Before - fun bringUp() { - IdlingRegistry.getInstance().register(fileRemovedIdlingResource) - } - - @After - fun tearDown() { - IdlingRegistry.getInstance().unregister(fileRemovedIdlingResource) - } - - private fun testDeleteFromSlideshow_impl(localOnly: Boolean, offline: Boolean) { - // Prepare local test data + private fun executeDeletionTestScenario( + localOnly: Boolean, + offline: Boolean, + fileListTransformation: (List) -> List + ) { val imageCount = 5 val testFiles = if (localOnly) { createLocalMockedImageFiles( @@ -165,9 +158,9 @@ class PreviewImageActivityIT : AbstractOnServerIT() { } else { createAndUploadImageFiles(imageCount) } + val expectedFileOrder = fileListTransformation(testFiles) - // Launch the activity with the first image - val intent = PreviewImageActivity.previewFileIntent(targetContext, user, testFiles[0]) + val intent = PreviewImageActivity.previewFileIntent(targetContext, user, expectedFileOrder.first()) launchActivity(intent).use { scenario -> if (offline) { scenario.onActivity { activity -> @@ -176,7 +169,7 @@ class PreviewImageActivityIT : AbstractOnServerIT() { } onView(isRoot()).check(matches(isDisplayed())) - for (testFile in testFiles) { + for (testFile in expectedFileOrder) { veryImageThenDelete(testFile) assertTrue( "Test file still exists on the server: ${testFile.remotePath}", @@ -186,6 +179,27 @@ class PreviewImageActivityIT : AbstractOnServerIT() { } } + private fun testDeleteFromSlideshow_impl(localOnly: Boolean, offline: Boolean) { + // Case 1: start at first image + executeDeletionTestScenario(localOnly, offline) { list -> list } + // Case 2: start at last image (reversed) + executeDeletionTestScenario(localOnly, offline) { list -> list.reversed() } + // Case 3: Start in the middle. From middle to the end, then backwards through remaining files of the first half + executeDeletionTestScenario(localOnly, offline) { list -> + list.subList(list.size / 2, list.size) + list.subList(0, list.size / 2).reversed() + } + } + + @Before + fun bringUp() { + IdlingRegistry.getInstance().register(fileRemovedIdlingResource) + } + + @After + fun tearDown() { + IdlingRegistry.getInstance().unregister(fileRemovedIdlingResource) + } + @Test fun deleteFromSlideshow_localOnly_online() { testDeleteFromSlideshow_impl(localOnly = true, offline = false) From 8bcbfead3b5518c537b9b7483996717d1ce3555b Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Sun, 8 Feb 2026 11:00:56 +0100 Subject: [PATCH 5/7] fix(slideshow) Dependency-inject ConnectivityService Towards a better decoupling of the RemoveFilesDialogFragment from any expectations about activities currently running. Signed-off-by: Philipp Hasper --- .../ui/dialog/RemoveFilesDialogFragment.kt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt b/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt index 437cbed483d6..35efb2fc7a40 100644 --- a/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt @@ -17,6 +17,7 @@ import android.view.ActionMode import androidx.appcompat.app.AlertDialog import com.google.android.material.button.MaterialButton import com.nextcloud.client.di.Injectable +import com.nextcloud.client.network.ConnectivityService import com.nextcloud.utils.extensions.getTypedActivity import com.owncloud.android.R import com.owncloud.android.datamodel.FileDataStorageManager @@ -41,6 +42,8 @@ class RemoveFilesDialogFragment : @Inject lateinit var fileDataStorageManager: FileDataStorageManager + @Inject + lateinit var connectivityService: ConnectivityService private var positiveButton: MaterialButton? = null override fun onStart() { @@ -98,16 +101,16 @@ class RemoveFilesDialogFragment : val fileActivity = getTypedActivity(FileActivity::class.java) val fda = getTypedActivity(FileDisplayActivity::class.java) val pia = getTypedActivity(PreviewImageActivity::class.java) - fileActivity?.connectivityService?.isNetworkAndServerAvailable { result -> - if (result) { - fileActivity.showLoadingDialog(fileActivity.getString(R.string.wait_a_moment)) + connectivityService.isNetworkAndServerAvailable { isAvailable -> + if (isAvailable) { + fileActivity?.showLoadingDialog(fileActivity.getString(R.string.wait_a_moment)) fda?.deleteBatchTracker?.startBatchDelete(files.size) if (files.isNotEmpty()) { // Display the snackbar message only when a single file is deleted. val inBackground = (files.size != 1) - fileActivity.fileOperationsHelper?.removeFiles(files, onlyLocalCopy, inBackground) + fileActivity?.fileOperationsHelper?.removeFiles(files, onlyLocalCopy, inBackground) } if (offlineFiles.isNotEmpty()) { @@ -115,10 +118,10 @@ class RemoveFilesDialogFragment : pia?.initViewPager() } - fileActivity.dismissLoadingDialog() + fileActivity?.dismissLoadingDialog() } else { if (onlyLocalCopy) { - fileActivity.fileOperationsHelper?.removeFiles(files, true, true) + fileActivity?.fileOperationsHelper?.removeFiles(files, true, true) } else { files.forEach { file -> fileDataStorageManager.addRemoveFileOfflineOperation(file) From c62cc28854ecdb662a3b23a513319fbadf67074b Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Sat, 14 Feb 2026 10:28:25 +0100 Subject: [PATCH 6/7] fix(slideshow): Use EventBus to notify activities of file deletion Reduces the coupling, using an already existing EventBus pattern. During registration, we need to first check if it is already registered. Otherwise, the app crashed directly at first start with the error org.greenrobot.eventbus.EventBusException: Subscriber class com.owncloud.android.ui.activity.FileDisplayActivity already registered to event class com.owncloud.android.ui.events.FilesRefreshEvent Signed-off-by: Philipp Hasper --- .../ui/activity/FileDisplayActivity.kt | 30 +++++++++++++++---- .../ui/dialog/RemoveFilesDialogFragment.kt | 12 ++++---- .../android/ui/events/FilesRefreshEvent.kt | 13 ++++++++ .../ui/preview/PreviewImageActivity.kt | 23 ++++++++++++++ 4 files changed, 65 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/com/owncloud/android/ui/events/FilesRefreshEvent.kt diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index dea787218cc3..9e08726ac458 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Philipp Hasper * SPDX-FileCopyrightText: 2025 Alper Ozturk * SPDX-FileCopyrightText: 2023-2024 TSI-mc * SPDX-FileCopyrightText: 2023 Archontis E. Kostis @@ -121,6 +122,7 @@ import com.owncloud.android.ui.dialog.SendShareDialog.SendShareDialogDownloader import com.owncloud.android.ui.dialog.SortingOrderDialogFragment.OnSortingOrderListener import com.owncloud.android.ui.dialog.StoragePermissionDialogFragment import com.owncloud.android.ui.dialog.TermsOfServiceDialog +import com.owncloud.android.ui.events.FilesRefreshEvent import com.owncloud.android.ui.events.SearchEvent import com.owncloud.android.ui.events.SyncEventFinished import com.owncloud.android.ui.events.TokenPushEvent @@ -1454,12 +1456,6 @@ class FileDisplayActivity : } // endregion - override fun onStop() { - Log_OC.v(TAG, "onStop()") - unregisterReceivers() - super.onStop() - } - override fun onSortingOrderChosen(selection: FileSortOrder?) { val ocFileListFragment = this.listOfFilesFragment ocFileListFragment?.sortFiles(selection) @@ -2768,6 +2764,11 @@ class FileDisplayActivity : registerReceivers() + // Register for the FilesRefreshEvent, only if not already registered + if (!EventBus.getDefault().isRegistered(this)) { + EventBus.getDefault().register(this) + } + if (SettingsActivity.isBackPressed) { Log_OC.d(TAG, "User returned from settings activity, skipping reset content logic") return @@ -2776,6 +2777,15 @@ class FileDisplayActivity : initFile() } + public override fun onStop() { + Log_OC.v(TAG, "onStop()") + if (EventBus.getDefault().isRegistered(this)) { + EventBus.getDefault().unregister(this) + } + unregisterReceivers() + super.onStop() + } + private fun initFile() { val userOpt = user if (userOpt.isEmpty) { @@ -3026,6 +3036,14 @@ class FileDisplayActivity : }) } + // region EventBus + @Suppress("unused") + @Subscribe(threadMode = ThreadMode.MAIN) + fun onFilesRefreshNeeded(event: FilesRefreshEvent) { + refreshCurrentDirectory() + } + // endregion + private fun handleEcosystemIntent(intent: Intent?) { ecosystemManager.receiveAccount( intent, diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt b/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt index 35efb2fc7a40..9708505ae95d 100644 --- a/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Philipp Hasper * SPDX-FileCopyrightText: 2023 Alper Ozturk * SPDX-FileCopyrightText: 2018 Andy Scherzinger * SPDX-FileCopyrightText: 2018 Jessie Chatham Spencer @@ -25,7 +26,8 @@ import com.owncloud.android.datamodel.OCFile import com.owncloud.android.ui.activity.FileActivity import com.owncloud.android.ui.activity.FileDisplayActivity import com.owncloud.android.ui.dialog.ConfirmationDialogFragment.ConfirmationDialogFragmentListener -import com.owncloud.android.ui.preview.PreviewImageActivity +import com.owncloud.android.ui.events.FilesRefreshEvent +import org.greenrobot.eventbus.EventBus import javax.inject.Inject /** @@ -100,7 +102,6 @@ class RemoveFilesDialogFragment : val fileActivity = getTypedActivity(FileActivity::class.java) val fda = getTypedActivity(FileDisplayActivity::class.java) - val pia = getTypedActivity(PreviewImageActivity::class.java) connectivityService.isNetworkAndServerAvailable { isAvailable -> if (isAvailable) { fileActivity?.showLoadingDialog(fileActivity.getString(R.string.wait_a_moment)) @@ -114,8 +115,7 @@ class RemoveFilesDialogFragment : } if (offlineFiles.isNotEmpty()) { - fda?.refreshCurrentDirectory() - pia?.initViewPager() + EventBus.getDefault().post(FilesRefreshEvent()) } fileActivity?.dismissLoadingDialog() @@ -127,9 +127,7 @@ class RemoveFilesDialogFragment : fileDataStorageManager.addRemoveFileOfflineOperation(file) } } - - fda?.refreshCurrentDirectory() - pia?.initViewPager() + EventBus.getDefault().post(FilesRefreshEvent()) } finishActionMode() diff --git a/app/src/main/java/com/owncloud/android/ui/events/FilesRefreshEvent.kt b/app/src/main/java/com/owncloud/android/ui/events/FilesRefreshEvent.kt new file mode 100644 index 000000000000..2d529b5de12d --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/events/FilesRefreshEvent.kt @@ -0,0 +1,13 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.ui.events + +/** + * Event denoting the need to refresh the files displayed in the current view + */ +class FilesRefreshEvent diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt index a0d04ba7bc36..b3ae8e0d60fa 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Philipp Hasper * SPDX-FileCopyrightText: 2024 Alper Ozturk * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -45,6 +46,7 @@ import com.owncloud.android.operations.RemoveFileOperation import com.owncloud.android.operations.SynchronizeFileOperation import com.owncloud.android.ui.activity.FileActivity import com.owncloud.android.ui.activity.FileDisplayActivity +import com.owncloud.android.ui.events.FilesRefreshEvent import com.owncloud.android.ui.fragment.FileFragment import com.owncloud.android.ui.fragment.GalleryFragment import com.owncloud.android.ui.fragment.OCFileListFragment @@ -52,6 +54,9 @@ import com.owncloud.android.ui.preview.model.PreviewImageActivityState import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.MimeTypeUtil import edu.umd.cs.findbugs.annotations.SuppressFBWarnings +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode import java.io.Serializable import javax.inject.Inject import kotlin.math.max @@ -208,6 +213,14 @@ class PreviewImageActivity : } } + // region EventBus + @Suppress("unused") + @Subscribe(threadMode = ThreadMode.MAIN) + fun onFilesRefreshNeeded(event: FilesRefreshEvent) { + initViewPager() + } + // endregion + fun initViewPager() { if (user.isPresent) { initViewPager(user.get()) @@ -259,6 +272,12 @@ class PreviewImageActivity : public override fun onStart() { super.onStart() registerReceivers() + + // Register for the FilesRefreshEvent, only if not already registered + if (!EventBus.getDefault().isRegistered(this)) { + EventBus.getDefault().register(this) + } + val optionalUser = user if (optionalUser.isPresent) { var file: OCFile? = file ?: throw IllegalStateException("Instanced with a NULL OCFile") @@ -399,6 +418,10 @@ class PreviewImageActivity : downloadFinishReceiver = null } + if (EventBus.getDefault().isRegistered(this)) { + EventBus.getDefault().unregister(this) + } + super.onStop() } From ba85d162a290a263c1b62a2b2fe052c6de49c090 Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Sat, 21 Feb 2026 10:37:22 +0100 Subject: [PATCH 7/7] fix(slideshow) Use interface instead of EventBus EventBus is considered deprecated by the maintainers and we want to get rid of it eventually. So extending its usage is not the right approach. Instead, a callback interface is defined. The pre-existing pattern of looking up the enclosing activity via getTypedActivity() is used to find the callback. Long-term, the getTypedActivity() might also be a pattern we want to get rid of, but it would require a pretty thorough refactoring of the FileDisplayActivity to make it work. Signed-off-by: Philipp Hasper --- .../ui/activity/FileDisplayActivity.kt | 28 ++++++------------- .../OnFilesRemovedListener.kt} | 9 +++--- .../ui/dialog/RemoveFilesDialogFragment.kt | 9 +++--- .../ui/preview/PreviewImageActivity.kt | 21 ++------------ 4 files changed, 20 insertions(+), 47 deletions(-) rename app/src/main/java/com/owncloud/android/ui/{events/FilesRefreshEvent.kt => activity/OnFilesRemovedListener.kt} (52%) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 9e08726ac458..2925b1ba3b62 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -122,7 +122,6 @@ import com.owncloud.android.ui.dialog.SendShareDialog.SendShareDialogDownloader import com.owncloud.android.ui.dialog.SortingOrderDialogFragment.OnSortingOrderListener import com.owncloud.android.ui.dialog.StoragePermissionDialogFragment import com.owncloud.android.ui.dialog.TermsOfServiceDialog -import com.owncloud.android.ui.events.FilesRefreshEvent import com.owncloud.android.ui.events.SearchEvent import com.owncloud.android.ui.events.SyncEventFinished import com.owncloud.android.ui.events.TokenPushEvent @@ -187,6 +186,7 @@ class FileDisplayActivity : OnEnforceableRefreshListener, OnSortingOrderListener, SendShareDialogDownloader, + OnFilesRemovedListener, Injectable { private lateinit var binding: FilesBinding @@ -1456,6 +1456,12 @@ class FileDisplayActivity : } // endregion + override fun onStop() { + Log_OC.v(TAG, "onStop()") + unregisterReceivers() + super.onStop() + } + override fun onSortingOrderChosen(selection: FileSortOrder?) { val ocFileListFragment = this.listOfFilesFragment ocFileListFragment?.sortFiles(selection) @@ -2764,11 +2770,6 @@ class FileDisplayActivity : registerReceivers() - // Register for the FilesRefreshEvent, only if not already registered - if (!EventBus.getDefault().isRegistered(this)) { - EventBus.getDefault().register(this) - } - if (SettingsActivity.isBackPressed) { Log_OC.d(TAG, "User returned from settings activity, skipping reset content logic") return @@ -2777,15 +2778,6 @@ class FileDisplayActivity : initFile() } - public override fun onStop() { - Log_OC.v(TAG, "onStop()") - if (EventBus.getDefault().isRegistered(this)) { - EventBus.getDefault().unregister(this) - } - unregisterReceivers() - super.onStop() - } - private fun initFile() { val userOpt = user if (userOpt.isEmpty) { @@ -3036,13 +3028,9 @@ class FileDisplayActivity : }) } - // region EventBus - @Suppress("unused") - @Subscribe(threadMode = ThreadMode.MAIN) - fun onFilesRefreshNeeded(event: FilesRefreshEvent) { + override fun onFilesRemoved() { refreshCurrentDirectory() } - // endregion private fun handleEcosystemIntent(intent: Intent?) { ecosystemManager.receiveAccount( diff --git a/app/src/main/java/com/owncloud/android/ui/events/FilesRefreshEvent.kt b/app/src/main/java/com/owncloud/android/ui/activity/OnFilesRemovedListener.kt similarity index 52% rename from app/src/main/java/com/owncloud/android/ui/events/FilesRefreshEvent.kt rename to app/src/main/java/com/owncloud/android/ui/activity/OnFilesRemovedListener.kt index 2d529b5de12d..edd275ee79fe 100644 --- a/app/src/main/java/com/owncloud/android/ui/events/FilesRefreshEvent.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/OnFilesRemovedListener.kt @@ -5,9 +5,8 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -package com.owncloud.android.ui.events +package com.owncloud.android.ui.activity -/** - * Event denoting the need to refresh the files displayed in the current view - */ -class FilesRefreshEvent +interface OnFilesRemovedListener { + fun onFilesRemoved() +} diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt b/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt index 9708505ae95d..9e8d98c562c4 100644 --- a/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt @@ -25,9 +25,8 @@ import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile import com.owncloud.android.ui.activity.FileActivity import com.owncloud.android.ui.activity.FileDisplayActivity +import com.owncloud.android.ui.activity.OnFilesRemovedListener import com.owncloud.android.ui.dialog.ConfirmationDialogFragment.ConfirmationDialogFragmentListener -import com.owncloud.android.ui.events.FilesRefreshEvent -import org.greenrobot.eventbus.EventBus import javax.inject.Inject /** @@ -46,6 +45,7 @@ class RemoveFilesDialogFragment : @Inject lateinit var connectivityService: ConnectivityService + private var positiveButton: MaterialButton? = null override fun onStart() { @@ -102,6 +102,7 @@ class RemoveFilesDialogFragment : val fileActivity = getTypedActivity(FileActivity::class.java) val fda = getTypedActivity(FileDisplayActivity::class.java) + val filesRemovedListener = getTypedActivity(OnFilesRemovedListener::class.java) connectivityService.isNetworkAndServerAvailable { isAvailable -> if (isAvailable) { fileActivity?.showLoadingDialog(fileActivity.getString(R.string.wait_a_moment)) @@ -115,7 +116,7 @@ class RemoveFilesDialogFragment : } if (offlineFiles.isNotEmpty()) { - EventBus.getDefault().post(FilesRefreshEvent()) + filesRemovedListener?.onFilesRemoved() } fileActivity?.dismissLoadingDialog() @@ -127,7 +128,7 @@ class RemoveFilesDialogFragment : fileDataStorageManager.addRemoveFileOfflineOperation(file) } } - EventBus.getDefault().post(FilesRefreshEvent()) + filesRemovedListener?.onFilesRemoved() } finishActionMode() diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt index b3ae8e0d60fa..c30e731e1a59 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt @@ -46,7 +46,7 @@ import com.owncloud.android.operations.RemoveFileOperation import com.owncloud.android.operations.SynchronizeFileOperation import com.owncloud.android.ui.activity.FileActivity import com.owncloud.android.ui.activity.FileDisplayActivity -import com.owncloud.android.ui.events.FilesRefreshEvent +import com.owncloud.android.ui.activity.OnFilesRemovedListener import com.owncloud.android.ui.fragment.FileFragment import com.owncloud.android.ui.fragment.GalleryFragment import com.owncloud.android.ui.fragment.OCFileListFragment @@ -54,9 +54,6 @@ import com.owncloud.android.ui.preview.model.PreviewImageActivityState import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.MimeTypeUtil import edu.umd.cs.findbugs.annotations.SuppressFBWarnings -import org.greenrobot.eventbus.EventBus -import org.greenrobot.eventbus.Subscribe -import org.greenrobot.eventbus.ThreadMode import java.io.Serializable import javax.inject.Inject import kotlin.math.max @@ -70,6 +67,7 @@ class PreviewImageActivity : FileActivity(), FileFragment.ContainerActivity, OnRemoteOperationListener, + OnFilesRemovedListener, Injectable { private var livePhotoFile: OCFile? = null private var viewPager: ViewPager2? = null @@ -213,13 +211,9 @@ class PreviewImageActivity : } } - // region EventBus - @Suppress("unused") - @Subscribe(threadMode = ThreadMode.MAIN) - fun onFilesRefreshNeeded(event: FilesRefreshEvent) { + override fun onFilesRemoved() { initViewPager() } - // endregion fun initViewPager() { if (user.isPresent) { @@ -273,11 +267,6 @@ class PreviewImageActivity : super.onStart() registerReceivers() - // Register for the FilesRefreshEvent, only if not already registered - if (!EventBus.getDefault().isRegistered(this)) { - EventBus.getDefault().register(this) - } - val optionalUser = user if (optionalUser.isPresent) { var file: OCFile? = file ?: throw IllegalStateException("Instanced with a NULL OCFile") @@ -418,10 +407,6 @@ class PreviewImageActivity : downloadFinishReceiver = null } - if (EventBus.getDefault().isRegistered(this)) { - EventBus.getDefault().unregister(this) - } - super.onStop() }