Flow migration: use throttleFirst instead of sample on UI

This commit is contained in:
ganfra 2021-10-28 17:36:29 +02:00
parent 79c5af2585
commit edf068ee57
11 changed files with 38 additions and 20 deletions

View File

@ -67,6 +67,24 @@ fun <T> Flow<T>.chunk(durationInMillis: Long): Flow<List<T>> {
} }
} }
@ExperimentalCoroutinesApi
fun <T> Flow<T>.throttleFirst(windowDuration: Long): Flow<T> = 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<Unit> { fun tickerFlow(scope: CoroutineScope, delayMillis: Long, initialDelayMillis: Long = delayMillis): Flow<Unit> {
return scope.fixedPeriodTicker(delayMillis, initialDelayMillis).consumeAsFlow() return scope.fixedPeriodTicker(delayMillis, initialDelayMillis).consumeAsFlow()
} }

View File

@ -59,6 +59,7 @@ import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.restart import im.vector.app.core.extensions.restart
import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.extensions.singletonEntryPoint 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.core.utils.toast
import im.vector.app.features.MainActivity import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs import im.vector.app.features.MainActivityArgs
@ -79,7 +80,6 @@ import im.vector.app.features.themes.ThemeUtils
import im.vector.app.receivers.DebugReceiver import im.vector.app.receivers.DebugReceiver
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.failure.GlobalError
import reactivecircus.flowbinding.android.view.clicks import reactivecircus.flowbinding.android.view.clicks
@ -118,7 +118,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
protected fun View.debouncedClicks(onClicked: () -> Unit) { protected fun View.debouncedClicks(onClicked: () -> Unit) {
clicks() clicks()
.sample(300) .throttleFirst(300)
.onEach { onClicked() } .onEach { onClicked() }
.launchIn(lifecycleScope) .launchIn(lifecycleScope)
} }

View File

@ -35,10 +35,10 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import dagger.hilt.android.EntryPointAccessors import dagger.hilt.android.EntryPointAccessors
import im.vector.app.core.di.ActivityEntryPoint import im.vector.app.core.di.ActivityEntryPoint
import im.vector.app.core.flow.throttleFirst
import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.DimensionConverter
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample
import reactivecircus.flowbinding.android.view.clicks import reactivecircus.flowbinding.android.view.clicks
import timber.log.Timber import timber.log.Timber
@ -168,7 +168,7 @@ abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomShe
protected fun View.debouncedClicks(onClicked: () -> Unit) { protected fun View.debouncedClicks(onClicked: () -> Unit) {
clicks() clicks()
.sample(300) .throttleFirst(300)
.onEach { onClicked() } .onEach { onClicked() }
.launchIn(viewLifecycleOwner.lifecycleScope) .launchIn(viewLifecycleOwner.lifecycleScope)
} }

View File

@ -42,11 +42,11 @@ import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.core.extensions.toMvRxBundle import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.flow.throttleFirst
import im.vector.app.features.navigation.Navigator import im.vector.app.features.navigation.Navigator
import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample
import reactivecircus.flowbinding.android.view.clicks import reactivecircus.flowbinding.android.view.clicks
import timber.log.Timber import timber.log.Timber
@ -239,7 +239,7 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
protected fun View.debouncedClicks(onClicked: () -> Unit) { protected fun View.debouncedClicks(onClicked: () -> Unit) {
clicks() clicks()
.sample(300) .throttleFirst(300)
.onEach { onClicked() } .onEach { onClicked() }
.launchIn(viewLifecycleOwner.lifecycleScope) .launchIn(viewLifecycleOwner.lifecycleScope)
} }

View File

@ -26,12 +26,12 @@ import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.registerStartForActivityResult 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.platform.VectorBaseFragment
import im.vector.app.core.utils.startImportTextFromFileIntent import im.vector.app.core.utils.startImportTextFromFileIntent
import im.vector.app.databinding.FragmentSsssAccessFromKeyBinding import im.vector.app.databinding.FragmentSsssAccessFromKeyBinding
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import reactivecircus.flowbinding.android.widget.editorActionEvents import reactivecircus.flowbinding.android.widget.editorActionEvents
import reactivecircus.flowbinding.android.widget.textChanges 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.ssssRestoreWithKeyText.text = getString(R.string.enter_secret_storage_input_key)
views.ssssKeyEnterEdittext.editorActionEvents() views.ssssKeyEnterEdittext.editorActionEvents()
.sample(300) .throttleFirst(300)
.onEach { .onEach {
if (it.actionId == EditorInfo.IME_ACTION_DONE) { if (it.actionId == EditorInfo.IME_ACTION_DONE) {
submit() submit()

View File

@ -25,12 +25,12 @@ import androidx.core.text.toSpannable
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.flow.throttleFirst
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
import im.vector.app.databinding.FragmentSsssAccessFromPassphraseBinding import im.vector.app.databinding.FragmentSsssAccessFromPassphraseBinding
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample
import reactivecircus.flowbinding.android.widget.editorActionEvents import reactivecircus.flowbinding.android.widget.editorActionEvents
import reactivecircus.flowbinding.android.widget.textChanges import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
@ -62,7 +62,7 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
// .colorizeMatchingText(key, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) // .colorizeMatchingText(key, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
views.ssssPassphraseEnterEdittext.editorActionEvents() views.ssssPassphraseEnterEdittext.editorActionEvents()
.sample(300) .throttleFirst(300)
.onEach { .onEach {
if (it.actionId == EditorInfo.IME_ACTION_DONE) { if (it.actionId == EditorInfo.IME_ACTION_DONE) {
submit() submit()

View File

@ -27,11 +27,11 @@ import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard 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.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample
import reactivecircus.flowbinding.android.widget.editorActionEvents import reactivecircus.flowbinding.android.widget.editorActionEvents
import reactivecircus.flowbinding.android.widget.textChanges import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
@ -60,7 +60,7 @@ class BootstrapConfirmPassphraseFragment @Inject constructor() :
} }
views.ssssPassphraseEnterEdittext.editorActionEvents() views.ssssPassphraseEnterEdittext.editorActionEvents()
.sample(300) .throttleFirst(300)
.onEach { .onEach {
if (it.actionId == EditorInfo.IME_ACTION_DONE) { if (it.actionId == EditorInfo.IME_ACTION_DONE) {
submit() submit()

View File

@ -25,12 +25,12 @@ import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.flow.throttleFirst
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding
import im.vector.app.features.settings.VectorLocale import im.vector.app.features.settings.VectorLocale
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample
import reactivecircus.flowbinding.android.widget.editorActionEvents import reactivecircus.flowbinding.android.widget.editorActionEvents
import reactivecircus.flowbinding.android.widget.textChanges import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
@ -55,7 +55,7 @@ class BootstrapEnterPassphraseFragment @Inject constructor() :
views.ssssPassphraseEnterEdittext.setText(it.passphrase ?: "") views.ssssPassphraseEnterEdittext.setText(it.passphrase ?: "")
} }
views.ssssPassphraseEnterEdittext.editorActionEvents() views.ssssPassphraseEnterEdittext.editorActionEvents()
.sample(300) .throttleFirst(300)
.onEach { .onEach {
if (it.actionId == EditorInfo.IME_ACTION_DONE) { if (it.actionId == EditorInfo.IME_ACTION_DONE) {
submit() submit()

View File

@ -33,6 +33,7 @@ import com.airbnb.mvrx.withState
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.registerStartForActivityResult 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.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.colorizeMatchingText 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 im.vector.app.databinding.FragmentBootstrapMigrateBackupBinding
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.internal.crypto.keysbackup.util.isValidRecoveryKey import org.matrix.android.sdk.internal.crypto.keysbackup.util.isValidRecoveryKey
import reactivecircus.flowbinding.android.widget.editorActionEvents import reactivecircus.flowbinding.android.widget.editorActionEvents
@ -65,7 +65,7 @@ class BootstrapMigrateBackupFragment @Inject constructor(
views.bootstrapMigrateEditText.setText(it.passphrase ?: "") views.bootstrapMigrateEditText.setText(it.passphrase ?: "")
} }
views.bootstrapMigrateEditText.editorActionEvents() views.bootstrapMigrateEditText.editorActionEvents()
.sample(300) .throttleFirst(300)
.onEach { .onEach {
if (it.actionId == EditorInfo.IME_ACTION_DONE) { if (it.actionId == EditorInfo.IME_ACTION_DONE) {
submit() submit()

View File

@ -182,10 +182,10 @@ import im.vector.app.features.widgets.WidgetActivity
import im.vector.app.features.widgets.WidgetArgs import im.vector.app.features.widgets.WidgetArgs
import im.vector.app.features.widgets.WidgetKind import im.vector.app.features.widgets.WidgetKind
import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import nl.dionsegijn.konfetti.models.Shape import nl.dionsegijn.konfetti.models.Shape
@ -1350,7 +1350,7 @@ class RoomDetailFragment @Inject constructor(
private fun observerUserTyping() { private fun observerUserTyping() {
views.composerLayout.views.composerEditText.textChanges() views.composerLayout.views.composerEditText.textChanges()
.skipInitialValue() .skipInitialValue()
.sample(300) .debounce(300)
.map { it.isNotEmpty() } .map { it.isNotEmpty() }
.onEach { .onEach {
Timber.d("Typing: User is typing: $it") Timber.d("Typing: User is typing: $it")

View File

@ -32,6 +32,7 @@ import com.airbnb.mvrx.withState
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith 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.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentSpacePreviewBinding import im.vector.app.databinding.FragmentSpacePreviewBinding
import im.vector.app.features.home.AvatarRenderer 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 im.vector.app.features.spaces.SpacePreviewSharedActionViewModel
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
import reactivecircus.flowbinding.appcompat.navigationClicks import reactivecircus.flowbinding.appcompat.navigationClicks
@ -76,7 +76,7 @@ class SpacePreviewFragment @Inject constructor(
views.roomPreviewNoPreviewToolbar views.roomPreviewNoPreviewToolbar
.navigationClicks() .navigationClicks()
.sample(300) .throttleFirst(300)
.onEach { sharedActionViewModel.post(SpacePreviewSharedAction.DismissAction) } .onEach { sharedActionViewModel.post(SpacePreviewSharedAction.DismissAction) }
.launchIn(viewLifecycleOwner.lifecycleScope) .launchIn(viewLifecycleOwner.lifecycleScope)