diff --git a/vector/src/main/java/im/vector/app/core/flow/TimingOperators.kt b/vector/src/main/java/im/vector/app/core/flow/TimingOperators.kt index edd77f6935..621a80d96e 100644 --- a/vector/src/main/java/im/vector/app/core/flow/TimingOperators.kt +++ b/vector/src/main/java/im/vector/app/core/flow/TimingOperators.kt @@ -67,6 +67,24 @@ fun Flow.chunk(durationInMillis: Long): Flow> { } } +@ExperimentalCoroutinesApi +fun Flow.throttleFirst(windowDuration: Long): Flow = flow { + var windowStartTime = System.currentTimeMillis() + var emitted = false + collect { value -> + val currentTime = System.currentTimeMillis() + val delta = currentTime - windowStartTime + if (delta >= windowDuration) { + windowStartTime += delta / windowDuration * windowDuration + emitted = false + } + if (!emitted) { + emit(value) + emitted = true + } + } +} + fun tickerFlow(scope: CoroutineScope, delayMillis: Long, initialDelayMillis: Long = delayMillis): Flow { return scope.fixedPeriodTicker(delayMillis, initialDelayMillis).consumeAsFlow() } diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index 4f51317e2f..7fe939bef3 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -59,6 +59,7 @@ import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.restart import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.extensions.singletonEntryPoint +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.utils.toast import im.vector.app.features.MainActivity import im.vector.app.features.MainActivityArgs @@ -79,7 +80,6 @@ import im.vector.app.features.themes.ThemeUtils import im.vector.app.receivers.DebugReceiver import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.GlobalError import reactivecircus.flowbinding.android.view.clicks @@ -118,7 +118,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver protected fun View.debouncedClicks(onClicked: () -> Unit) { clicks() - .sample(300) + .throttleFirst(300) .onEach { onClicked() } .launchIn(lifecycleScope) } diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt index 1304c31177..20697c6d11 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt @@ -35,10 +35,10 @@ import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import dagger.hilt.android.EntryPointAccessors import im.vector.app.core.di.ActivityEntryPoint +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.utils.DimensionConverter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.sample import reactivecircus.flowbinding.android.view.clicks import timber.log.Timber @@ -168,7 +168,7 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomShe protected fun View.debouncedClicks(onClicked: () -> Unit) { clicks() - .sample(300) + .throttleFirst(300) .onEach { onClicked() } .launchIn(viewLifecycleOwner.lifecycleScope) } diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt index 6f0a944b32..f4e1fe84e1 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt @@ -42,11 +42,11 @@ import im.vector.app.core.dialogs.UnrecognizedCertificateDialog import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.extensions.toMvRxBundle +import im.vector.app.core.flow.throttleFirst import im.vector.app.features.navigation.Navigator import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.sample import reactivecircus.flowbinding.android.view.clicks import timber.log.Timber @@ -239,7 +239,7 @@ abstract class VectorBaseFragment : Fragment(), MavericksView protected fun View.debouncedClicks(onClicked: () -> Unit) { clicks() - .sample(300) + .throttleFirst(300) .onEach { onClicked() } .launchIn(viewLifecycleOwner.lifecycleScope) } diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt index edeb2f875d..c49291d6a2 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt @@ -26,12 +26,12 @@ import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.startImportTextFromFileIntent import im.vector.app.databinding.FragmentSsssAccessFromKeyBinding import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.extensions.tryOrNull import reactivecircus.flowbinding.android.widget.editorActionEvents import reactivecircus.flowbinding.android.widget.textChanges @@ -50,7 +50,7 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment views.ssssRestoreWithKeyText.text = getString(R.string.enter_secret_storage_input_key) views.ssssKeyEnterEdittext.editorActionEvents() - .sample(300) + .throttleFirst(300) .onEach { if (it.actionId == EditorInfo.IME_ACTION_DONE) { submit() diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt index ee7a1ec305..c93e562d77 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt @@ -25,12 +25,12 @@ import androidx.core.text.toSpannable import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel import im.vector.app.R +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider import im.vector.app.databinding.FragmentSsssAccessFromPassphraseBinding import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.sample import reactivecircus.flowbinding.android.widget.editorActionEvents import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject @@ -62,7 +62,7 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor( // .colorizeMatchingText(key, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) views.ssssPassphraseEnterEdittext.editorActionEvents() - .sample(300) + .throttleFirst(300) .onEach { if (it.actionId == EditorInfo.IME_ACTION_DONE) { submit() diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt index a3580db996..940a4d9af3 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt @@ -27,11 +27,11 @@ import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.sample import reactivecircus.flowbinding.android.widget.editorActionEvents import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject @@ -60,7 +60,7 @@ class BootstrapConfirmPassphraseFragment @Inject constructor() : } views.ssssPassphraseEnterEdittext.editorActionEvents() - .sample(300) + .throttleFirst(300) .onEach { if (it.actionId == EditorInfo.IME_ACTION_DONE) { submit() diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt index 6fbd003017..77fb5ab3a6 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt @@ -25,12 +25,12 @@ import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import im.vector.app.R +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding import im.vector.app.features.settings.VectorLocale import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.sample import reactivecircus.flowbinding.android.widget.editorActionEvents import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject @@ -55,7 +55,7 @@ class BootstrapEnterPassphraseFragment @Inject constructor() : views.ssssPassphraseEnterEdittext.setText(it.passphrase ?: "") } views.ssssPassphraseEnterEdittext.editorActionEvents() - .sample(300) + .throttleFirst(300) .onEach { if (it.actionId == EditorInfo.IME_ACTION_DONE) { submit() diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt index 5cc4cfd0c2..5d0f3bbeae 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt @@ -33,6 +33,7 @@ import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.registerStartForActivityResult +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider import im.vector.app.core.utils.colorizeMatchingText @@ -40,7 +41,6 @@ import im.vector.app.core.utils.startImportTextFromFileIntent import im.vector.app.databinding.FragmentBootstrapMigrateBackupBinding import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.internal.crypto.keysbackup.util.isValidRecoveryKey import reactivecircus.flowbinding.android.widget.editorActionEvents @@ -65,7 +65,7 @@ class BootstrapMigrateBackupFragment @Inject constructor( views.bootstrapMigrateEditText.setText(it.passphrase ?: "") } views.bootstrapMigrateEditText.editorActionEvents() - .sample(300) + .throttleFirst(300) .onEach { if (it.actionId == EditorInfo.IME_ACTION_DONE) { submit() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 1cd1221ac0..e855852f10 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -182,10 +182,10 @@ import im.vector.app.features.widgets.WidgetActivity import im.vector.app.features.widgets.WidgetArgs import im.vector.app.features.widgets.WidgetKind import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import nl.dionsegijn.konfetti.models.Shape @@ -1350,7 +1350,7 @@ class RoomDetailFragment @Inject constructor( private fun observerUserTyping() { views.composerLayout.views.composerEditText.textChanges() .skipInitialValue() - .sample(300) + .debounce(300) .map { it.isNotEmpty() } .onEach { Timber.d("Typing: User is typing: $it") diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt index 8d3d623525..4d0d301721 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt @@ -32,6 +32,7 @@ import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentSpacePreviewBinding import im.vector.app.features.home.AvatarRenderer @@ -39,7 +40,6 @@ import im.vector.app.features.spaces.SpacePreviewSharedAction import im.vector.app.features.spaces.SpacePreviewSharedActionViewModel import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.sample import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.util.MatrixItem import reactivecircus.flowbinding.appcompat.navigationClicks @@ -76,7 +76,7 @@ class SpacePreviewFragment @Inject constructor( views.roomPreviewNoPreviewToolbar .navigationClicks() - .sample(300) + .throttleFirst(300) .onEach { sharedActionViewModel.post(SpacePreviewSharedAction.DismissAction) } .launchIn(viewLifecycleOwner.lifecycleScope)