diff --git a/.travis.yml b/.travis.yml index 5c1404d..756dac2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,8 +15,8 @@ before_install: - echo "d56f5187479451eabf01fb78af6dfcb131a6481e" > "$ANDROID_HOME/licenses/android-sdk-license" - sdkmanager tools - - sdkmanager "system-images;android-18;default;armeabi-v7a" - - echo no | avdmanager create avd --force -n test -k "system-images;android-18;default;armeabi-v7a" + - sdkmanager "system-images;android-22;default;armeabi-v7a" + - echo no | avdmanager create avd --force -n test -k "system-images;android-22;default;armeabi-v7a" - $ANDROID_HOME/emulator/emulator -avd test -no-audio -no-window & before_script: diff --git a/build.gradle b/build.gradle index c490715..b605e58 100644 --- a/build.gradle +++ b/build.gradle @@ -1,23 +1,22 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.2.71' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' - classpath 'com.novoda:bintray-release:0.7.0' - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + classpath 'com.android.tools.build:gradle:3.1.4' + classpath 'com.novoda:bintray-release:0.8.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } allprojects { ext { - compileSdkVersion = 26 - buildToolVersion = '26.0.2' + compileSdkVersion = 28 + buildToolVersion = '28.0.3' minSdkVersion = 14 //publish @@ -28,6 +27,7 @@ allprojects { reactiveAndroidSupportV4 = 'reactiveandroid-support-v4' reactiveAndroidUI = 'reactiveandroid-ui' reactiveAndroidDesign = 'reactiveandroid-design' + reactiveAndroidXAppcompat = 'reactiveandroidx-appcompat' autoPublish = true desc = 'Reactive events and properties with RxJava for Android' @@ -37,7 +37,7 @@ allprojects { website = 'https://github.com/kittinunf/ReactiveAndroid' //kotlin - kotlinVersion = '1.1.60' + kotlinVersion = '1.2.71' kotlinGradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" kotlinStdLib = "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" kotlinAndroidExtensions = "org.jetbrains.kotlin:kotlin-android-extensions:$kotlinVersion" @@ -52,9 +52,15 @@ allprojects { androidSupportGridLayout = "com.android.support:gridlayout-v7:$supportVersion" androidSupportDesign = "com.android.support:design:$supportVersion" + //androidx + androidxVersion = '1.0.0' + androidxAppCompat = "androidx.appcompat:appcompat:$androidxVersion" + androidxRecyclerView = "androidx.recyclerview:recyclerview:$androidxVersion" + androidxCardView = "androidx.cardview:cardview:$androidxVersion" + //rxjava2 - reactivexRxJava2 = 'io.reactivex.rxjava2:rxjava:2.1.0' - reactivexRxAndroid2 = 'io.reactivex.rxjava2:rxandroid:2.0.1' + reactivexRxJava2 = 'io.reactivex.rxjava2:rxjava:2.2.2' + reactivexRxAndroid2 = 'io.reactivex.rxjava2:rxandroid:2.1.0' //test junit = 'junit:junit:4.12' diff --git a/deploy_bintray.sh b/deploy_bintray.sh index 9aa4679..699b617 100644 --- a/deploy_bintray.sh +++ b/deploy_bintray.sh @@ -4,7 +4,7 @@ if [[ "$TRAVIS_BRANCH" == */release-v* ]]; then echo "We're on release branch, deploying at $TRAVIS_BRANCH" - modules=("reactiveandroid" "reactiveandroid-design" "reactiveandroid-ui" "reactiveandroid-support-v4" "reactiveandroid-appcompat-v7") + modules=("reactiveandroid" "reactiveandroid-design" "reactiveandroid-ui" "reactiveandroid-support-v4" "reactiveandroid-appcompat-v7" "reactiveandroidx-appcompat") for i in "${modules[@]}" do echo ">> Deploying $i ..." diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f16d266..933b647 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-bin.zip diff --git a/reactiveandroid-appcompat-v7/build.gradle b/reactiveandroid-appcompat-v7/build.gradle index 6ca58e8..3b1cff0 100644 --- a/reactiveandroid-appcompat-v7/build.gradle +++ b/reactiveandroid-appcompat-v7/build.gradle @@ -43,20 +43,20 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) + api fileTree(dir: 'libs', include: ['*.jar']) - compile project(':reactiveandroid-ui') + api project(':reactiveandroid-ui') - compile parent.ext.androidAppCompatV7 - compile parent.ext.androidSupportGridLayout - compile parent.ext.androidSupportRecyclerView - compile parent.ext.androidSupportCardView + api parent.ext.androidAppCompatV7 + api parent.ext.androidSupportGridLayout + api parent.ext.androidSupportRecyclerView + api parent.ext.androidSupportCardView - androidTestCompile parent.ext.supportTestEspressoCore - androidTestCompile parent.ext.supportTestEspressoContrib - androidTestCompile parent.ext.supportTestRunner - androidTestCompile parent.ext.supportTestRules - androidTestCompile parent.ext.awaitility + androidTestImplementation parent.ext.supportTestEspressoCore + androidTestImplementation parent.ext.supportTestEspressoContrib + androidTestImplementation parent.ext.supportTestRunner + androidTestImplementation parent.ext.supportTestRules + androidTestImplementation parent.ext.awaitility } buildscript { @@ -82,4 +82,7 @@ publish { tasks.withType(Javadoc) { enabled = false +} +repositories { + mavenCentral() } \ No newline at end of file diff --git a/reactiveandroid-design/build.gradle b/reactiveandroid-design/build.gradle index 23a1015..f70e075 100644 --- a/reactiveandroid-design/build.gradle +++ b/reactiveandroid-design/build.gradle @@ -41,14 +41,14 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) + api fileTree(dir: 'libs', include: ['*.jar']) - compile project(':reactiveandroid-ui') + api project(':reactiveandroid-ui') - compile parent.ext.androidSupportDesign + api parent.ext.androidSupportDesign - testCompile parent.ext.junit - testCompile parent.ext.robolectric + testImplementation parent.ext.junit + testImplementation parent.ext.robolectric\ } buildscript { @@ -74,4 +74,7 @@ publish { tasks.withType(Javadoc) { enabled = false +} +repositories { + mavenCentral() } \ No newline at end of file diff --git a/reactiveandroid-support-v4/build.gradle b/reactiveandroid-support-v4/build.gradle index 9f81e05..7dcce57 100644 --- a/reactiveandroid-support-v4/build.gradle +++ b/reactiveandroid-support-v4/build.gradle @@ -41,14 +41,14 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) + api fileTree(dir: 'libs', include: ['*.jar']) - compile project(':reactiveandroid-ui') + api project(':reactiveandroid-ui') - compile parent.ext.androidSupportV4 + api parent.ext.androidSupportV4 - testCompile parent.ext.junit - testCompile parent.ext.robolectric + testImplementation parent.ext.junit + testImplementation parent.ext.robolectric } buildscript { @@ -74,4 +74,7 @@ publish { tasks.withType(Javadoc) { enabled = false +} +repositories { + mavenCentral() } \ No newline at end of file diff --git a/reactiveandroid-ui/build.gradle b/reactiveandroid-ui/build.gradle index 18f82c7..6eb675a 100644 --- a/reactiveandroid-ui/build.gradle +++ b/reactiveandroid-ui/build.gradle @@ -43,18 +43,19 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) + api fileTree(dir: 'libs', include: ['*.jar']) - compile project(':reactiveandroid') + api project(':reactiveandroid') - compile parent.ext.reactivexRxAndroid2 - compile parent.ext.androidSupportAnnotation + api parent.ext.kotlinStdLib + api parent.ext.reactivexRxAndroid2 + api parent.ext.androidSupportAnnotation - androidTestCompile parent.ext.supportTestEspressoCore - androidTestCompile parent.ext.supportTestEspressoContrib - androidTestCompile parent.ext.supportTestRunner - androidTestCompile parent.ext.supportTestRules - androidTestCompile parent.ext.awaitility + androidTestImplementation parent.ext.supportTestEspressoCore + androidTestImplementation parent.ext.supportTestEspressoContrib + androidTestImplementation parent.ext.supportTestRunner + androidTestImplementation parent.ext.supportTestRules + androidTestImplementation parent.ext.awaitility } buildscript { @@ -80,4 +81,7 @@ publish { tasks.withType(Javadoc) { enabled = false +} +repositories { + mavenCentral() } \ No newline at end of file diff --git a/reactiveandroid-ui/src/main/kotlin/com/github/kittinunf/reactiveandroid/reactive/AndroidBindingConsumer.kt b/reactiveandroid-ui/src/main/kotlin/com/github/kittinunf/reactiveandroid/reactive/AndroidBindingConsumer.kt index 42e8e0c..1e9490c 100644 --- a/reactiveandroid-ui/src/main/kotlin/com/github/kittinunf/reactiveandroid/reactive/AndroidBindingConsumer.kt +++ b/reactiveandroid-ui/src/main/kotlin/com/github/kittinunf/reactiveandroid/reactive/AndroidBindingConsumer.kt @@ -16,5 +16,5 @@ class AndroidBindingConsumer(item: E, private object Threads { val mainThreadHandler = Handler(Looper.getMainLooper()) - val mainThread = Looper.getMainLooper().thread + val mainThread: Thread = Looper.getMainLooper().thread } diff --git a/reactiveandroid-ui/src/main/kotlin/com/github/kittinunf/reactiveandroid/scheduler/AndroidThreadScheduler.kt b/reactiveandroid-ui/src/main/kotlin/com/github/kittinunf/reactiveandroid/scheduler/AndroidThreadScheduler.kt index 0783d9a..cd506e6 100644 --- a/reactiveandroid-ui/src/main/kotlin/com/github/kittinunf/reactiveandroid/scheduler/AndroidThreadScheduler.kt +++ b/reactiveandroid-ui/src/main/kotlin/com/github/kittinunf/reactiveandroid/scheduler/AndroidThreadScheduler.kt @@ -17,7 +17,7 @@ private class ReadWriteLazyVal(private val initializer: () -> T) : ReadWriteP private var value: Any? = null - operator override fun getValue(thisRef: Any?, property: KProperty<*>): T { + override operator fun getValue(thisRef: Any?, property: KProperty<*>): T { if (value == null) { value = (initializer()) ?: throw IllegalStateException("Initializer block of property ${property.name} return null") } @@ -25,7 +25,7 @@ private class ReadWriteLazyVal(private val initializer: () -> T) : ReadWriteP return value as T } - operator override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { + override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { this.value = value } diff --git a/reactiveandroid/build.gradle b/reactiveandroid/build.gradle index ea90944..630563b 100644 --- a/reactiveandroid/build.gradle +++ b/reactiveandroid/build.gradle @@ -12,13 +12,13 @@ repositories { } dependencies { - compile parent.ext.kotlinStdLib - compile parent.ext.reactivexRxJava2 + api parent.ext.kotlinStdLib + api parent.ext.reactivexRxJava2 sourceCompatibility = JavaVersion.VERSION_1_6 targetCompatibility = JavaVersion.VERSION_1_6 - testCompile parent.ext.junit + testImplementation parent.ext.junit } buildscript { diff --git a/reactiveandroid/src/main/kotlin/com/github/kittinunf/reactiveandroid/ExtensionFieldDelegate.kt b/reactiveandroid/src/main/kotlin/com/github/kittinunf/reactiveandroid/ExtensionFieldDelegate.kt index 3bf59da..d277129 100644 --- a/reactiveandroid/src/main/kotlin/com/github/kittinunf/reactiveandroid/ExtensionFieldDelegate.kt +++ b/reactiveandroid/src/main/kotlin/com/github/kittinunf/reactiveandroid/ExtensionFieldDelegate.kt @@ -9,7 +9,7 @@ class ExtensionFieldDelegate(private val initializer: (R) -> T, private val map = WeakIdentityHashMap() operator fun getValue(ref: R, property: KProperty<*>): T = - map.getOrPut(ref, { setValue(ref, property, initializer(ref)) }) + map.getOrPut(ref) { setValue(ref, property, initializer(ref)) } operator fun setValue(ref: R, property: KProperty<*>, value: T): T { ref.builder(value) @@ -25,7 +25,7 @@ class FieldDelegate(private val initializer: (T) -> U, private val map = WeakIdentityHashMap() operator fun getValue(ref: T, property: KProperty<*>): U = - map.getOrPut(ref, { setValue(ref, property, initializer(ref)) }) + map.getOrPut(ref) { setValue(ref, property, initializer(ref)) } operator fun setValue(ref: T, property: KProperty<*>, value: U): U { ref.builder(value) diff --git a/reactiveandroid/src/main/kotlin/com/github/kittinunf/reactiveandroid/Property.kt b/reactiveandroid/src/main/kotlin/com/github/kittinunf/reactiveandroid/Property.kt index ac4b535..9ab3008 100644 --- a/reactiveandroid/src/main/kotlin/com/github/kittinunf/reactiveandroid/Property.kt +++ b/reactiveandroid/src/main/kotlin/com/github/kittinunf/reactiveandroid/Property.kt @@ -12,13 +12,13 @@ interface PropertyType { val observable: Observable - fun subscribe(observer: Observer) = observable.subscribeWith(observer) + fun subscribe(observer: Observer): Observer = observable.subscribeWith(observer) - fun subscribe(onNext: (T) -> Unit) = observable.subscribe(onNext) + fun subscribe(onNext: (T) -> Unit): Disposable = observable.subscribe(onNext) - fun subscribe(onNext: (T) -> Unit, onError: (Throwable) -> Unit) = observable.subscribe(onNext, onError) + fun subscribe(onNext: (T) -> Unit, onError: (Throwable) -> Unit): Disposable = observable.subscribe(onNext, onError) - fun subscribe(onNext: (T) -> Unit, onError: (Throwable) -> Unit, onComplete: () -> Unit) = observable.subscribe(onNext, onError, onComplete) + fun subscribe(onNext: (T) -> Unit, onError: (Throwable) -> Unit, onComplete: () -> Unit): Disposable = observable.subscribe(onNext, onError, onComplete) } diff --git a/reactiveandroid/src/main/kotlin/com/github/kittinunf/reactiveandroid/helper/Observables.kt b/reactiveandroid/src/main/kotlin/com/github/kittinunf/reactiveandroid/helper/Observables.kt index 9238c7f..501e126 100644 --- a/reactiveandroid/src/main/kotlin/com/github/kittinunf/reactiveandroid/helper/Observables.kt +++ b/reactiveandroid/src/main/kotlin/com/github/kittinunf/reactiveandroid/helper/Observables.kt @@ -6,9 +6,9 @@ import io.reactivex.functions.Function3 object Observables { - inline fun combineLatest(s1: Observable, s2: Observable, crossinline f: (T1, T2) -> R) = + inline fun combineLatest(s1: Observable, s2: Observable, crossinline f: (T1, T2) -> R): Observable = Observable.combineLatest(s1, s2, BiFunction { t1, t2 -> f(t1, t2) }) - inline fun combineLatest(s1: Observable, s2: Observable, s3: Observable, crossinline f: (T1, T2, T3) -> R) = + inline fun combineLatest(s1: Observable, s2: Observable, s3: Observable, crossinline f: (T1, T2, T3) -> R): Observable = Observable.combineLatest(s1, s2, s3, Function3 { t1, t2, t3 -> f(t1, t2, t3) }) } diff --git a/reactiveandroid/src/main/kotlin/com/github/kittinunf/reactiveandroid/reactive/ObservableExtension.kt b/reactiveandroid/src/main/kotlin/com/github/kittinunf/reactiveandroid/reactive/ObservableExtension.kt index 54450c1..c670775 100644 --- a/reactiveandroid/src/main/kotlin/com/github/kittinunf/reactiveandroid/reactive/ObservableExtension.kt +++ b/reactiveandroid/src/main/kotlin/com/github/kittinunf/reactiveandroid/reactive/ObservableExtension.kt @@ -15,4 +15,4 @@ fun Observable.cachedPrevious(): Observable> { .skip(1) } -inline fun Observable.ofType() = ofType(T::class.java) \ No newline at end of file +inline fun Observable.ofType(): Observable = ofType(T::class.java) \ No newline at end of file diff --git a/reactiveandroidx-appcompat/.gitignore b/reactiveandroidx-appcompat/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/reactiveandroidx-appcompat/.gitignore @@ -0,0 +1 @@ +/build diff --git a/reactiveandroidx-appcompat/build.gradle b/reactiveandroidx-appcompat/build.gradle new file mode 100644 index 0000000..4ad5f36 --- /dev/null +++ b/reactiveandroidx-appcompat/build.gradle @@ -0,0 +1,85 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +apply plugin: 'com.novoda.bintray-release' + +android { + compileSdkVersion parent.ext.compileSdkVersion + buildToolsVersion parent.ext.buildToolVersion + + defaultConfig { + minSdkVersion parent.ext.minSdkVersion + targetSdkVersion parent.ext.compileSdkVersion + versionCode 1 + versionName "1.0" + + testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + androidTest.java.srcDirs += 'src/androidTest/kotlin' + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_6 + targetCompatibility JavaVersion.VERSION_1_6 + } + + lintOptions { + abortOnError false + } + + testOptions { + unitTests.returnDefaultValues = true + } +} + +dependencies { + api project(':reactiveandroid-ui') + + api parent.ext.androidxAppCompat + api parent.ext.androidxRecyclerView + api parent.ext.androidxCardView + + androidTestImplementation parent.ext.supportTestEspressoCore + androidTestImplementation parent.ext.supportTestEspressoContrib + androidTestImplementation parent.ext.supportTestRunner + androidTestImplementation parent.ext.supportTestRules + androidTestImplementation parent.ext.awaitility +} + +buildscript { + repositories { + mavenCentral() + } + + dependencies { + classpath parent.ext.kotlinGradlePlugin + } +} + +publish { + artifactId = parent.ext.reactiveAndroidXAppcompat + autoPublish = parent.ext.autoPublish + desc = parent.ext.desc + groupId = parent.ext.groupId + licences = parent.ext.licences + publishVersion = parent.ext.reactiveAndroidVersion + uploadName = parent.ext.uploadName + website = parent.ext.website +} + +tasks.withType(Javadoc) { + enabled = false +} +repositories { + mavenCentral() +} \ No newline at end of file diff --git a/reactiveandroidx-appcompat/src/androidTest/AndroidManifest.xml b/reactiveandroidx-appcompat/src/androidTest/AndroidManifest.xml new file mode 100644 index 0000000..7ef5c99 --- /dev/null +++ b/reactiveandroidx-appcompat/src/androidTest/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/reactiveandroidx-appcompat/src/androidTest/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/reactive/activity/RecyclerViewTestActivity.kt b/reactiveandroidx-appcompat/src/androidTest/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/reactive/activity/RecyclerViewTestActivity.kt new file mode 100644 index 0000000..90d0261 --- /dev/null +++ b/reactiveandroidx-appcompat/src/androidTest/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/reactive/activity/RecyclerViewTestActivity.kt @@ -0,0 +1,83 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.reactive.activity + +import android.app.Activity +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import kotlin.properties.Delegates + +class RecyclerViewTestActivity : Activity() { + + lateinit var recyclerView: RecyclerView + lateinit var child: View + + var items by Delegates.observable((1..100).toList()) { _, _, _ -> + runOnUiThread { + recyclerView.adapter?.notifyDataSetChanged() + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + child = View(this) + + recyclerView = RecyclerView(this).apply { + id = android.R.id.primary + layoutManager = LinearLayoutManager(this@RecyclerViewTestActivity) + } + + setContentView(recyclerView) + } + + fun setItem1Adapter(view: View) { + runOnUiThread { + recyclerView.adapter = Item1Adapter(view) + } + } + + fun setItemsAdapter() { + runOnUiThread { + recyclerView.adapter = ItemsAdapter() + } + } + + fun unsetAdapter() { + runOnUiThread { + recyclerView.adapter = null + } + } + + class Item1Adapter(val child: View) : RecyclerView.Adapter() { + + override fun getItemCount(): Int = 1 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = + object : RecyclerView.ViewHolder(child) {} + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + } + } + + class ViewHolder(val view: TextView) : RecyclerView.ViewHolder(view) + + inner class ItemsAdapter : RecyclerView.Adapter() { + + override fun getItemCount(): Int = items.size + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val textView = LayoutInflater.from(parent.context).inflate(android.R.layout.simple_list_item_1, parent, false) + return ViewHolder(textView as TextView) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.view.text = position.toString() + } + + override fun getItemId(position: Int): Long = items[position].toLong() + } +} \ No newline at end of file diff --git a/reactiveandroidx-appcompat/src/androidTest/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/reactive/widget/RecyclerViewTest.kt b/reactiveandroidx-appcompat/src/androidTest/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/reactive/widget/RecyclerViewTest.kt new file mode 100644 index 0000000..f95bf31 --- /dev/null +++ b/reactiveandroidx-appcompat/src/androidTest/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/reactive/widget/RecyclerViewTest.kt @@ -0,0 +1,198 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.reactive.widget + +import android.support.test.InstrumentationRegistry +import android.support.test.annotation.UiThreadTest +import android.support.test.rule.ActivityTestRule +import android.support.test.runner.AndroidJUnit4 +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.github.kittinunf.reactiveandroidx.appcompat.reactive.activity.RecyclerViewTestActivity +import io.reactivex.Single +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.CoreMatchers.notNullValue +import org.hamcrest.MatcherAssert.assertThat +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class RecyclerViewTest { + + @Rule + @JvmField + val activityRule = ActivityTestRule(RecyclerViewTestActivity::class.java) + + val instrumentation = InstrumentationRegistry.getInstrumentation() + + lateinit var view: RecyclerView + + @Before + fun before() { + view = activityRule.activity.recyclerView + } + + @Test + @Ignore + fun scrollStateChanged() { + val test = view.rx.scrollStateChanged().test() + + activityRule.activity.setItemsAdapter() + + instrumentation.runOnMainSync { + view.smoothScrollToPosition(10) + } + + test.awaitCount(2, {}, 2000) + + val values = test.values() + val last = values.last() + assertThat(last.recyclerView, equalTo(view)) + } + + @Test + fun scrolled() { + val test = view.rx.scrolled().test() + + activityRule.activity.setItemsAdapter() + + instrumentation.runOnMainSync { + view.scrollBy(0, 100) + } + + test.awaitCount(2) + + val values = test.values() + val last = values.last() + assertThat(last.dx, equalTo(0)) + assertThat(last.dy, equalTo(100)) + } + + @Test + @Ignore + fun recycler() { + val test = view.rx.recycler().test() + + activityRule.activity.setItemsAdapter() + + instrumentation.runOnMainSync { + view.smoothScrollToPosition(50) + } + + test.awaitCount(1, {}, 2000) + + val values = test.values() + val last = values.last() + + assertThat(last.itemView, notNullValue()) + } + + @Test + fun childViewAttachedDetached() { + val attach = view.rx.childViewAttachedToWindow().test() + val detach = view.rx.childViewDetachedFromWindow().test() + + val child = activityRule.activity.child + activityRule.activity.setItem1Adapter(child) + + attach.awaitCount(1) + attach.assertValueCount(1) + attach.assertValue(ChildAttachStateChange.ChildViewAttachedToWindow(child)) + + activityRule.activity.unsetAdapter() + detach.awaitCount(1) + detach.assertValueCount(1) + detach.assertValue(ChildAttachStateChange.ChildViewDetachedFromWindow(child)) + + attach.dispose() + detach.dispose() + + activityRule.activity.setItem1Adapter(child) + attach.assertValueCount(1) + detach.assertValueCount(1) + } + + @Test + fun touchEvent() { + } + + @Test + fun interceptTouchEvent() { + } + + @Test + @Ignore + fun changed() { + val change = view.rx.changed().test() + + val newItem = (1..5).toList() + + activityRule.activity.setItemsAdapter() + activityRule.activity.items = newItem + + instrumentation.runOnMainSync { + view.adapter?.notifyDataSetChanged() + } + + change.awaitCount(1, {}, 2000) + } + + @Test + fun itemChanged() { + } + + @Test + fun itemInserted() { + } + + @Test + fun itemMoved() { + } + + @Test + fun itemRemoved() { + } + + @Test + @UiThreadTest + fun adapter() { + val expected = object : RecyclerView.Adapter() { + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + } + + override fun getItemCount(): Int = 1 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = + object : RecyclerView.ViewHolder(View(instrumentation.context)) {} + } + + Single.just(expected).subscribe(view.rx.adapter) + + assertThat(view.adapter, equalTo(expected as RecyclerView.Adapter<*>)) + } + + @Test + @UiThreadTest + fun itemAnimator() { + val expected = DefaultItemAnimator() + + Single.just(expected).subscribe(view.rx.itemAnimator) + + assertThat(view.itemAnimator, equalTo(expected as RecyclerView.ItemAnimator)) + } + + @Test + @UiThreadTest + fun layoutManager() { + val expected = LinearLayoutManager(instrumentation.context) + + Single.just(expected).subscribe(view.rx.layoutManager) + + assertThat(view.layoutManager, equalTo(expected as RecyclerView.LayoutManager)) + } +} \ No newline at end of file diff --git a/reactiveandroidx-appcompat/src/main/AndroidManifest.xml b/reactiveandroidx-appcompat/src/main/AndroidManifest.xml new file mode 100644 index 0000000..da108d7 --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/app/ActionBarProperty.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/app/ActionBarProperty.kt new file mode 100644 index 0000000..ec61363 --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/app/ActionBarProperty.kt @@ -0,0 +1,17 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.app + +import androidx.appcompat.app.ActionBar +import com.github.kittinunf.reactiveandroid.MutableProperty +import com.github.kittinunf.reactiveandroid.createMainThreadMutableProperty + +//================================================================================ +// Properties +//================================================================================ + +val ActionBar.rx_title: MutableProperty + get() { + val getter = { title } + val setter: (CharSequence?) -> Unit = { title = it } + + return createMainThreadMutableProperty(getter, setter) + } \ No newline at end of file diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/reactive/widget/RecyclerViewExt.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/reactive/widget/RecyclerViewExt.kt new file mode 100644 index 0000000..681a701 --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/reactive/widget/RecyclerViewExt.kt @@ -0,0 +1,81 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.reactive.widget + +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.github.kittinunf.reactiveandroid.reactive.Reactive +import com.github.kittinunf.reactiveandroid.reactive.cachedPrevious +import com.github.kittinunf.reactiveandroid.scheduler.AndroidThreadScheduler +import io.reactivex.Observable +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers + +abstract class RecyclerViewProxyAdapter : RecyclerView.Adapter() { + + internal var items: List = listOf() + + abstract var createViewHolder: (ViewGroup?, Int) -> VH + abstract var bindViewHolder: (VH, Int, T) -> Unit + open var itemViewType: ((Int) -> Int)? = null + + override fun getItemCount(): Int = items.size + + fun getItem(position: Int) = items[position] + operator fun get(position: Int) = items[position] + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH = createViewHolder.invoke(parent, viewType) + + override fun onBindViewHolder(viewHolder: VH, position: Int) { + bindViewHolder.invoke(viewHolder, position, items[position]) + } + + override fun getItemViewType(position: Int): Int = + itemViewType?.invoke(position) ?: super.getItemViewType(position) + +} + +fun Reactive.bind(items: Observable>, + onCreateViewHolder: (ViewGroup?, Int) -> VH, + onBindViewHolder: (VH, Int, T) -> Unit, + onItemViewType: ((Int) -> Int)? = null): Disposable { + val proxy = object : RecyclerViewProxyAdapter() { + override var createViewHolder: (ViewGroup?, Int) -> VH = onCreateViewHolder + override var bindViewHolder: (VH, Int, T) -> Unit = onBindViewHolder + override var itemViewType: ((Int) -> Int)? = onItemViewType + } + return bind(items, proxy) +} + +fun Reactive.bind(items: Observable>, + proxy: RecyclerViewProxyAdapter): Disposable { + + class DefaultDiffCallBack(val oldItems: List, val newItems: List) : DiffUtil.Callback() { + override fun getOldListSize(): Int = oldItems.size + + override fun getNewListSize(): Int = newItems.size + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = + oldItems[oldItemPosition] == newItems[newItemPosition] + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = + oldItems[oldItemPosition] == newItems[newItemPosition] + + } + + return bind(items, proxy) { oldItems, newItems -> DefaultDiffCallBack(oldItems, newItems) } +} + +fun Reactive.bind(items: Observable>, + proxy: RecyclerViewProxyAdapter, + callback: (List, List) -> DiffUtil.Callback): Disposable { + item.adapter = proxy + + return items.cachedPrevious() + .map { (oldItems, newItems) -> + proxy.items = newItems ?: emptyList() + DiffUtil.calculateDiff(callback(oldItems ?: emptyList(), newItems ?: emptyList())) + } + .subscribeOn(Schedulers.computation()) + .observeOn(AndroidThreadScheduler.main) + .subscribe { result -> result.dispatchUpdatesTo(proxy) } +} diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/reactive/widget/RecyclerViewRx.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/reactive/widget/RecyclerViewRx.kt new file mode 100644 index 0000000..79aa0d0 --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/reactive/widget/RecyclerViewRx.kt @@ -0,0 +1,201 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.reactive.widget + +import android.view.MotionEvent +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.github.kittinunf.reactiveandroid.FieldDelegate +import com.github.kittinunf.reactiveandroid.internal.AndroidMainThreadDisposable +import com.github.kittinunf.reactiveandroid.reactive.AndroidBindingConsumer +import com.github.kittinunf.reactiveandroid.reactive.Reactive +import com.github.kittinunf.reactiveandroid.reactive.ofType +import io.reactivex.Observable +import io.reactivex.functions.Consumer + +val RecyclerView.rx: Reactive by FieldDelegate({ Reactive(it) }) + +// Properties + +val Reactive.adapter: Consumer> + get() = AndroidBindingConsumer(item) { item, value -> + item.adapter = value + } + +val Reactive.itemAnimator: Consumer + get() = AndroidBindingConsumer(item) { item, value -> + item.itemAnimator = value + } + +val Reactive.layoutManager: Consumer + get() = AndroidBindingConsumer(item) { item, value -> + item.layoutManager = value + } + +// Listeners + +sealed class RecyclerViewScrollEvent { + data class ScrollStateChanged(val recyclerView: RecyclerView, val newState: Int) : RecyclerViewScrollEvent() + data class Scrolled(val recyclerView: RecyclerView, val dx: Int, val dy: Int) : RecyclerViewScrollEvent() +} + +fun Reactive.scrollStateChanged() = scroll().ofType() + +fun Reactive.scrolled() = scroll().ofType() + +private fun Reactive.scroll(): Observable = + Observable.create { emitter -> + + val listener = object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + if (!emitter.isDisposed) { + emitter.onNext(RecyclerViewScrollEvent.ScrollStateChanged(recyclerView, newState)) + } + } + + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + if (!emitter.isDisposed) { + emitter.onNext(RecyclerViewScrollEvent.Scrolled(recyclerView, dx, dy)) + } + } + } + + item.addOnScrollListener(listener) + + emitter.setDisposable(AndroidMainThreadDisposable { item.removeOnScrollListener(listener) }) + } + +fun Reactive.recycler(): Observable = + Observable.create { emitter -> + item.setRecyclerListener { emitter.onNext(it) } + + emitter.setDisposable(AndroidMainThreadDisposable { item.setRecyclerListener(null) }) + } + +sealed class ChildAttachStateChange { + data class ChildViewDetachedFromWindow(val view: View) : ChildAttachStateChange() + data class ChildViewAttachedToWindow(val view: View) : ChildAttachStateChange() +} + +fun Reactive.childViewDetachedFromWindow() = childAttachStateChange().ofType() + +fun Reactive.childViewAttachedToWindow() = childAttachStateChange().ofType() + +private fun Reactive.childAttachStateChange(): Observable = + Observable.create { emitter -> + + val listener = object : RecyclerView.OnChildAttachStateChangeListener { + override fun onChildViewDetachedFromWindow(view: View) { + if (!emitter.isDisposed) { + emitter.onNext(ChildAttachStateChange.ChildViewDetachedFromWindow(view)) + } + } + + override fun onChildViewAttachedToWindow(view: View) { + if (!emitter.isDisposed) { + emitter.onNext(ChildAttachStateChange.ChildViewAttachedToWindow(view)) + } + } + } + + item.addOnChildAttachStateChangeListener(listener) + + emitter.setDisposable(AndroidMainThreadDisposable { item.removeOnChildAttachStateChangeListener(listener) }) + } + +sealed class ItemTouch { + data class TouchEvent(val recyclerView: RecyclerView, val motionEvent: MotionEvent) : ItemTouch() + data class InterceptTouchEvent(val recyclerView: RecyclerView, val motionEvent: MotionEvent) : ItemTouch() + data class RequestDisallowInterceptTouchEvent(val disallowIntercept: Boolean) : ItemTouch() +} + +fun Reactive.touchEvent() = itemTouch().ofType() + +fun Reactive.interceptTouchEvent() = itemTouch().ofType() + +fun Reactive.requestDisallowInterceptTouchEvent() = itemTouch().ofType() + +private fun Reactive.itemTouch(onInterceptTouchConsumed: Boolean = true): Observable = + Observable.create { emitter -> + + val listener = object : RecyclerView.OnItemTouchListener { + override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) { + if (!emitter.isDisposed) { + emitter.onNext(ItemTouch.TouchEvent(rv, e)) + } + } + + override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean { + if (!emitter.isDisposed) { + emitter.onNext(ItemTouch.InterceptTouchEvent(rv, e)) + } + return onInterceptTouchConsumed + } + + override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) { + if (!emitter.isDisposed) { + emitter.onNext(ItemTouch.RequestDisallowInterceptTouchEvent(disallowIntercept)) + } + } + } + + item.addOnItemTouchListener(listener) + + emitter.setDisposable(AndroidMainThreadDisposable { item.removeOnItemTouchListener(listener) }) + } + +sealed class AdapterData { + object Changed : AdapterData() + data class ItemRangeChanged(val positionStart: Int, val itemCount: Int) : AdapterData() + data class ItemRangeInserted(val positionStart: Int, val itemCount: Int) : AdapterData() + data class ItemRangeMoved(val fromPosition: Int, val toPosition: Int, val itemCount: Int) : AdapterData() + data class ItemRangeRemoved(val positionStart: Int, val itemCount: Int) : AdapterData() +} + +fun Reactive.changed() = adapterDataObserver().ofType() + +fun Reactive.itemRangeChanged() = adapterDataObserver().ofType() + +fun Reactive.itemRangeInserted() = adapterDataObserver().ofType() + +fun Reactive.itemRangeMoved() = adapterDataObserver().ofType() + +fun Reactive.itemRangeRemoved() = adapterDataObserver().ofType() + +private fun Reactive.adapterDataObserver(): Observable = + Observable.create { emitter -> + + val listener = object : RecyclerView.AdapterDataObserver() { + override fun onChanged() { + if (!emitter.isDisposed) { + emitter.onNext(AdapterData.Changed) + } + } + + override fun onItemRangeChanged(positionStart: Int, itemCount: Int) { + if (!emitter.isDisposed) { + emitter.onNext(AdapterData.ItemRangeChanged(positionStart, itemCount)) + } + } + + override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { + if (!emitter.isDisposed) { + emitter.onNext(AdapterData.ItemRangeInserted(positionStart, itemCount)) + } + } + + override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { + if (!emitter.isDisposed) { + emitter.onNext(AdapterData.ItemRangeMoved(fromPosition, toPosition, itemCount)) + } + } + + override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { + if (!emitter.isDisposed) { + emitter.onNext(AdapterData.ItemRangeRemoved(positionStart, itemCount)) + } + } + } + + item.adapter?.registerAdapterDataObserver(listener) + + emitter.setDisposable(AndroidMainThreadDisposable { item.adapter?.unregisterAdapterDataObserver(listener) }) + } \ No newline at end of file diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/view/PagerTabStripProperty.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/view/PagerTabStripProperty.kt new file mode 100644 index 0000000..4c0c57f --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/view/PagerTabStripProperty.kt @@ -0,0 +1,25 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.view + +import androidx.viewpager.widget.PagerTabStrip +import com.github.kittinunf.reactiveandroid.MutableProperty +import com.github.kittinunf.reactiveandroid.createMainThreadMutableProperty + +//================================================================================ +// Properties +//================================================================================ + +val PagerTabStrip.rx_drawFullUnderline: MutableProperty + get() { + val getter = { drawFullUnderline } + val setter: (Boolean) -> Unit = { drawFullUnderline = it } + + return createMainThreadMutableProperty(getter, setter) + } + +val PagerTabStrip.rx_tabIndicatorColor: MutableProperty + get() { + val getter = { tabIndicatorColor } + val setter: (Int) -> Unit = { tabIndicatorColor = it } + + return createMainThreadMutableProperty(getter, setter) + } diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/view/PagerTitleStripProperty.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/view/PagerTitleStripProperty.kt new file mode 100644 index 0000000..8bee8b3 --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/view/PagerTitleStripProperty.kt @@ -0,0 +1,17 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.view + +import androidx.viewpager.widget.PagerTitleStrip +import com.github.kittinunf.reactiveandroid.MutableProperty +import com.github.kittinunf.reactiveandroid.createMainThreadMutableProperty + +//================================================================================ +// Properties +//================================================================================ + +val PagerTitleStrip.rx_textSpacing: MutableProperty + get() { + val getter = { textSpacing } + val setter: (Int) -> Unit = { textSpacing = it } + + return createMainThreadMutableProperty(getter, setter) + } diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/view/ViewPagerEvent.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/view/ViewPagerEvent.kt new file mode 100644 index 0000000..3982581 --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/view/ViewPagerEvent.kt @@ -0,0 +1,85 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.view + +import androidx.viewpager.widget.ViewPager +import com.github.kittinunf.reactiveandroid.ExtensionFieldDelegate +import com.github.kittinunf.reactiveandroid.subscription.AndroidMainThreadSubscription +import io.reactivex.Observable + +//================================================================================ +// Events +//================================================================================ + +fun ViewPager.rx_pageScrollStateChanged(): Observable { + return Observable.create { subscriber -> + _pageChange.onPageScrollStateChanged { + subscriber.onNext(it) + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + removeOnPageChangeListener(_pageChange) + }) + } +} + +data class PageScrolledListener(val position: Int, val positionOffset: Float, val positionOffsetPixels: Int) + +fun ViewPager.rx_pageScrolled(): Observable { + return Observable.create { subscriber -> + _pageChange.onPageScrolled { position, positionOffset, positionOffsetPixels -> + subscriber.onNext(PageScrolledListener(position, positionOffset, positionOffsetPixels)) + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + removeOnPageChangeListener(_pageChange) + }) + } +} + +fun ViewPager.rx_pageSelected(): Observable { + return Observable.create { subscriber -> + _pageChange.onPageSelected { + subscriber.onNext(it) + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + removeOnPageChangeListener(_pageChange) + }) + } +} + +private val ViewPager._pageChange: _ViewPager_OnPageChangeListener + by ExtensionFieldDelegate({ _ViewPager_OnPageChangeListener() }, { addOnPageChangeListener(it) }) + +private class _ViewPager_OnPageChangeListener : ViewPager.OnPageChangeListener { + + private var onPageScrollStateChanged: ((Int) -> Unit)? = null + + private var onPageScrolled: ((Int, Float, Int) -> Unit)? = null + + private var onPageSelected: ((Int) -> Unit)? = null + + fun onPageScrollStateChanged(listener: (Int) -> Unit) { + onPageScrollStateChanged = listener + } + + override fun onPageScrollStateChanged(state: Int) { + onPageScrollStateChanged?.invoke(state) + } + + fun onPageScrolled(listener: (Int, Float, Int) -> Unit) { + onPageScrolled = listener + } + + override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { + onPageScrolled?.invoke(position, positionOffset, positionOffsetPixels) + } + + fun onPageSelected(listener: (Int) -> Unit) { + onPageSelected = listener + } + + override fun onPageSelected(position: Int) { + onPageSelected?.invoke(position) + } + +} diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/view/ViewPagerProperty.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/view/ViewPagerProperty.kt new file mode 100644 index 0000000..61479b3 --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/view/ViewPagerProperty.kt @@ -0,0 +1,17 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.view + +import androidx.viewpager.widget.ViewPager +import com.github.kittinunf.reactiveandroid.MutableProperty +import com.github.kittinunf.reactiveandroid.createMainThreadMutableProperty + +//================================================================================ +// Properties +//================================================================================ + +val ViewPager.rx_currentItem: MutableProperty + get() { + val getter = { currentItem } + val setter: (Int) -> Unit = { currentItem = it } + + return createMainThreadMutableProperty(getter, setter) + } diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/ActionMenuViewEvent.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/ActionMenuViewEvent.kt new file mode 100644 index 0000000..3959665 --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/ActionMenuViewEvent.kt @@ -0,0 +1,23 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.widget + +import android.view.MenuItem +import androidx.appcompat.widget.ActionMenuView +import com.github.kittinunf.reactiveandroid.subscription.AndroidMainThreadSubscription +import io.reactivex.Observable + +//================================================================================ +// Events +//================================================================================ + +fun ActionMenuView.rx_menuItemClick(consumed: Boolean): Observable { + return Observable.create { subscriber -> + setOnMenuItemClickListener { + subscriber.onNext(it) + consumed + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + setOnMenuItemClickListener(null) + }) + } +} diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/ActionMenuViewProperty.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/ActionMenuViewProperty.kt new file mode 100644 index 0000000..9703552 --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/ActionMenuViewProperty.kt @@ -0,0 +1,26 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.widget + +import android.graphics.drawable.Drawable +import androidx.appcompat.widget.ActionMenuView +import com.github.kittinunf.reactiveandroid.MutableProperty +import com.github.kittinunf.reactiveandroid.createMainThreadMutableProperty + +//================================================================================ +// Properties +//================================================================================ + +val ActionMenuView.rx_overflowIcon: MutableProperty + get() { + val getter = { overflowIcon } + val setter: (Drawable?) -> Unit = { overflowIcon = it } + + return createMainThreadMutableProperty(getter, setter) + } + +val ActionMenuView.rx_popupTheme: MutableProperty + get() { + val getter = { popupTheme } + val setter: (Int) -> Unit = { popupTheme = it } + + return createMainThreadMutableProperty(getter, setter) + } diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/CardViewProperty.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/CardViewProperty.kt new file mode 100644 index 0000000..a9f7739 --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/CardViewProperty.kt @@ -0,0 +1,59 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.widget + +import androidx.cardview.widget.CardView +import com.github.kittinunf.reactiveandroid.MutableProperty +import com.github.kittinunf.reactiveandroid.createMainThreadMutableProperty +import com.github.kittinunf.reactiveandroid.reactive.view.Padding + +//================================================================================ +// Properties +//================================================================================ + +val CardView.rx_radius: MutableProperty + get() { + val getter = { radius } + val setter: (Float) -> Unit = { radius = it } + + return createMainThreadMutableProperty(getter, setter) + } + +val CardView.rx_cardElevation: MutableProperty + get() { + val getter = { cardElevation } + val setter: (Float) -> Unit = { cardElevation = it } + + return createMainThreadMutableProperty(getter, setter) + } + +val CardView.rx_maxCardElevation: MutableProperty + get() { + val getter = { maxCardElevation } + val setter: (Float) -> Unit = { maxCardElevation = it } + + return createMainThreadMutableProperty(getter, setter) + } + + +val CardView.rx_preventCornerOverlap: MutableProperty + get() { + val getter = { preventCornerOverlap } + val setter: (Boolean) -> Unit = { preventCornerOverlap = it } + + return createMainThreadMutableProperty(getter, setter) + } + +val CardView.rx_useCompatPadding: MutableProperty + get() { + val getter = { useCompatPadding } + val setter: (Boolean) -> Unit = { useCompatPadding = it } + + return createMainThreadMutableProperty(getter, setter) + } + +val CardView.rx_contentPadding: MutableProperty + get() { + val getter = { Padding(contentPaddingLeft, contentPaddingTop, contentPaddingRight, contentPaddingBottom) } + val setter: (Padding) -> Unit = { setContentPadding(it.start, it.top, it.end, it.bottom) } + + return createMainThreadMutableProperty(getter, setter) + } diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/DrawerLayoutEvent.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/DrawerLayoutEvent.kt new file mode 100644 index 0000000..9f1e1fc --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/DrawerLayoutEvent.kt @@ -0,0 +1,110 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.widget + +import android.view.View +import androidx.drawerlayout.widget.DrawerLayout +import com.github.kittinunf.reactiveandroid.ExtensionFieldDelegate +import com.github.kittinunf.reactiveandroid.subscription.AndroidMainThreadSubscription +import io.reactivex.Observable + +//================================================================================ +// Events +//================================================================================ + +fun DrawerLayout.rx_drawerClosed(): Observable { + return Observable.create { subscriber -> + _drawer.onDrawerClosed { + if (it != null) subscriber.onNext(it) + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + removeDrawerListener(_drawer) + }) + } +} + +fun DrawerLayout.rx_drawerStateChanged(): Observable { + return Observable.create { subscriber -> + _drawer.onDrawerStateChanged { + subscriber.onNext(it) + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + removeDrawerListener(_drawer) + }) + } +} + +data class DrawerSlideListener(val view: View?, val offset: Float) + +fun DrawerLayout.rx_drawerSlide(): Observable { + return Observable.create { subscriber -> + _drawer.onDrawerSlide { view, offset -> + subscriber.onNext(DrawerSlideListener(view, offset)) + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + removeDrawerListener(_drawer) + }) + } +} + +fun DrawerLayout.rx_drawerOpened(): Observable { + return Observable.create { subscriber -> + _drawer.onDrawerOpened { + if (it != null) subscriber.onNext(it) + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + removeDrawerListener(_drawer) + }) + } +} + +private val DrawerLayout._drawer: _DrawerLayout_DrawerListener + by ExtensionFieldDelegate({ _DrawerLayout_DrawerListener() }, { addDrawerListener(it) }) + +internal class _DrawerLayout_DrawerListener : DrawerLayout.DrawerListener { + + private var onDrawerClosed: ((View?) -> Unit)? = null + + private var onDrawerStateChanged: ((Int) -> Unit)? = null + + private var onDrawerSlide: ((View?, Float) -> Unit)? = null + + private var onDrawerOpened: ((View?) -> Unit)? = null + + fun onDrawerClosed(listener: (View?) -> Unit) { + onDrawerClosed = listener + } + + override fun onDrawerClosed(drawerView: View) { + onDrawerClosed?.invoke(drawerView) + } + + fun onDrawerStateChanged(listener: (Int) -> Unit) { + onDrawerStateChanged = listener + } + + override fun onDrawerStateChanged(newState: Int) { + onDrawerStateChanged?.invoke(newState) + } + + fun onDrawerSlide(listener: (View?, Float) -> Unit) { + onDrawerSlide = listener + } + + override fun onDrawerSlide(drawerView: View, slideOffset: Float) { + onDrawerSlide?.invoke(drawerView, slideOffset) + } + + fun onDrawerOpened(listener: (View?) -> Unit) { + onDrawerOpened = listener + } + + override fun onDrawerOpened(drawerView: View) { + onDrawerOpened?.invoke(drawerView) + } + +} + + diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/DrawerLayoutProperty.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/DrawerLayoutProperty.kt new file mode 100644 index 0000000..3c44666 --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/DrawerLayoutProperty.kt @@ -0,0 +1,26 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.widget + +import android.graphics.drawable.Drawable +import androidx.drawerlayout.widget.DrawerLayout +import com.github.kittinunf.reactiveandroid.MutableProperty +import com.github.kittinunf.reactiveandroid.createMainThreadMutableProperty + +//================================================================================ +// Properties +//================================================================================ + +val DrawerLayout.rx_drawerElevation: MutableProperty + get() { + val getter = { drawerElevation } + val setter: (Float) -> Unit = { drawerElevation = it } + + return createMainThreadMutableProperty(getter, setter) + } + +val DrawerLayout.rx_statusBarBackground: MutableProperty + get() { + val getter = { statusBarBackgroundDrawable!! } + val setter: (Drawable) -> Unit = { setStatusBarBackground(it) } + + return createMainThreadMutableProperty(getter, setter) + } diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/FragmentPagerExtension.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/FragmentPagerExtension.kt new file mode 100644 index 0000000..95aa96a --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/FragmentPagerExtension.kt @@ -0,0 +1,47 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.widget + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentPagerAdapter +import androidx.viewpager.widget.ViewPager +import com.github.kittinunf.reactiveandroid.scheduler.AndroidThreadScheduler +import io.reactivex.Observable +import io.reactivex.disposables.Disposable + +abstract class FragmentPagerProxyAdapter(fragmentManager: FragmentManager) : FragmentPagerAdapter(fragmentManager) { + + internal var items: List = listOf() + + abstract var pageTitle: ((Int, ARG) -> String) + + abstract var item: ((Int, ARG) -> Fragment) + + override fun getItem(position: Int): Fragment? = item.invoke(position, items[position]) + + override fun getPageTitle(position: Int): CharSequence? = pageTitle.invoke(position, items[position]) + + override fun getCount(): Int = items.size + + override fun getItemId(position: Int): Long = position.toLong() + +} + +fun > ViewPager.rx_fragmentsWith(observable: Observable, fragmentManager: FragmentManager, + getItem: (Int, ARG) -> Fragment, + getPageTitle: ((Int, ARG) -> String)): Disposable { + val proxyAdapter = object : FragmentPagerProxyAdapter(fragmentManager) { + + override var item: (Int, ARG) -> Fragment = getItem + override var pageTitle: ((Int, ARG) -> String) = getPageTitle + + } + return rx_fragmentsWith(observable, proxyAdapter) +} + +fun , L : List> ViewPager.rx_fragmentsWith(observable: Observable, fragmentPagerProxyAdapter: ADT): Disposable { + adapter = fragmentPagerProxyAdapter + return observable.observeOn(AndroidThreadScheduler.main).subscribe { + fragmentPagerProxyAdapter.items = it + fragmentPagerProxyAdapter.notifyDataSetChanged() + } +} diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/FragmentStatePagerExtension.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/FragmentStatePagerExtension.kt new file mode 100644 index 0000000..a382d3f --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/FragmentStatePagerExtension.kt @@ -0,0 +1,45 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.widget + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentStatePagerAdapter +import androidx.viewpager.widget.ViewPager +import com.github.kittinunf.reactiveandroid.scheduler.AndroidThreadScheduler +import io.reactivex.Observable +import io.reactivex.disposables.Disposable + +abstract class FragmentStatePagerProxyAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager) { + + internal var items: List = listOf() + + abstract var pageTitle: ((Int, ARG) -> String) + + abstract var item: ((Int, ARG) -> Fragment) + + override fun getItem(position: Int): Fragment? = item.invoke(position, items[position]) + + override fun getPageTitle(position: Int): CharSequence? = pageTitle.invoke(position, items[position]) + + override fun getCount(): Int = items.size + +} + +fun > ViewPager.rx_fragmentsStateWith(observable: Observable, fragmentManager: FragmentManager, + getItem: (Int, ARG) -> Fragment, + getPageTitle: ((Int, ARG) -> String)): Disposable { + val proxyAdapter = object : FragmentStatePagerProxyAdapter(fragmentManager) { + + override var item: (Int, ARG) -> Fragment = getItem + override var pageTitle: ((Int, ARG) -> String) = getPageTitle + + } + return rx_fragmentsStateWith(observable, proxyAdapter) +} + +fun , L : List> ViewPager.rx_fragmentsStateWith(observable: Observable, fragmentPagerProxyAdapter: ADT): Disposable { + adapter = fragmentPagerProxyAdapter + return observable.observeOn(AndroidThreadScheduler.main).subscribe { + fragmentPagerProxyAdapter.items = it + post { fragmentPagerProxyAdapter.notifyDataSetChanged() } + } +} diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/PopupMenuEvent.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/PopupMenuEvent.kt new file mode 100644 index 0000000..8ffd557 --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/PopupMenuEvent.kt @@ -0,0 +1,37 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.widget + +import android.view.MenuItem +import androidx.appcompat.widget.PopupMenu +import com.github.kittinunf.reactiveandroid.subscription.AndroidMainThreadSubscription +import io.reactivex.Observable + +//================================================================================ +// Events +//================================================================================ + +fun PopupMenu.rx_dismiss(): Observable { + return Observable.create { subscriber -> + setOnDismissListener { + subscriber.onNext(it) + + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + setOnDismissListener(null) + }) + } +} + +fun PopupMenu.rx_menuItemClick(consumed: Boolean): Observable { + return Observable.create { subscriber -> + setOnMenuItemClickListener { + subscriber.onNext(it) + consumed + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + setOnMenuItemClickListener(null) + }) + } +} + diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/PopupMenuProperty.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/PopupMenuProperty.kt new file mode 100644 index 0000000..a0c6f7c --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/PopupMenuProperty.kt @@ -0,0 +1,17 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.widget + +import androidx.appcompat.widget.PopupMenu +import com.github.kittinunf.reactiveandroid.MutableProperty +import com.github.kittinunf.reactiveandroid.createMainThreadMutableProperty + +//================================================================================ +// Properties +//================================================================================ + +val PopupMenu.rx_gravity: MutableProperty + get() { + val getter = { gravity } + val setter: (Int) -> Unit = { gravity = it } + + return createMainThreadMutableProperty(getter, setter) + } diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SearchViewEvent.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SearchViewEvent.kt new file mode 100644 index 0000000..54ac9d0 --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SearchViewEvent.kt @@ -0,0 +1,157 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.widget + +import android.view.View +import androidx.appcompat.widget.SearchView +import com.github.kittinunf.reactiveandroid.ExtensionFieldDelegate +import com.github.kittinunf.reactiveandroid.reactive.view.FocusChangeListener +import com.github.kittinunf.reactiveandroid.subscription.AndroidMainThreadSubscription +import io.reactivex.Observable + +//================================================================================ +// Events +//================================================================================ + +fun SearchView.rx_close(overriden: Boolean): Observable { + return Observable.create { subscriber -> + setOnCloseListener { -> + subscriber.onNext(Unit) + overriden + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + setOnCloseListener(null) + }) + } +} + +fun SearchView.rx_queryTextFocusChange(): Observable { + return Observable.create { subscriber -> + setOnQueryTextFocusChangeListener { view, hasFocus -> + subscriber.onNext(FocusChangeListener(view, hasFocus)) + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + setOnQueryTextFocusChangeListener(null) + }) + } +} + +fun SearchView.rx_queryTextChange(consumed: Boolean): Observable { + return Observable.create { subscriber -> + _queryText.onQueryTextChange { + if (it != null) subscriber.onNext(it) + consumed + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + setOnQueryTextListener(null) + }) + } +} + +fun SearchView.rx_queryTextSubmit(consumed: Boolean): Observable { + return Observable.create { subscriber -> + _queryText.onQueryTextSubmit { + if (it != null) subscriber.onNext(it) + consumed + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + setOnQueryTextListener(null) + }) + } +} + +fun SearchView.rx_searchClick(): Observable { + return Observable.create { subscriber -> + setOnSearchClickListener { + subscriber.onNext(it) + + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + setOnSearchClickListener(null) + }) + } +} + +fun SearchView.rx_suggestionSelect(consumed: Boolean): Observable { + return Observable.create { subscriber -> + _suggestion.onSuggestionSelect { + subscriber.onNext(it) + consumed + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + setOnSuggestionListener(null) + }) + } +} + +fun SearchView.rx_suggestionClick(consumed: Boolean): Observable { + return Observable.create { subscriber -> + _suggestion.onSuggestionClick { + subscriber.onNext(it) + consumed + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + setOnSuggestionListener(null) + }) + } +} + +private val SearchView._suggestion: _SearchView_OnSuggestionListener + by ExtensionFieldDelegate({ _SearchView_OnSuggestionListener() }, { setOnSuggestionListener(it) }) + +internal class _SearchView_OnSuggestionListener : SearchView.OnSuggestionListener { + + private var onSuggestionSelect: ((Int) -> Boolean)? = null + + private var onSuggestionClick: ((Int) -> Boolean)? = null + + fun onSuggestionSelect(listener: (Int) -> Boolean) { + onSuggestionSelect = listener + } + + override fun onSuggestionSelect(position: Int): Boolean { + return onSuggestionSelect?.invoke(position) ?: false + } + + fun onSuggestionClick(listener: (Int) -> Boolean) { + onSuggestionClick = listener + } + + override fun onSuggestionClick(position: Int): Boolean { + return onSuggestionClick?.invoke(position) ?: false + } + +} + +private val SearchView._queryText: _SearchView_OnQueryTextListener + by ExtensionFieldDelegate({ _SearchView_OnQueryTextListener() }, { setOnQueryTextListener(it) }) + +private class _SearchView_OnQueryTextListener : SearchView.OnQueryTextListener { + + private var onQueryTextSubmit: ((String?) -> Boolean)? = null + + private var onQueryTextChange: ((String?) -> Boolean)? = null + + fun onQueryTextSubmit(listener: (String?) -> Boolean) { + onQueryTextSubmit = listener + } + + override fun onQueryTextSubmit(query: String?): Boolean { + return onQueryTextSubmit?.invoke(query) ?: false + } + + fun onQueryTextChange(listener: (String?) -> Boolean) { + onQueryTextChange = listener + } + + override fun onQueryTextChange(newText: String?): Boolean { + return onQueryTextChange?.invoke(newText) ?: false + } + +} + diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SearchViewProperty.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SearchViewProperty.kt new file mode 100644 index 0000000..606d760 --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SearchViewProperty.kt @@ -0,0 +1,44 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.widget + +import androidx.appcompat.widget.SearchView +import androidx.cursoradapter.widget.CursorAdapter +import com.github.kittinunf.reactiveandroid.MutableProperty +import com.github.kittinunf.reactiveandroid.createMainThreadMutableProperty + +//================================================================================ +// Properties +//================================================================================ + +data class SearchViewQuery(val text: CharSequence, val submit: Boolean) + +val SearchView.rx_query: MutableProperty + get() { + val getter = { SearchViewQuery(query, false) } + val setter: (SearchViewQuery) -> Unit = { setQuery(it.text, it.submit) } + + return createMainThreadMutableProperty(getter, setter) + } + +val SearchView.rx_queryRequirementEnabled: MutableProperty + get() { + val getter = { isQueryRefinementEnabled } + val setter: (Boolean) -> Unit = { isQueryRefinementEnabled = it } + + return createMainThreadMutableProperty(getter, setter) + } + +val SearchView.rx_submitButtonEnabled: MutableProperty + get() { + val getter = { isSubmitButtonEnabled } + val setter: (Boolean) -> Unit = { isSubmitButtonEnabled = it } + + return createMainThreadMutableProperty(getter, setter) + } + +val SearchView.rx_suggestionsAdapter: MutableProperty + get() { + val getter = { suggestionsAdapter } + val setter: (CursorAdapter) -> Unit = { suggestionsAdapter = it } + + return createMainThreadMutableProperty(getter, setter) + } diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SectionedRecyclerViewExtension.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SectionedRecyclerViewExtension.kt new file mode 100644 index 0000000..718c4fc --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SectionedRecyclerViewExtension.kt @@ -0,0 +1,110 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.widget + +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.reactivex.Observable +import io.reactivex.disposables.Disposable +import kotlin.properties.Delegates + +interface SectionModelType { + val items: List + + operator fun get(index: Int): T = items[index] + + fun size(): Int = items.size +} + +class SimpleSection(val name: String, override var items: List) : SectionModelType + +fun List.mapToSection(sectionName: (T) -> X): List> = groupBy { sectionName(it) }.mapTo(mutableListOf()) { + SimpleSection(it.key.toString(), it.value) +} + +fun Observable>.mapToSection(sectionName: (T) -> X): Observable>> = map { it.mapToSection(sectionName) } + +val SECTION_HEADER_VIEW_TYPE = 1000 +val SECTION_ITEM_VIEW_TYPE = 1001 + +abstract class SectionedRecyclerViewProxyAdapter, VH : RecyclerView.ViewHolder> : RecyclerView.Adapter() { + + internal var sections: List by Delegates.observable(listOf()) { property, oldValue, newValue -> + headerPositions = newValue.foldIndexed(mutableListOf()) { index, acc, section -> + val value = if (index == 0) 0 else { + val sizeOfPreviousSection = sections[index - 1].size() + val previousIndex = acc[index - 1] + val self = 1 + previousIndex + sizeOfPreviousSection + self + } + acc.add(value) + acc + } + } + + private var headerPositions = mutableListOf() + + abstract var createViewHolder: (ViewGroup?, Int) -> VH + abstract var bindHeaderViewHolder: (VH, Int, S) -> Unit + abstract var bindItemViewHolder: (VH, Int, T) -> Unit + + override fun getItemCount(): Int = sections.fold(sections.size) { acc, section -> acc + section.items.size } + + override fun getItemViewType(position: Int): Int = if (headerPositions.contains(position)) SECTION_HEADER_VIEW_TYPE else SECTION_ITEM_VIEW_TYPE + + override fun onBindViewHolder(holder: VH, position: Int) { + if (headerPositions.contains(position)) { + bindHeaderViewHolder(holder, position, sections[headerPositions.indexOf(position)]) + } else { + val item = findItemFromPosition(position) + bindItemViewHolder(holder, position, item!!) + } + } + + private fun findItemFromPosition(position: Int): T? { + //copy into another list + val _headerPositions = headerPositions.toMutableList() + + //add upperbound to support last item + _headerPositions.add(Int.MAX_VALUE) + + for (index in 0.._headerPositions.size - 1) { + val headerPosition = _headerPositions[index] + if (headerPosition > position) { + val previousIndex = index - 1 + val previousHeaderPosition = _headerPositions[previousIndex] + val relativePreviousIndex = position - previousHeaderPosition - 1 + return sections[previousIndex][relativePreviousIndex] + } + } + + return null + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH = createViewHolder.invoke(parent, viewType) + +} + +fun , C : List> RecyclerView.rx_itemsWith(observable: Observable, + onCreateViewHolder: (ViewGroup?, Int) -> VH, + onBindHeaderViewHolder: (VH, Int, S) -> Unit, + onBindItemViewHolder: (VH, Int, T) -> Unit): Disposable { + val proxyAdapter = object : SectionedRecyclerViewProxyAdapter() { + + override var createViewHolder: (ViewGroup?, Int) -> VH = onCreateViewHolder + + override var bindHeaderViewHolder: (VH, Int, S) -> Unit = onBindHeaderViewHolder + + override var bindItemViewHolder: (VH, Int, T) -> Unit = onBindItemViewHolder + + } + return rx_itemsWith(observable, proxyAdapter) +} + +fun , C : List, A : SectionedRecyclerViewProxyAdapter> RecyclerView.rx_itemsWith(observable: Observable, + sectionedRecyclerViewProxyAdapter: A): Disposable { + adapter = sectionedRecyclerViewProxyAdapter + return observable.subscribe { + sectionedRecyclerViewProxyAdapter.sections = it + post { sectionedRecyclerViewProxyAdapter.notifyDataSetChanged() } + } + +} diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SlidingPaneLayoutEvent.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SlidingPaneLayoutEvent.kt new file mode 100644 index 0000000..3fb948f --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SlidingPaneLayoutEvent.kt @@ -0,0 +1,87 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.widget + +import android.view.View +import androidx.slidingpanelayout.widget.SlidingPaneLayout +import com.github.kittinunf.reactiveandroid.ExtensionFieldDelegate +import com.github.kittinunf.reactiveandroid.subscription.AndroidMainThreadSubscription +import io.reactivex.Observable + +//================================================================================ +// Events +//================================================================================ + +data class PanelSlideListener(val panel: View?, val slideOffset: Float) + +fun SlidingPaneLayout.rx_panelSlide(): Observable { + return Observable.create { subscriber -> + _panelSlide.onPanelSlide { view, offset -> + subscriber.onNext(PanelSlideListener(view, offset)) + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + setPanelSlideListener(null) + }) + } +} + +fun SlidingPaneLayout.rx_panelOpened(): Observable { + return Observable.create { subscriber -> + _panelSlide.onPanelOpened { + if (it != null) subscriber.onNext(it) + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + setPanelSlideListener(null) + }) + } +} + +fun SlidingPaneLayout.rx_panelClosed(): Observable { + return Observable.create { subscriber -> + _panelSlide.onPanelClosed { + if (it != null) subscriber.onNext(it) + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + setPanelSlideListener(null) + }) + } +} + +private val SlidingPaneLayout._panelSlide: _SlidingPaneLayout_PanelSlideListener + by ExtensionFieldDelegate({ _SlidingPaneLayout_PanelSlideListener() }, { setPanelSlideListener(it) }) + +internal class _SlidingPaneLayout_PanelSlideListener : SlidingPaneLayout.PanelSlideListener { + + private var onPanelSlide: ((View?, Float) -> Unit)? = null + + private var onPanelClosed: ((View?) -> Unit)? = null + + private var onPanelOpened: ((View?) -> Unit)? = null + + fun onPanelSlide(listener: (View?, Float) -> Unit) { + onPanelSlide = listener + } + + override fun onPanelSlide(panel: View, slideOffset: Float) { + onPanelSlide?.invoke(panel, slideOffset) + } + + fun onPanelClosed(listener: (View?) -> Unit) { + onPanelClosed = listener + } + + override fun onPanelClosed(panel: View) { + onPanelClosed?.invoke(panel) + } + + fun onPanelOpened(listener: (View?) -> Unit) { + onPanelOpened = listener + } + + override fun onPanelOpened(panel: View) { + onPanelOpened?.invoke(panel) + } + +} + diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SlidingPaneLayoutProperty.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SlidingPaneLayoutProperty.kt new file mode 100644 index 0000000..e20694f --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SlidingPaneLayoutProperty.kt @@ -0,0 +1,33 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.widget + +import androidx.slidingpanelayout.widget.SlidingPaneLayout +import com.github.kittinunf.reactiveandroid.MutableProperty +import com.github.kittinunf.reactiveandroid.createMainThreadMutableProperty + +//================================================================================ +// Properties +//================================================================================ + +val SlidingPaneLayout.rx_coveredFadeColor: MutableProperty + get() { + val getter = { coveredFadeColor } + val setter: (Int) -> Unit = { coveredFadeColor = it } + + return createMainThreadMutableProperty(getter, setter) + } + +val SlidingPaneLayout.rx_parallaxDistance: MutableProperty + get() { + val getter = { parallaxDistance } + val setter: (Int) -> Unit = { parallaxDistance = it } + + return createMainThreadMutableProperty(getter, setter) + } + +val SlidingPaneLayout.rx_sliderFadeColor: MutableProperty + get() { + val getter = { sliderFadeColor } + val setter: (Int) -> Unit = { sliderFadeColor = it } + + return createMainThreadMutableProperty(getter, setter) + } diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SwipeRefreshLayoutEvent.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SwipeRefreshLayoutEvent.kt new file mode 100644 index 0000000..05f62dd --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SwipeRefreshLayoutEvent.kt @@ -0,0 +1,21 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.widget + +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import com.github.kittinunf.reactiveandroid.subscription.AndroidMainThreadSubscription +import io.reactivex.Observable + +//================================================================================ +// Event +//================================================================================ + +fun SwipeRefreshLayout.rx_refresh(): Observable { + return Observable.create { subscriber -> + setOnRefreshListener { -> + subscriber.onNext(Unit) + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + setOnRefreshListener(null) + }) + } +} diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SwipeRefreshLayoutProperty.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SwipeRefreshLayoutProperty.kt new file mode 100644 index 0000000..6d15d99 --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/SwipeRefreshLayoutProperty.kt @@ -0,0 +1,25 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.widget + +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import com.github.kittinunf.reactiveandroid.MutableProperty +import com.github.kittinunf.reactiveandroid.createMainThreadMutableProperty + +//================================================================================ +// Property +//================================================================================ + +val SwipeRefreshLayout.rx_refreshing: MutableProperty + get() { + val getter = { isRefreshing } + val setter: (Boolean) -> Unit = { isRefreshing = it } + + return createMainThreadMutableProperty(getter, setter) + } + +val SwipeRefreshLayout.rx_nestedScrollingEnabled: MutableProperty + get() { + val getter = { isNestedScrollingEnabled } + val setter: (Boolean) -> Unit = { isNestedScrollingEnabled = it } + + return createMainThreadMutableProperty(getter, setter) + } diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/ToolbarEvent.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/ToolbarEvent.kt new file mode 100644 index 0000000..15d88a9 --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/ToolbarEvent.kt @@ -0,0 +1,37 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.widget + +import android.view.MenuItem +import android.view.View +import androidx.appcompat.widget.Toolbar +import com.github.kittinunf.reactiveandroid.subscription.AndroidMainThreadSubscription +import io.reactivex.Observable + +//================================================================================ +// Events +//================================================================================ + +fun Toolbar.rx_navigationClick(): Observable { + return Observable.create { subscriber -> + setNavigationOnClickListener { + subscriber.onNext(it) + + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + setNavigationOnClickListener(null) + }) + } +} + +fun Toolbar.rx_menuItemClick(consumed: Boolean): Observable { + return Observable.create { subscriber -> + setOnMenuItemClickListener { + subscriber.onNext(it) + consumed + } + + subscriber.setDisposable(AndroidMainThreadSubscription { + setOnMenuItemClickListener(null) + }) + } +} diff --git a/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/ToolbarProperty.kt b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/ToolbarProperty.kt new file mode 100644 index 0000000..04f2289 --- /dev/null +++ b/reactiveandroidx-appcompat/src/main/kotlin/com/github/kittinunf/reactiveandroidx/appcompat/widget/ToolbarProperty.kt @@ -0,0 +1,42 @@ +package com.github.kittinunf.reactiveandroidx.appcompat.widget + +import android.graphics.drawable.Drawable +import androidx.appcompat.widget.Toolbar +import com.github.kittinunf.reactiveandroid.MutableProperty +import com.github.kittinunf.reactiveandroid.createMainThreadMutableProperty + +//================================================================================ +// Properties +//================================================================================ + +val Toolbar.rx_logo: MutableProperty + get() { + val getter = { logo } + val setter: (Drawable) -> Unit = { logo = it } + + return createMainThreadMutableProperty(getter, setter) + } + +val Toolbar.rx_navigationIcon: MutableProperty + get() { + val getter = { navigationIcon } + val setter: (Drawable?) -> Unit = { navigationIcon = it } + + return createMainThreadMutableProperty(getter, setter) + } + +val Toolbar.rx_subtitle: MutableProperty + get() { + val getter = { subtitle } + val setter: (CharSequence) -> Unit = { subtitle = it } + + return createMainThreadMutableProperty(getter, setter) + } + +val Toolbar.rx_title: MutableProperty + get() { + val getter = { title } + val setter: (CharSequence) -> Unit = { title = it } + + return createMainThreadMutableProperty(getter, setter) + } diff --git a/sample/build.gradle b/sample/build.gradle index 78f0cdc..d139042 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -40,14 +40,14 @@ android { } dependencies { - compile parent.ext.kotlinStdLib - compile parent.ext.androidAppCompatV7 - - compile project(':reactiveandroid-ui') - compile project(':reactiveandroid-appcompat-v7') - compile project(':reactiveandroid-support-v4') - compile project(':reactiveandroid-design') - compile 'com.android.support.constraint:constraint-layout:1.0.2' + implementation parent.ext.kotlinStdLib + implementation parent.ext.androidAppCompatV7 + + implementation project(':reactiveandroid-ui') + implementation project(':reactiveandroid-appcompat-v7') + implementation project(':reactiveandroid-support-v4') + implementation project(':reactiveandroid-design') + implementation 'com.android.support.constraint:constraint-layout:1.1.3' } buildscript { diff --git a/sample/src/main/java/com/github/kittinunf/reactiveandroid/sample/view/RecyclerViewActivity.kt b/sample/src/main/java/com/github/kittinunf/reactiveandroid/sample/view/RecyclerViewActivity.kt index 67dce46..902856f 100644 --- a/sample/src/main/java/com/github/kittinunf/reactiveandroid/sample/view/RecyclerViewActivity.kt +++ b/sample/src/main/java/com/github/kittinunf/reactiveandroid/sample/view/RecyclerViewActivity.kt @@ -49,14 +49,14 @@ class RecyclerViewActivity : AppCompatActivity() { CountryViewHolder(view).apply { itemView.rx.click() .subscribe { - val mutableList = itemSubject.value.toMutableList() + val mutableList = itemSubject.value?.toMutableList() ?: mutableListOf() mutableList[layoutPosition] = countries.random() itemSubject.onNext(mutableList) } itemView.rx.longClick() .subscribe { - val mutableList = itemSubject.value.toMutableList() + val mutableList = itemSubject.value?.toMutableList() ?: mutableListOf() mutableList.removeAt(layoutPosition) itemSubject.onNext(mutableList) } @@ -76,7 +76,7 @@ class RecyclerViewActivity : AppCompatActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.menu_add -> { - val mutableList = itemSubject.value.toMutableList() + val mutableList = itemSubject.value?.toMutableList() ?: mutableListOf() mutableList.add(countries.random()) itemSubject.onNext(mutableList) diff --git a/settings.gradle b/settings.gradle index 94f00f8..768be0c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,4 +3,5 @@ include ':reactiveandroid', ':reactiveandroid-ui', ':reactiveandroid-support-v4', ':reactiveandroid-appcompat-v7', + ':reactiveandroidx-appcompat', ':sample'