diff --git a/build.gradle b/build.gradle index b151c3e5..913c4040 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ buildscript { 'androidXTestEspresso': 'androidx.test.espresso:espresso-core:3.1.1', 'androidXTestEspressoContrib': 'androidx.test.espresso:espresso-contrib:3.1.1', - 'googleMaterial': 'com.google.android.material:material:1.0.0', + 'googleMaterial': 'com.google.android.material:material:1.3.0', 'rxJava': 'io.reactivex.rxjava3:rxjava:3.0.3', 'rxAndroid': 'io.reactivex.rxjava3:rxandroid:3.0.0', diff --git a/rxbinding-material/src/androidTest/AndroidManifest.xml b/rxbinding-material/src/androidTest/AndroidManifest.xml index 78ca0d47..dd2fcc6a 100644 --- a/rxbinding-material/src/androidTest/AndroidManifest.xml +++ b/rxbinding-material/src/androidTest/AndroidManifest.xml @@ -3,10 +3,12 @@ - - - + + + + diff --git a/rxbinding-material/src/androidTest/java/com/jakewharton/rxbinding4/material/RxSliderTest.kt b/rxbinding-material/src/androidTest/java/com/jakewharton/rxbinding4/material/RxSliderTest.kt new file mode 100644 index 00000000..37fb1b77 --- /dev/null +++ b/rxbinding-material/src/androidTest/java/com/jakewharton/rxbinding4/material/RxSliderTest.kt @@ -0,0 +1,127 @@ +package com.jakewharton.rxbinding4.material + +import android.util.Log +import android.view.MotionEvent +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.rule.ActivityTestRule +import com.google.android.material.slider.Slider +import com.jakewharton.rxbinding4.MotionEventUtil +import com.jakewharton.rxbinding4.RecordingObserver +import com.jakewharton.rxbinding4.widget.* +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import org.junit.Assert +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class RxSliderTest { + @Rule @JvmField + val activityRule = ActivityTestRule(RxSliderTestActivity::class.java) + private val instrumentation = InstrumentationRegistry.getInstrumentation() + lateinit var slider: Slider + + @Before + fun setUp() { + slider = activityRule.activity.slider + } + + @Test + fun changes() { + val o = RecordingObserver() + slider.changes() // + .subscribeOn(AndroidSchedulers.mainThread()) // + .subscribe(o) + assertEquals(0.0f, o.takeNext()) + instrumentation.sendPointerSync(MotionEventUtil.motionEventAtPosition(slider, MotionEvent.ACTION_DOWN, 0, 50)) + instrumentation.waitForIdleSync() + o.assertNoMoreEvents() + instrumentation.sendPointerSync(MotionEventUtil.motionEventAtPosition(slider, MotionEvent.ACTION_MOVE, 100, 50)) + instrumentation.waitForIdleSync() + assertEquals(1.0f, o.takeNext()) + instrumentation.sendPointerSync(MotionEventUtil.motionEventAtPosition(slider, MotionEvent.ACTION_MOVE, 0, 50)) + instrumentation.waitForIdleSync() + assertEquals(0.0f, o.takeNext()) + instrumentation.runOnMainSync { slider.value = 0.85f } + instrumentation.waitForIdleSync() + assertEquals(0.85f, o.takeNext()) + o.dispose() + instrumentation.sendPointerSync(MotionEventUtil.motionEventAtPosition(slider, MotionEvent.ACTION_MOVE, 100, 50)) + instrumentation.waitForIdleSync() + o.assertNoMoreEvents() + instrumentation.runOnMainSync { slider.value = 0.85f } + instrumentation.waitForIdleSync() + o.assertNoMoreEvents() + } + + @Test + fun systemChanges() { + val o = RecordingObserver() + slider.systemChanges() // + .subscribeOn(AndroidSchedulers.mainThread()) // + .subscribe(o) + assertEquals(0.0f, o.takeNext()) + instrumentation.sendPointerSync(MotionEventUtil.motionEventAtPosition(slider, MotionEvent.ACTION_MOVE, 100, 50)) + instrumentation.waitForIdleSync() + o.assertNoMoreEvents() + instrumentation.runOnMainSync { slider.value = 0.85f } + instrumentation.waitForIdleSync() + assertEquals(0.85f, o.takeNext()) + o.dispose() + instrumentation.sendPointerSync(MotionEventUtil.motionEventAtPosition(slider, MotionEvent.ACTION_MOVE, 100, 50)) + instrumentation.waitForIdleSync() + o.assertNoMoreEvents() + instrumentation.runOnMainSync { slider.value = 0.85f } + instrumentation.waitForIdleSync() + o.assertNoMoreEvents() + } + + @Test + fun userChanges() { + val o = RecordingObserver() + slider.userChanges() // + .subscribeOn(AndroidSchedulers.mainThread()) // + .subscribe(o) + assertEquals(0.0f, o.takeNext()) + instrumentation.sendPointerSync(MotionEventUtil.motionEventAtPosition(slider, MotionEvent.ACTION_DOWN, 0, 50)) + instrumentation.waitForIdleSync() + o.assertNoMoreEvents() + instrumentation.sendPointerSync(MotionEventUtil.motionEventAtPosition(slider, MotionEvent.ACTION_MOVE, 100, 50)) + instrumentation.waitForIdleSync() + assertEquals(1.0f, o.takeNext()) + instrumentation.sendPointerSync(MotionEventUtil.motionEventAtPosition(slider, MotionEvent.ACTION_MOVE, 0, 50)) + instrumentation.waitForIdleSync() + assertEquals(0.0f, o.takeNext()) + instrumentation.runOnMainSync { slider.value = 0.85f } + instrumentation.waitForIdleSync() + o.assertNoMoreEvents() + o.dispose() + instrumentation.sendPointerSync(MotionEventUtil.motionEventAtPosition(slider, MotionEvent.ACTION_MOVE, 100, 50)) + instrumentation.waitForIdleSync() + o.assertNoMoreEvents() + instrumentation.runOnMainSync { slider.value = 0.85f } + instrumentation.waitForIdleSync() + o.assertNoMoreEvents() + } + + @Test + fun touchEvents() { + val o = RecordingObserver() + slider.touchEvents() // + .subscribeOn(AndroidSchedulers.mainThread()) // + .subscribe(o) + instrumentation.sendPointerSync(MotionEventUtil.motionEventAtPosition(slider, MotionEvent.ACTION_DOWN, 0, 50)) + instrumentation.waitForIdleSync() + assertEquals(SliderStartTrackingTouchEvent(slider), o.takeNext()) + instrumentation.sendPointerSync(MotionEventUtil.motionEventAtPosition(slider, MotionEvent.ACTION_MOVE, 100, 50)) + instrumentation.waitForIdleSync() + instrumentation.sendPointerSync(MotionEventUtil.motionEventAtPosition(slider, MotionEvent.ACTION_UP, 100, 50)) + instrumentation.waitForIdleSync() + assertEquals(SliderStopTrackingTouchEvent(slider), o.takeNext()) + instrumentation.runOnMainSync { slider.value = 0f } + o.dispose() + instrumentation.sendPointerSync(MotionEventUtil.motionEventAtPosition(slider, MotionEvent.ACTION_DOWN, 0, 50)) + instrumentation.waitForIdleSync() + o.assertNoMoreEvents() + } +} diff --git a/rxbinding-material/src/androidTest/java/com/jakewharton/rxbinding4/material/RxSliderTestActivity.kt b/rxbinding-material/src/androidTest/java/com/jakewharton/rxbinding4/material/RxSliderTestActivity.kt new file mode 100644 index 00000000..974b8391 --- /dev/null +++ b/rxbinding-material/src/androidTest/java/com/jakewharton/rxbinding4/material/RxSliderTestActivity.kt @@ -0,0 +1,16 @@ +package com.jakewharton.rxbinding4.material + +import android.app.Activity +import android.os.Bundle +import com.google.android.material.slider.Slider + +class RxSliderTestActivity : Activity() { + lateinit var slider: Slider + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + slider = Slider(this) + setContentView(slider) + } +} \ No newline at end of file diff --git a/rxbinding-material/src/main/java/com/jakewharton/rxbinding4/material/SliderChangeObservable.kt b/rxbinding-material/src/main/java/com/jakewharton/rxbinding4/material/SliderChangeObservable.kt new file mode 100644 index 00000000..13edc1db --- /dev/null +++ b/rxbinding-material/src/main/java/com/jakewharton/rxbinding4/material/SliderChangeObservable.kt @@ -0,0 +1,87 @@ +@file:JvmName("RxSlider") +@file:JvmMultifileClass + +package com.jakewharton.rxbinding4.material + +import androidx.annotation.CheckResult +import com.google.android.material.slider.Slider +import com.jakewharton.rxbinding4.InitialValueObservable +import com.jakewharton.rxbinding4.internal.checkMainThread +import io.reactivex.rxjava3.android.MainThreadDisposable +import io.reactivex.rxjava3.core.Observer + + +/** + * Create an observable of progress value changes on `view`. + * + * *Warning:* The created observable keeps a strong reference to `view`. Unsubscribe + * to free this reference. + * + * *Note:* A value will be emitted immediately on subscribe. + */ +@CheckResult +fun Slider.changes(): InitialValueObservable { + return SliderChangeObservable(this, null) +} + +/** + * Create an observable of progress value changes on `view` that were made only from the + * user. + * + * *Warning:* The created observable keeps a strong reference to `view`. Unsubscribe + * to free this reference. + * + * *Note:* A value will be emitted immediately on subscribe. + */ +@CheckResult +fun Slider.userChanges(): InitialValueObservable { + return SliderChangeObservable(this, true) +} + +/** + * Create an observable of progress value changes on `view` that were made only from the + * system. + * + * *Warning:* The created observable keeps a strong reference to `view`. Unsubscribe + * to free this reference. + * + * *Note:* A value will be emitted immediately on subscribe. + */ +@CheckResult +fun Slider.systemChanges(): InitialValueObservable { + return SliderChangeObservable(this, false) +} + +private class SliderChangeObservable( + private val view: Slider, + private val shouldBeFromUser: Boolean? +) : InitialValueObservable() { + + override fun subscribeListener(observer: Observer) { + if (!checkMainThread(observer)) { + return + } + val listener = Listener(view, shouldBeFromUser, observer) + view.addOnChangeListener(listener) + observer.onSubscribe(listener) + } + + override val initialValue get() = view.value + + private class Listener( + private val view: Slider, + private val shouldBeFromUser: Boolean?, + private val observer: Observer + ) : MainThreadDisposable(), Slider.OnChangeListener { + + override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) { + if (!isDisposed && (shouldBeFromUser == null || shouldBeFromUser == fromUser)) { + observer.onNext(value) + } + } + + override fun onDispose() { + view.clearOnChangeListeners() + } + } +} \ No newline at end of file diff --git a/rxbinding-material/src/main/java/com/jakewharton/rxbinding4/material/SliderTouchEvent.kt b/rxbinding-material/src/main/java/com/jakewharton/rxbinding4/material/SliderTouchEvent.kt new file mode 100644 index 00000000..f8128bbf --- /dev/null +++ b/rxbinding-material/src/main/java/com/jakewharton/rxbinding4/material/SliderTouchEvent.kt @@ -0,0 +1,15 @@ +package com.jakewharton.rxbinding4.material + +import com.google.android.material.slider.Slider + +sealed class SliderTouchEvent { + abstract val view: Slider +} + +data class SliderStartTrackingTouchEvent( + override val view: Slider +) : SliderTouchEvent() + +data class SliderStopTrackingTouchEvent( + override val view: Slider +) : SliderTouchEvent() \ No newline at end of file diff --git a/rxbinding-material/src/main/java/com/jakewharton/rxbinding4/material/SliderTouchEventsObservable.kt b/rxbinding-material/src/main/java/com/jakewharton/rxbinding4/material/SliderTouchEventsObservable.kt new file mode 100644 index 00000000..48d30a5d --- /dev/null +++ b/rxbinding-material/src/main/java/com/jakewharton/rxbinding4/material/SliderTouchEventsObservable.kt @@ -0,0 +1,51 @@ +package com.jakewharton.rxbinding4.material + +import androidx.annotation.CheckResult +import com.google.android.material.slider.Slider +import com.jakewharton.rxbinding4.internal.checkMainThread +import io.reactivex.rxjava3.android.MainThreadDisposable +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Observer + +/** + * Create an observable of touch events for `view`. + * + * *Warning:* The created observable keeps a strong reference to `view`. Unsubscribe + * to free this reference. + */ +@CheckResult +fun Slider.touchEvents(): Observable { + return SliderTouchEventsObservable(this) +} + +private class SliderTouchEventsObservable( + private val view: Slider +) : Observable() { + + override fun subscribeActual(observer: Observer) { + if (!checkMainThread(observer)) { + return + } + val listener = Listener(view, observer) + observer.onSubscribe(listener) + view.addOnSliderTouchListener(listener) + } + + private class Listener( + private val view: Slider, + private val observer: Observer + ) : MainThreadDisposable(), Slider.OnSliderTouchListener { + + override fun onStartTrackingTouch(slider: Slider) { + observer.onNext(SliderStartTrackingTouchEvent(view)) + } + + override fun onStopTrackingTouch(slider: Slider) { + observer.onNext(SliderStopTrackingTouchEvent(view)) + } + + override fun onDispose() { + view.clearOnSliderTouchListeners() + } + } +} \ No newline at end of file