mirror of
https://github.com/nextcloud/android.git
synced 2024-11-22 05:05:31 +03:00
Merge pull request #9435 from nextcloud/fix/multi-tap-overflow
OcFileListFragment: throttle overflow menu clicks
This commit is contained in:
commit
3b947b981b
4 changed files with 158 additions and 13 deletions
|
@ -61,6 +61,7 @@ import com.owncloud.android.ui.activities.data.activities.RemoteActivitiesReposi
|
|||
import com.owncloud.android.ui.activities.data.files.FilesRepository;
|
||||
import com.owncloud.android.ui.activities.data.files.FilesServiceApiImpl;
|
||||
import com.owncloud.android.ui.activities.data.files.RemoteFilesRepository;
|
||||
import com.nextcloud.client.utils.Throttler;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
|
@ -231,4 +232,9 @@ class AppModule {
|
|||
LocalBroadcastManager localBroadcastManager(Context context) {
|
||||
return LocalBroadcastManager.getInstance(context);
|
||||
}
|
||||
|
||||
@Provides
|
||||
Throttler throttler(Clock clock) {
|
||||
return new Throttler(clock);
|
||||
}
|
||||
}
|
||||
|
|
48
src/main/java/com/nextcloud/client/utils/Throttler.kt
Normal file
48
src/main/java/com/nextcloud/client/utils/Throttler.kt
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Nextcloud Android client application
|
||||
*
|
||||
* @author Álvaro Brey Vilas
|
||||
* Copyright (C) 2021 Álvaro Brey Vilas
|
||||
* Copyright (C) 2021 Nextcloud GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.nextcloud.client.utils
|
||||
|
||||
import com.nextcloud.client.core.Clock
|
||||
|
||||
/**
|
||||
* Simple throttler that just discards new calls until interval has passed.
|
||||
*
|
||||
* @param clock the Clock to provide timestamps
|
||||
*/
|
||||
class Throttler(private val clock: Clock) {
|
||||
|
||||
/**
|
||||
* The interval, in milliseconds, between accepted calls
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
var intervalMillis = 150L
|
||||
private val timestamps: MutableMap<String, Long> = mutableMapOf()
|
||||
|
||||
@Synchronized
|
||||
fun run(key: String, runnable: Runnable) {
|
||||
val time = clock.currentTime
|
||||
val lastCallTimestamp = timestamps[key] ?: 0
|
||||
if (time - lastCallTimestamp > intervalMillis) {
|
||||
runnable.run()
|
||||
timestamps[key] = time
|
||||
}
|
||||
}
|
||||
}
|
|
@ -101,6 +101,7 @@ import com.owncloud.android.utils.EncryptionUtils;
|
|||
import com.owncloud.android.utils.FileSortOrder;
|
||||
import com.owncloud.android.utils.FileStorageUtils;
|
||||
import com.owncloud.android.utils.MimeTypeUtil;
|
||||
import com.nextcloud.client.utils.Throttler;
|
||||
import com.owncloud.android.utils.theme.ThemeColorUtils;
|
||||
import com.owncloud.android.utils.theme.ThemeFabUtils;
|
||||
import com.owncloud.android.utils.theme.ThemeToolbarUtils;
|
||||
|
@ -180,6 +181,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
|||
@Inject AppPreferences preferences;
|
||||
@Inject UserAccountManager accountManager;
|
||||
@Inject ClientFactory clientFactory;
|
||||
@Inject Throttler throttler;
|
||||
protected FileFragment.ContainerActivity mContainerActivity;
|
||||
|
||||
protected OCFile mFile;
|
||||
|
@ -199,6 +201,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
|||
protected String mLimitToMimeType;
|
||||
private FloatingActionButton mFabMain;
|
||||
|
||||
|
||||
@Inject DeviceInfo deviceInfo;
|
||||
|
||||
protected enum MenuItemAddRemove {
|
||||
|
@ -518,20 +521,22 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
|||
|
||||
@Override
|
||||
public void onOverflowIconClicked(OCFile file, View view) {
|
||||
PopupMenu popup = new PopupMenu(getActivity(), view);
|
||||
popup.inflate(R.menu.item_file);
|
||||
FileMenuFilter mf = new FileMenuFilter(mAdapter.getFiles().size(),
|
||||
Collections.singleton(file),
|
||||
mContainerActivity, getActivity(),
|
||||
true,
|
||||
accountManager.getUser());
|
||||
mf.filter(popup.getMenu(), true);
|
||||
popup.setOnMenuItemClickListener(item -> {
|
||||
Set<OCFile> checkedFiles = new HashSet<>();
|
||||
checkedFiles.add(file);
|
||||
return onFileActionChosen(item, checkedFiles);
|
||||
throttler.run("overflowClick", () -> {
|
||||
PopupMenu popup = new PopupMenu(getActivity(), view);
|
||||
popup.inflate(R.menu.item_file);
|
||||
FileMenuFilter mf = new FileMenuFilter(mAdapter.getFiles().size(),
|
||||
Collections.singleton(file),
|
||||
mContainerActivity, getActivity(),
|
||||
true,
|
||||
accountManager.getUser());
|
||||
mf.filter(popup.getMenu(), true);
|
||||
popup.setOnMenuItemClickListener(item -> {
|
||||
Set<OCFile> checkedFiles = new HashSet<>();
|
||||
checkedFiles.add(file);
|
||||
return onFileActionChosen(item, checkedFiles);
|
||||
});
|
||||
popup.show();
|
||||
});
|
||||
popup.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
86
src/test/java/com/nextcloud/client/utils/ThrottlerTest.kt
Normal file
86
src/test/java/com/nextcloud/client/utils/ThrottlerTest.kt
Normal file
|
@ -0,0 +1,86 @@
|
|||
package com.nextcloud.client.utils
|
||||
|
||||
import com.nextcloud.client.core.Clock
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.Runs
|
||||
import io.mockk.every
|
||||
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import io.mockk.just
|
||||
import io.mockk.verify
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class ThrottlerTest {
|
||||
companion object {
|
||||
private const val KEY = "TEST"
|
||||
}
|
||||
|
||||
@MockK
|
||||
lateinit var runnable: Runnable
|
||||
|
||||
@MockK
|
||||
lateinit var clock: Clock
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockKAnnotations.init(this, relaxed = true)
|
||||
every { runnable.run() } just Runs
|
||||
}
|
||||
|
||||
private fun runWithThrottler(throttler: Throttler) {
|
||||
throttler.run(KEY, runnable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun unchangingTime_multipleCalls_calledExactlyOnce() {
|
||||
// given
|
||||
every { clock.currentTime } returns 300
|
||||
|
||||
val sut = Throttler(clock).apply {
|
||||
intervalMillis = 150
|
||||
}
|
||||
|
||||
// when
|
||||
repeat(10) {
|
||||
runWithThrottler(sut)
|
||||
}
|
||||
|
||||
// then
|
||||
verify(exactly = 1) { runnable.run() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun spacedCalls_noThrottle() {
|
||||
// given
|
||||
val sut = Throttler(clock).apply {
|
||||
intervalMillis = 150
|
||||
}
|
||||
every { clock.currentTime } returnsMany listOf(200, 400, 600, 800)
|
||||
|
||||
// when
|
||||
repeat(4) {
|
||||
runWithThrottler(sut)
|
||||
}
|
||||
|
||||
// then
|
||||
verify(exactly = 4) { runnable.run() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mixedIntervals_sometimesThrottled() {
|
||||
// given
|
||||
val sut = Throttler(clock).apply {
|
||||
intervalMillis = 150
|
||||
}
|
||||
every { clock.currentTime } returnsMany listOf(200, 300, 400, 500)
|
||||
|
||||
// when
|
||||
repeat(4) {
|
||||
runWithThrottler(sut)
|
||||
}
|
||||
|
||||
// then
|
||||
verify(exactly = 2) { runnable.run() }
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue