From d91f47dee43eb133b89a28c89f0e0f27f2bced40 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 4 Jan 2023 11:17:30 +0700 Subject: [PATCH 01/31] refactoring for v2 --- .../component/ReactiveActivity.kt | 29 ++-- .../component/ReactiveFragment.kt | 40 ++--- .../component/ReactiveViewModel.kt | 23 +++ .../alexdeww/reactiveviewmodel/core/Const.kt | 4 + .../core/ReactiveViewModel.kt | 150 ------------------ .../core/RvmAutoDisposableSupport.kt | 48 ++++++ .../core/RvmComponentsSupport.kt | 124 +++++++++++++++ .../reactiveviewmodel/core/RvmExtensions.kt | 60 +++++-- .../core/RvmSavedStateDelegates.kt | 31 ++-- .../core/RvmViewComponent.kt | 42 +++-- .../core/RvmViewModelComponent.kt | 78 +++++++++ .../core/RvmWidgetsSupport.kt | 3 + .../core/annotation/RvmBinderDslMarker.kt | 5 + .../core/annotation/RvmDslMarker.kt | 5 + .../core/common/RvmComponent.kt | 42 ----- .../core/property/{Action.kt => RvmAction.kt} | 2 +- ...mationEvent.kt => RvmConfirmationEvent.kt} | 8 +- .../core/property/{Event.kt => RvmEvent.kt} | 2 +- .../property/RvmPropertyReadOnlyDelegate.kt | 10 ++ .../core/property/{State.kt => RvmState.kt} | 2 +- .../reactiveviewmodel/widget/BaseControl.kt | 36 +---- .../widget/BaseVisualControl.kt | 10 +- .../reactiveviewmodel/widget/CheckControl.kt | 26 ++- .../reactiveviewmodel/widget/DialogControl.kt | 6 +- .../widget/DisplayableControl.kt | 3 +- .../reactiveviewmodel/widget/InputControl.kt | 3 +- 26 files changed, 452 insertions(+), 340 deletions(-) create mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt create mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/Const.kt delete mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/ReactiveViewModel.kt create mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmAutoDisposableSupport.kt create mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmComponentsSupport.kt create mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt create mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt create mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/annotation/RvmBinderDslMarker.kt create mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/annotation/RvmDslMarker.kt delete mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/common/RvmComponent.kt rename reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/{Action.kt => RvmAction.kt} (85%) rename reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/{ConfirmationEvent.kt => RvmConfirmationEvent.kt} (88%) rename reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/{Event.kt => RvmEvent.kt} (95%) create mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmPropertyReadOnlyDelegate.kt rename reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/{State.kt => RvmState.kt} (97%) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveActivity.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveActivity.kt index a96d7fa..433b847 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveActivity.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveActivity.kt @@ -3,41 +3,30 @@ package com.alexdeww.reactiveviewmodel.component import androidx.annotation.CallSuper import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.LifecycleOwner +import com.alexdeww.reactiveviewmodel.core.DefaultRvmDisposableStore +import com.alexdeww.reactiveviewmodel.core.RvmAutoDisposableStore import com.alexdeww.reactiveviewmodel.core.RvmViewComponent import io.reactivex.rxjava3.disposables.Disposable -abstract class ReactiveActivity : AppCompatActivity(), RvmViewComponent { +abstract class ReactiveActivity : AppCompatActivity(), RvmViewComponent, + RvmAutoDisposableStore by DefaultRvmDisposableStore() { - private val disposableOnDestroyList = HashMap() - private val disposableOnStopList = HashMap() - - override val componentLifecycleOwner: LifecycleOwner - get() = this + override val componentLifecycleOwner: LifecycleOwner get() = this @CallSuper override fun onStop() { - disposableOnStopList.values.forEach { it.dispose() } - disposableOnStopList.clear() + dispose(RvmViewComponent.onStopStoreKey) super.onStop() } @CallSuper override fun onDestroy() { - disposableOnDestroyList.values.forEach { it.dispose() } - disposableOnDestroyList.clear() + dispose() super.onDestroy() } - override fun Disposable.disposeOnDestroy(tag: String) { - disposableOnDestroyList.put(tag, this)?.dispose() - } - - override fun Disposable.disposeOnStop(tag: String) { - disposableOnStopList.put(tag, this)?.dispose() - } - override fun Disposable.disposeOnDestroyView(tag: String) { - disposableOnDestroyList.put("dv-$tag", this)?.dispose() + autoDispose("dv-$tag", RvmViewComponent.onDestroyViewStoreKey) } -} \ No newline at end of file +} diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveFragment.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveFragment.kt index 11869ca..df261c8 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveFragment.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveFragment.kt @@ -4,53 +4,33 @@ import androidx.annotation.CallSuper import androidx.annotation.LayoutRes import androidx.fragment.app.Fragment import androidx.lifecycle.LifecycleOwner +import com.alexdeww.reactiveviewmodel.core.DefaultRvmDisposableStore +import com.alexdeww.reactiveviewmodel.core.RvmAutoDisposableStore import com.alexdeww.reactiveviewmodel.core.RvmViewComponent -import io.reactivex.rxjava3.disposables.Disposable -abstract class ReactiveFragment : Fragment, RvmViewComponent { +abstract class ReactiveFragment( + @LayoutRes layoutId: Int = 0 +) : Fragment(layoutId), RvmViewComponent, + RvmAutoDisposableStore by DefaultRvmDisposableStore() { - constructor() : super() - - constructor(@LayoutRes layoutId: Int) : super(layoutId) - - private val disposableOnDestroyList = HashMap() - private val disposableOnStopList = HashMap() - private val disposableOnDestroyViewList = HashMap() - - override val componentLifecycleOwner: LifecycleOwner - get() = viewLifecycleOwner + override val componentLifecycleOwner: LifecycleOwner get() = viewLifecycleOwner @CallSuper override fun onStop() { - disposableOnStopList.values.forEach { it.dispose() } - disposableOnStopList.clear() + dispose(RvmViewComponent.onStopStoreKey) super.onStop() } @CallSuper override fun onDestroyView() { - disposableOnDestroyViewList.values.forEach { it.dispose() } - disposableOnDestroyViewList.clear() + dispose(RvmViewComponent.onDestroyViewStoreKey) super.onDestroyView() } @CallSuper override fun onDestroy() { - disposableOnDestroyList.values.forEach { it.dispose() } - disposableOnDestroyList.clear() + dispose() super.onDestroy() } - override fun Disposable.disposeOnDestroy(tag: String) { - disposableOnDestroyList.put(tag, this)?.dispose() - } - - override fun Disposable.disposeOnStop(tag: String) { - disposableOnStopList.put(tag, this)?.dispose() - } - - override fun Disposable.disposeOnDestroyView(tag: String) { - disposableOnDestroyViewList.put(tag, this)?.dispose() - } - } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt new file mode 100644 index 0000000..7718807 --- /dev/null +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt @@ -0,0 +1,23 @@ +package com.alexdeww.reactiveviewmodel.component + +import androidx.annotation.CallSuper +import androidx.lifecycle.ViewModel +import com.alexdeww.reactiveviewmodel.core.DefaultRvmDisposableStore +import com.alexdeww.reactiveviewmodel.core.RvmAutoDisposableStore +import com.alexdeww.reactiveviewmodel.core.RvmViewModelComponent + +/** + * Based on RxPM + * https://github.com/dmdevgo/RxPM + */ + +abstract class ReactiveViewModel : ViewModel(), RvmViewModelComponent, + RvmAutoDisposableStore by DefaultRvmDisposableStore() { + + @CallSuper + override fun onCleared() { + dispose() + super.onCleared() + } + +} diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/Const.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/Const.kt new file mode 100644 index 0000000..f2aa7d4 --- /dev/null +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/Const.kt @@ -0,0 +1,4 @@ +package com.alexdeww.reactiveviewmodel.core + +const val DEF_PROGRESS_DEBOUNCE_INTERVAL = 500L //ms +const val DEF_ACTION_DEBOUNCE_INTERVAL = 300L //ms diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/ReactiveViewModel.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/ReactiveViewModel.kt deleted file mode 100644 index 4e134e6..0000000 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/ReactiveViewModel.kt +++ /dev/null @@ -1,150 +0,0 @@ -package com.alexdeww.reactiveviewmodel.core - -import androidx.lifecycle.ViewModel -import com.alexdeww.reactiveviewmodel.core.common.RvmComponent -import com.alexdeww.reactiveviewmodel.core.property.Action -import com.alexdeww.reactiveviewmodel.core.property.ConfirmationEvent -import com.alexdeww.reactiveviewmodel.core.property.Event -import com.alexdeww.reactiveviewmodel.core.property.State -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Maybe -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.disposables.CompositeDisposable -import io.reactivex.rxjava3.disposables.Disposable -import io.reactivex.rxjava3.subjects.BehaviorSubject - -/** - * Based on RxPM - * https://github.com/dmdevgo/RxPM - */ - -const val DEF_PROGRESS_DEBOUNCE_INTERVAL = 500L //ms -const val DEF_ACTION_DEBOUNCE_INTERVAL = 300L //ms - -abstract class ReactiveViewModel : ViewModel(), RvmComponent { - - protected interface Invocable { - val isExecute: Boolean - val isExecuteObservable: Observable - operator fun invoke(params: T) - } - - private val disposableList = CompositeDisposable() - - override fun onCleared() { - disposableList.clear() - super.onCleared() - } - - fun Disposable.disposeOnCleared(): Disposable { - disposableList.add(this) - return this - } - - protected fun state( - initValue: T? = null, - debounceInterval: Long? = null - ): State = State(initValue, debounceInterval) - - protected fun progressState( - initValue: Boolean? = null, - debounceInterval: Long = DEF_PROGRESS_DEBOUNCE_INTERVAL - ): State = state(initValue, debounceInterval) - - protected fun event(debounceInterval: Long? = null): Event = - Event(debounceInterval) - - protected fun eventNone(debounceInterval: Long? = null): Event = - event(debounceInterval) - - protected fun confirmationEvent( - debounceInterval: Long? = null - ): ConfirmationEvent = ConfirmationEvent(debounceInterval) - - protected fun confirmationEventNone( - debounceInterval: Long? = null - ): ConfirmationEvent = confirmationEvent(debounceInterval) - - protected fun action(debounceInterval: Long? = null): Action = - Action(debounceInterval) - - protected fun actionNone(debounceInterval: Long? = null): Action = - action(debounceInterval) - - protected fun debouncedAction( - debounceInterval: Long = DEF_ACTION_DEBOUNCE_INTERVAL - ): Action = action(debounceInterval) - - protected fun debouncedActionNone( - debounceInterval: Long = DEF_ACTION_DEBOUNCE_INTERVAL - ): Action = action(debounceInterval) - - protected fun Action.bind( - transformChainBlock: Observable.() -> Observable - ) { - observable - .transformChainBlock() - .applyDefaultErrorHandler() - .retry() - .subscribe() - .disposeOnCleared() - } - - protected fun State.bind( - transformChainBlock: Observable.() -> Observable - ) { - observable - .transformChainBlock() - .applyDefaultErrorHandler() - .retry() - .subscribe() - .disposeOnCleared() - } - - protected fun invocable( - block: (params: T) -> Completable - ): Lazy> = lazy { - val action = action() - val isExecuteSubj: BehaviorSubject = BehaviorSubject.createDefault(false) - action.bind { - this.switchMapCompletable { params -> block(params).bindProgress(isExecuteSubj::onNext) } - .toObservable() - } - object : Invocable { - override val isExecute: Boolean get() = isExecuteSubj.value ?: false - override val isExecuteObservable: Observable = isExecuteSubj.serialize() - - override fun invoke(params: T) = action.consumer.accept(params) - } - } - - protected fun Observable.untilOn(vararg action: Action): Observable = - takeUntil(Observable.merge(action.map { it.observable })) - - protected fun Observable.untilOn(vararg event: Event): Observable = - takeUntil(Observable.merge(event.map { it.observable })) - - protected fun Observable.untilOn(vararg observable: Observable<*>): Observable = - takeUntil(Observable.merge(observable.toList())) - - protected fun Maybe.untilOn(vararg action: Action<*>): Maybe = - takeUntil(Maybe.merge(action.map { it.observable.firstElement() })) - - protected fun Maybe.untilOn(vararg event: Event<*>): Maybe = - takeUntil(Maybe.merge(event.map { it.observable.firstElement() })) - - protected fun Maybe.untilOn(vararg maybe: Maybe<*>): Maybe = - takeUntil(Maybe.merge(maybe.toList())) - - protected fun Completable.untilOn(vararg action: Action<*>): Completable = - takeUntil(Completable.merge(action.map { it.observable.firstElement().ignoreElement() })) - - protected fun Completable.untilOn(vararg event: Event<*>): Completable = - takeUntil(Completable.merge(event.map { it.observable.firstElement().ignoreElement() })) - - protected fun Completable.untilOn(vararg completable: Completable): Completable = - takeUntil(Completable.merge(completable.toList())) - - protected open fun Observable.applyDefaultErrorHandler(): Observable = this - -} diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmAutoDisposableSupport.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmAutoDisposableSupport.kt new file mode 100644 index 0000000..f5c4555 --- /dev/null +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmAutoDisposableSupport.kt @@ -0,0 +1,48 @@ +package com.alexdeww.reactiveviewmodel.core + +import io.reactivex.rxjava3.disposables.Disposable +import java.util.* + +interface RvmAutoDisposableSupport { + + data class StoreKey(val name: String) { + init { + check(name.isNotBlank()) { "Name can`t be blank" } + } + } + + fun Disposable.autoDispose( + tagKey: String = UUID.randomUUID().toString(), + storeKey: StoreKey? = null + ) + +} + +interface RvmAutoDisposableStore : RvmAutoDisposableSupport { + fun dispose(storeKey: RvmAutoDisposableSupport.StoreKey? = null) +} + +class DefaultRvmDisposableStore : RvmAutoDisposableStore { + + private val disposablesStore = + hashMapOf>() + + override fun dispose(storeKey: RvmAutoDisposableSupport.StoreKey?) { + if (storeKey == null) { + disposablesStore.entries.forEach { it.value.disposeAndClear() } + disposablesStore.clear() + } else { + disposablesStore[storeKey]?.disposeAndClear() + } + } + + override fun Disposable.autoDispose(tagKey: String, storeKey: RvmAutoDisposableSupport.StoreKey?) { + disposablesStore.getOrPut(storeKey) { hashMapOf() }.put(tagKey, this)?.dispose() + } + + private fun HashMap.disposeAndClear() { + values.forEach { it.dispose() } + clear() + } + +} diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmComponentsSupport.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmComponentsSupport.kt new file mode 100644 index 0000000..63cecec --- /dev/null +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmComponentsSupport.kt @@ -0,0 +1,124 @@ +package com.alexdeww.reactiveviewmodel.core + +import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker +import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker +import com.alexdeww.reactiveviewmodel.core.property.* +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.functions.Consumer +import java.util.* +import kotlin.properties.ReadOnlyProperty + +@RvmDslMarker +@RvmBinderDslMarker +interface RvmComponentsSupport { + + // State + val RvmState.consumer: Consumer get() = this.consumer + val RvmState.observable: Observable get() = this.observable + fun RvmState.setValue(value: T) = consumer.accept(value) + fun RvmState.setValueIfChanged(value: T) { + if (this.value != value) setValue(value) + } + + + // Event + val RvmEvent.consumer: Consumer get() = this.consumer + val RvmEvent.observable: Observable get() = this.observable + fun RvmEvent.call(value: T) = this.consumer.accept(value) + fun RvmEvent.call() = call(Unit) + + + // ConfirmationEvent + val RvmConfirmationEvent.consumer: Consumer get() = this.consumer + val RvmConfirmationEvent.observable: Observable get() = this.observable + fun RvmConfirmationEvent.call(value: T) = this.consumer.accept(value) + fun RvmConfirmationEvent.call() = call(Unit) + + + // Action + val RvmAction.observable: Observable get() = this.observable + +} + +@Suppress("unused") +@RvmDslMarker +fun RvmComponentsSupport.state( + initValue: T? = null, + debounceInterval: Long? = null +): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( + property = RvmState(initValue, debounceInterval) +) + +@Suppress("unused") +@RvmDslMarker +fun RvmComponentsSupport.progressState( + initValue: Boolean? = null, + debounceInterval: Long = DEF_PROGRESS_DEBOUNCE_INTERVAL +): ReadOnlyProperty> = state( + initValue = initValue, + debounceInterval = debounceInterval +) + +@Suppress("unused") +@RvmDslMarker +fun RvmComponentsSupport.event( + debounceInterval: Long? = null +): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( + property = RvmEvent(debounceInterval) +) + +@Suppress("unused") +@RvmDslMarker +fun RvmComponentsSupport.eventNone( + debounceInterval: Long? = null +): ReadOnlyProperty> = event( + debounceInterval = debounceInterval +) + +@Suppress("unused") +@RvmDslMarker +fun RvmComponentsSupport.confirmationEvent( + debounceInterval: Long? = null +): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( + property = RvmConfirmationEvent(debounceInterval) +) + +@Suppress("unused") +@RvmDslMarker +fun RvmComponentsSupport.confirmationEventNone( + debounceInterval: Long? = null +): ReadOnlyProperty> = confirmationEvent( + debounceInterval = debounceInterval +) + +@Suppress("unused") +@RvmDslMarker +fun RvmComponentsSupport.action( + debounceInterval: Long? = null +): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( + property = RvmAction(debounceInterval) +) + +@Suppress("unused") +@RvmDslMarker +fun RvmComponentsSupport.actionNone( + debounceInterval: Long? = null +): ReadOnlyProperty> = action( + debounceInterval = debounceInterval +) + +@Suppress("unused") +@RvmDslMarker +fun RvmComponentsSupport.debouncedAction( + debounceInterval: Long = DEF_ACTION_DEBOUNCE_INTERVAL +): ReadOnlyProperty> = action( + debounceInterval = debounceInterval +) + +@Suppress("unused") +@RvmDslMarker +fun RvmComponentsSupport.debouncedActionNone( + debounceInterval: Long = DEF_ACTION_DEBOUNCE_INTERVAL +): ReadOnlyProperty> = action( + debounceInterval = debounceInterval +) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmExtensions.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmExtensions.kt index 89ec2d4..6ef630f 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmExtensions.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmExtensions.kt @@ -4,10 +4,10 @@ import android.view.View import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.Observer -import com.alexdeww.reactiveviewmodel.core.property.Action -import com.alexdeww.reactiveviewmodel.core.property.ConfirmationEvent -import com.alexdeww.reactiveviewmodel.core.property.Event -import com.alexdeww.reactiveviewmodel.core.property.State +import com.alexdeww.reactiveviewmodel.core.property.RvmAction +import com.alexdeww.reactiveviewmodel.core.property.RvmConfirmationEvent +import com.alexdeww.reactiveviewmodel.core.property.RvmEvent +import com.alexdeww.reactiveviewmodel.core.property.RvmState import io.reactivex.rxjava3.core.* import io.reactivex.rxjava3.functions.Consumer import io.reactivex.rxjava3.functions.Function @@ -21,33 +21,33 @@ fun LiveData.observe(owner: LifecycleOwner, action: OnLiveDataAction): return observer } -fun Event.observe( +fun RvmEvent.observe( owner: LifecycleOwner, action: OnLiveDataAction ): Observer = liveData.observe(owner = owner, action = action) -fun ConfirmationEvent.observe( +fun RvmConfirmationEvent.observe( owner: LifecycleOwner, action: OnLiveDataAction ): Observer = liveData.observe(owner = owner, action = action) -fun State.observe( +fun RvmState.observe( owner: LifecycleOwner, action: OnLiveDataAction ): Observer = liveData.observe(owner = owner, action = action) -fun Action.call() = call(Unit) +fun RvmAction.call() = call(Unit) typealias ActionOnClick = () -> Unit -fun Action.bindOnClick(view: View, value: T, onClickAction: ActionOnClick? = null) { +fun RvmAction.bindOnClick(view: View, value: T, onClickAction: ActionOnClick? = null) { view.setOnClickListener { call(value) onClickAction?.invoke() } } -fun Action.bindOnClick(view: View, onClickAction: ActionOnClick? = null) { +fun RvmAction.bindOnClick(view: View, onClickAction: ActionOnClick? = null) { bindOnClick(view, Unit, onClickAction) } @@ -131,6 +131,46 @@ fun Completable.bindProgressAny(progressConsumer: Consumer): Completabl .doOnDispose { consumerWrapper.end() } } +fun Observable.untilOn( + vararg rvmAction: RvmAction +): Observable = takeUntil(Observable.merge(rvmAction.map { it.observable })) + +fun Observable.untilOn( + vararg event: RvmEvent +): Observable = takeUntil(Observable.merge(event.map { it.observable })) + +fun Observable.untilOn( + vararg observable: Observable<*> +): Observable = takeUntil(Observable.merge(observable.toList())) + +fun Maybe.untilOn( + vararg action: RvmAction<*> +): Maybe = takeUntil(Maybe.merge(action.map { it.observable.firstElement() })) + +fun Maybe.untilOn( + vararg event: RvmEvent<*> +): Maybe = takeUntil(Maybe.merge(event.map { it.observable.firstElement() })) + +fun Maybe.untilOn( + vararg maybe: Maybe<*> +): Maybe = takeUntil(Maybe.merge(maybe.toList())) + +fun Completable.untilOn( + vararg action: RvmAction<*> +): Completable = takeUntil(Completable.merge(action.map { + it.observable.firstElement().ignoreElement() +})) + +fun Completable.untilOn( + vararg event: RvmEvent<*> +): Completable = takeUntil(Completable.merge(event.map { + it.observable.firstElement().ignoreElement() +})) + +fun Completable.untilOn( + vararg completable: Completable +): Completable = takeUntil(Completable.merge(completable.toList())) + /** * Returns the [Observable] that emits items when active, and buffers them when [idle][isIdle]. * Buffered items is emitted when idle state ends. diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmSavedStateDelegates.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmSavedStateDelegates.kt index a1e9389..0338935 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmSavedStateDelegates.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmSavedStateDelegates.kt @@ -1,7 +1,8 @@ package com.alexdeww.reactiveviewmodel.core import androidx.lifecycle.SavedStateHandle -import com.alexdeww.reactiveviewmodel.core.property.State +import com.alexdeww.reactiveviewmodel.component.ReactiveViewModel +import com.alexdeww.reactiveviewmodel.core.property.RvmState import com.alexdeww.reactiveviewmodel.widget.* import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadWriteProperty @@ -27,9 +28,9 @@ fun SavedStateHandle.valueNonNull( fun SavedStateHandle.state( initialValue: T? = null, debounceInterval: Long? = null -): ReadOnlyProperty> = delegate { thisRef, stateHandle, key -> - val state = State(stateHandle[key] ?: initialValue, debounceInterval) - thisRef.run { state.viewFlowable.subscribe { stateHandle[key] = it }.disposeOnCleared() } +): ReadOnlyProperty> = delegate { thisRef, stateHandle, key -> + val state = RvmState(stateHandle[key] ?: initialValue, debounceInterval) + thisRef.run { state.viewFlowable.subscribe { stateHandle[key] = it }.autoDispose() } state } @@ -54,16 +55,16 @@ fun SavedStateHandle.inputControl( thisRef.run { control.value.viewFlowable .subscribe { stateHandle[textKey] = it } - .disposeOnCleared() + .autoDispose() control.error.viewFlowable .subscribe { stateHandle[errorKey] = it } - .disposeOnCleared() + .autoDispose() control.enabled.viewFlowable .subscribe { stateHandle[enabledKey] = it } - .disposeOnCleared() + .autoDispose() control.visibility.viewFlowable .subscribe { stateHandle[visibilityKey] = it } - .disposeOnCleared() + .autoDispose() } control } @@ -84,13 +85,13 @@ fun SavedStateHandle.ratingControl( thisRef.run { control.value.viewFlowable .subscribe { stateHandle[ratingKey] = it } - .disposeOnCleared() + .autoDispose() control.enabled.viewFlowable .subscribe { stateHandle[enabledKey] = it } - .disposeOnCleared() + .autoDispose() control.visibility.viewFlowable .subscribe { stateHandle[visibilityKey] = it } - .disposeOnCleared() + .autoDispose() } control } @@ -105,7 +106,7 @@ fun SavedStateHandle.displayableControl( control.action.setValue(stateHandle[actionKey] ?: DisplayableControl.Action.Hide) control.action.viewFlowable .subscribe { stateHandle[actionKey] = it } - .disposeOnCleared() + .autoDispose() } control } @@ -126,13 +127,13 @@ fun SavedStateHandle.checkControl( thisRef.run { control.value.viewFlowable .subscribe { stateHandle[checkedKey] = it } - .disposeOnCleared() + .autoDispose() control.enabled.viewFlowable .subscribe { stateHandle[enabledKey] = it } - .disposeOnCleared() + .autoDispose() control.visibility.viewFlowable .subscribe { stateHandle[visibilityKey] = it } - .disposeOnCleared() + .autoDispose() } control } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt index f20e74d..bbe4abd 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt @@ -7,34 +7,42 @@ import android.widget.RatingBar import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.Observer -import com.alexdeww.reactiveviewmodel.core.property.ConfirmationEvent -import com.alexdeww.reactiveviewmodel.core.property.Event -import com.alexdeww.reactiveviewmodel.core.property.State +import com.alexdeww.reactiveviewmodel.core.RvmAutoDisposableSupport.StoreKey +import com.alexdeww.reactiveviewmodel.core.property.RvmConfirmationEvent +import com.alexdeww.reactiveviewmodel.core.property.RvmEvent +import com.alexdeww.reactiveviewmodel.core.property.RvmState import com.alexdeww.reactiveviewmodel.widget.* import com.google.android.material.textfield.TextInputLayout import io.reactivex.rxjava3.disposables.Disposable -interface RvmViewComponent { +interface RvmViewComponent : RvmAutoDisposableSupport { - val componentLifecycleOwner: LifecycleOwner - - fun Disposable.disposeOnDestroy(tag: String) + companion object { + val onStopStoreKey = StoreKey("RvmViewComponent.onStopStoreKey") + val onDestroyViewStoreKey = StoreKey("RvmViewComponent.onDestroyViewStoreKey") + } - fun Disposable.disposeOnStop(tag: String) + val componentLifecycleOwner: LifecycleOwner - fun Disposable.disposeOnDestroyView(tag: String) + fun Disposable.disposeOnStop(tag: String) = autoDispose(tag, onStopStoreKey) + fun Disposable.disposeOnDestroyView(tag: String) = autoDispose(tag, onDestroyViewStoreKey) + fun Disposable.disposeOnDestroy(tag: String) = autoDispose(tag) - fun LiveData.observe(action: OnLiveDataAction): Observer = - observe(owner = componentLifecycleOwner, action = action) + fun LiveData.observe( + action: OnLiveDataAction + ): Observer = observe(owner = componentLifecycleOwner, action = action) - fun State.observe(action: OnLiveDataAction): Observer = - observe(componentLifecycleOwner, action) + fun RvmState.observe( + action: OnLiveDataAction + ): Observer = observe(componentLifecycleOwner, action) - fun Event.observe(action: OnLiveDataAction): Observer = - observe(componentLifecycleOwner, action) + fun RvmEvent.observe( + action: OnLiveDataAction + ): Observer = observe(componentLifecycleOwner, action) - fun ConfirmationEvent.observe(action: OnLiveDataAction): Observer = - observe(componentLifecycleOwner, action) + fun RvmConfirmationEvent.observe( + action: OnLiveDataAction + ): Observer = observe(componentLifecycleOwner, action) fun DisplayableControl.observe( action: DisplayableAction diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt new file mode 100644 index 0000000..0733a8a --- /dev/null +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt @@ -0,0 +1,78 @@ +package com.alexdeww.reactiveviewmodel.core + +import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker +import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker +import com.alexdeww.reactiveviewmodel.core.property.RvmAction +import com.alexdeww.reactiveviewmodel.core.property.RvmState +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.subjects.BehaviorSubject + +@RvmDslMarker +@RvmBinderDslMarker +interface RvmViewModelComponent : RvmComponentsSupport, RvmWidgetsSupport, + RvmAutoDisposableSupport { + + interface Invocable { + val isExecute: Boolean + val isExecuteObservable: Observable + operator fun invoke(params: T) + } + + // Bind Logic + fun Observable.applyDefaultErrorHandler(): Observable = this + + @RvmBinderDslMarker + infix fun RvmAction.bind( + transformChainBlock: Observable.() -> Observable + ) = bindAction(this, transformChainBlock) + + @RvmBinderDslMarker + infix fun RvmState.bind( + transformChainBlock: Observable.() -> Observable + ) = bindState(this, transformChainBlock) + +} + +@Suppress("unused") +@RvmDslMarker +fun RvmViewModelComponent.invocable( + block: (params: T) -> Completable +): Lazy> = lazy { + val action by action() + val isExecuteSubj: BehaviorSubject = BehaviorSubject.createDefault(false) + action bind { + this.switchMapCompletable { params -> block(params).bindProgress(isExecuteSubj::onNext) } + .toObservable() + } + object : RvmViewModelComponent.Invocable { + override val isExecute: Boolean get() = isExecuteSubj.value ?: false + override val isExecuteObservable: Observable = isExecuteSubj.serialize() + override fun invoke(params: T) = action.consumer.accept(params) + } +} + +internal fun RvmViewModelComponent.bindAction( + action: RvmAction, + transformChainBlock: Observable.() -> Observable +) { + action.observable + .transformChainBlock() + .applyDefaultErrorHandler() + .retry() + .subscribe() + .autoDispose() +} + +internal fun RvmViewModelComponent.bindState( + state: RvmState, + transformChainBlock: Observable.() -> Observable +) { + state.observable + .transformChainBlock() + .applyDefaultErrorHandler() + .retry() + .subscribe() + .autoDispose() +} + diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt new file mode 100644 index 0000000..652c59d --- /dev/null +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt @@ -0,0 +1,3 @@ +package com.alexdeww.reactiveviewmodel.core + +interface RvmWidgetsSupport diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/annotation/RvmBinderDslMarker.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/annotation/RvmBinderDslMarker.kt new file mode 100644 index 0000000..053569e --- /dev/null +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/annotation/RvmBinderDslMarker.kt @@ -0,0 +1,5 @@ +package com.alexdeww.reactiveviewmodel.core.annotation + +@DslMarker +@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPEALIAS, AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) +annotation class RvmBinderDslMarker diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/annotation/RvmDslMarker.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/annotation/RvmDslMarker.kt new file mode 100644 index 0000000..35adf96 --- /dev/null +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/annotation/RvmDslMarker.kt @@ -0,0 +1,5 @@ +package com.alexdeww.reactiveviewmodel.core.annotation + +@DslMarker +@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPEALIAS, AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) +annotation class RvmDslMarker diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/common/RvmComponent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/common/RvmComponent.kt deleted file mode 100644 index c3e4706..0000000 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/common/RvmComponent.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.alexdeww.reactiveviewmodel.core.common - -import com.alexdeww.reactiveviewmodel.core.property.Action -import com.alexdeww.reactiveviewmodel.core.property.ConfirmationEvent -import com.alexdeww.reactiveviewmodel.core.property.Event -import com.alexdeww.reactiveviewmodel.core.property.State -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.functions.Consumer - -interface RvmComponent { - - fun State.setValue(value: T) { - this.consumer.accept(value) - } - - fun State.setValueIfChanged(value: T) { - if (this.value != value) this.consumer.accept(value) - } - - val State.consumer: Consumer get() = this.consumer - - val State.observable: Observable get() = this.observable - - val Action.observable: Observable get() = this.observable - - fun Event.call(value: T) = this.consumer.accept(value) - - fun Event.call() = this.consumer.accept(Unit) - - val Event.consumer: Consumer get() = this.consumer - - val Event.observable: Observable get() = this.observable - - fun ConfirmationEvent.call(value: T) = this.consumer.accept(value) - - fun ConfirmationEvent.call() = this.consumer.accept(Unit) - - val ConfirmationEvent.consumer: Consumer get() = this.consumer - - val ConfirmationEvent.observable: Observable get() = this.observable - -} diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/Action.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmAction.kt similarity index 85% rename from reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/Action.kt rename to reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmAction.kt index 098b0eb..10694a5 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/Action.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmAction.kt @@ -4,7 +4,7 @@ import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.functions.Consumer import io.reactivex.rxjava3.subjects.PublishSubject -class Action internal constructor(debounceInterval: Long? = null) { +class RvmAction internal constructor(debounceInterval: Long? = null) { private val subject = PublishSubject.create().toSerialized() diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/ConfirmationEvent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmConfirmationEvent.kt similarity index 88% rename from reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/ConfirmationEvent.kt rename to reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmConfirmationEvent.kt index 3386f8d..0170f14 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/ConfirmationEvent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmConfirmationEvent.kt @@ -6,12 +6,12 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.Observer -import com.alexdeww.reactiveviewmodel.core.property.ConfirmationEvent.ObserverWrapper +import com.alexdeww.reactiveviewmodel.core.property.RvmConfirmationEvent.ObserverWrapper import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.functions.Consumer -class ConfirmationEvent internal constructor(debounceInterval: Long? = null) { +class RvmConfirmationEvent internal constructor(debounceInterval: Long? = null) { private sealed class EventType { data class Pending(val data: Any) : EventType() @@ -21,7 +21,7 @@ class ConfirmationEvent internal constructor(debounceInterval: Long? = fun tryGetData(): T? = if (this is Pending) data as T else null } - private val eventState = State(EventType.Confirmed, debounceInterval) + private val eventState = RvmState(EventType.Confirmed, debounceInterval) internal val consumer: Consumer = Consumer { eventState.consumer.accept(EventType.Pending(it)) @@ -42,7 +42,7 @@ class ConfirmationEvent internal constructor(debounceInterval: Long? = private inner class ConfirmationEventLiveData : MediatorLiveData() { - private val observers = ArraySet>() + private val observers = ArraySet>() @MainThread override fun observe(owner: LifecycleOwner, observer: Observer) { diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/Event.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmEvent.kt similarity index 95% rename from reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/Event.kt rename to reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmEvent.kt index 4be67c3..46b4dc7 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/Event.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmEvent.kt @@ -9,7 +9,7 @@ import io.reactivex.rxjava3.functions.Consumer import io.reactivex.rxjava3.subjects.BehaviorSubject import java.util.concurrent.atomic.AtomicBoolean -class Event internal constructor(debounceInterval: Long? = null) { +class RvmEvent internal constructor(debounceInterval: Long? = null) { private val subject = BehaviorSubject.create() private val serializedSubject = subject.toSerialized() diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmPropertyReadOnlyDelegate.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmPropertyReadOnlyDelegate.kt new file mode 100644 index 0000000..872c978 --- /dev/null +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmPropertyReadOnlyDelegate.kt @@ -0,0 +1,10 @@ +package com.alexdeww.reactiveviewmodel.core.property + +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +class RvmPropertyReadOnlyDelegate

( + private val property: P +) : ReadOnlyProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): P = this.property +} diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/State.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt similarity index 97% rename from reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/State.kt rename to reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt index a459b3e..2fb223b 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/State.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt @@ -7,7 +7,7 @@ import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.functions.Consumer import io.reactivex.rxjava3.subjects.BehaviorSubject -class State internal constructor( +class RvmState internal constructor( initValue: T? = null, debounceInterval: Long? = null ) { diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt index 5a4e0a3..6c66dd2 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt @@ -1,36 +1,6 @@ package com.alexdeww.reactiveviewmodel.widget -import com.alexdeww.reactiveviewmodel.core.common.RvmComponent -import com.alexdeww.reactiveviewmodel.core.property.Action -import com.alexdeww.reactiveviewmodel.core.property.ConfirmationEvent -import com.alexdeww.reactiveviewmodel.core.property.Event -import com.alexdeww.reactiveviewmodel.core.property.State +import com.alexdeww.reactiveviewmodel.core.RvmComponentsSupport -abstract class BaseControl : RvmComponent { - - protected fun state( - initValue: T? = null, - debounceInterval: Long? = null - ): State = State(initValue, debounceInterval) - - protected fun event(debounceInterval: Long? = null): Event = - Event(debounceInterval) - - protected fun eventNone(debounceInterval: Long? = null): Event = - event(debounceInterval) - - protected fun action(debounceInterval: Long? = null): Action = - Action(debounceInterval) - - protected fun actionNone(debounceInterval: Long? = null): Action = - action(debounceInterval) - - protected fun confirmationEvent( - debounceInterval: Long? = null - ): ConfirmationEvent = ConfirmationEvent(debounceInterval) - - protected fun confirmationEventNone( - debounceInterval: Long? = null - ): ConfirmationEvent = confirmationEvent(debounceInterval) - -} +@Suppress("UnnecessaryAbstractClass") +abstract class BaseControl : RvmComponentsSupport diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt index 469a25e..58c3028 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt @@ -4,6 +4,8 @@ import android.view.View import androidx.annotation.CallSuper import androidx.lifecycle.MediatorLiveData import com.alexdeww.reactiveviewmodel.core.RvmViewComponent +import com.alexdeww.reactiveviewmodel.core.action +import com.alexdeww.reactiveviewmodel.core.state import io.reactivex.rxjava3.functions.Consumer import java.lang.ref.WeakReference @@ -19,11 +21,11 @@ abstract class BaseVisualControl( GONE(View.GONE) } - val value = state(initialValue) - val enabled = state(initialEnabled) - val visibility = state(initialVisibility) + val value by state(initialValue) + val enabled by state(initialEnabled) + val visibility by state(initialVisibility) - val actionChangeValue = action() + val actionChangeValue by action() init { actionChangeValue.observable diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt index cf89f44..25f9ba4 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt @@ -1,24 +1,34 @@ package com.alexdeww.reactiveviewmodel.widget -import android.annotation.SuppressLint import android.widget.CompoundButton import com.alexdeww.reactiveviewmodel.core.RvmViewComponent +import com.alexdeww.reactiveviewmodel.core.RvmWidgetsSupport +import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker +import com.alexdeww.reactiveviewmodel.core.property.RvmPropertyReadOnlyDelegate +import kotlin.properties.ReadOnlyProperty -@SuppressLint("CheckResult") class CheckControl internal constructor( initialChecked: Boolean, initialEnabled: Boolean, initialVisibility: Visibility -) : BaseVisualControl(initialChecked, initialEnabled, initialVisibility) +) : BaseVisualControl( + initialChecked, + initialEnabled, + initialVisibility +) -fun checkControl( +@Suppress("unused") +@RvmDslMarker +fun RvmWidgetsSupport.checkControl( initialChecked: Boolean = false, initialEnabled: Boolean = true, initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): CheckControl = CheckControl( - initialChecked = initialChecked, - initialEnabled = initialEnabled, - initialVisibility = initialVisibility +): ReadOnlyProperty = RvmPropertyReadOnlyDelegate( + property = CheckControl( + initialChecked = initialChecked, + initialEnabled = initialEnabled, + initialVisibility = initialVisibility + ) ) fun CheckControl.bindTo( diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt index e1762ce..abd4920 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt @@ -3,6 +3,8 @@ package com.alexdeww.reactiveviewmodel.widget import android.app.Dialog import androidx.lifecycle.MediatorLiveData import com.alexdeww.reactiveviewmodel.core.RvmViewComponent +import com.alexdeww.reactiveviewmodel.core.action +import com.alexdeww.reactiveviewmodel.core.state import io.reactivex.rxjava3.core.Maybe sealed class DialogResult { @@ -17,9 +19,9 @@ class DialogControl internal constructor() : BaseControl() { object Absent : Display() } - internal val result = action() + internal val result by action() - val displayed = state>(Display.Absent) + val displayed by state>(Display.Absent) val isShowing get() = displayed.value is Display.Displayed fun show(data: T) { diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt index 323c508..d6c5033 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt @@ -3,6 +3,7 @@ package com.alexdeww.reactiveviewmodel.widget import android.os.Parcelable import androidx.lifecycle.Observer import com.alexdeww.reactiveviewmodel.core.RvmViewComponent +import com.alexdeww.reactiveviewmodel.core.state import kotlinx.parcelize.Parcelize import kotlinx.parcelize.RawValue @@ -22,7 +23,7 @@ class DisplayableControl internal constructor( fun getShowingValue(): T? = (this as? Show)?.data } - val action = state>(Action.Hide, debounceInterval) + val action by state>(Action.Hide, debounceInterval) val isShowing get() = action.value?.isShowing ?: false val showingValue: T? get() = action.value?.getShowingValue() diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt index 8b2b636..9e6e91d 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt @@ -4,6 +4,7 @@ import android.text.* import android.view.View import android.widget.EditText import com.alexdeww.reactiveviewmodel.core.RvmViewComponent +import com.alexdeww.reactiveviewmodel.core.state import com.google.android.material.textfield.TextInputLayout typealias FormatterAction = (text: String) -> String @@ -20,7 +21,7 @@ class InputControl internal constructor( value.valueChangesHook = formatter } - val error = state() + val error by state() override fun onChangedValue(newValue: String) { if (hideErrorOnUserInput) error.consumer.accept("") From 5c84d2b2fa43eb864c2d5a164a8bbe2c2e84edcc Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 4 Jan 2023 15:12:21 +0700 Subject: [PATCH 02/31] add new view binder --- .../core/RvmComponentsSupport.kt | 26 ++- .../core/RvmSavedStateDelegates.kt | 126 +---------- .../core/RvmViewComponent.kt | 79 +------ .../core/RvmViewModelComponent.kt | 4 +- .../core/RvmWidgetsSupport.kt | 2 +- .../property/RvmPropertyReadOnlyDelegate.kt | 10 - .../core/utils/RvmPropertyReadOnlyDelegate.kt | 11 + .../reactiveviewmodel/widget/BaseControl.kt | 13 +- .../widget/BaseVisualControl.kt | 69 +++--- .../reactiveviewmodel/widget/CheckControl.kt | 95 +++++--- .../reactiveviewmodel/widget/DialogControl.kt | 61 ++++-- .../widget/DisplayableControl.kt | 81 +++++-- .../reactiveviewmodel/widget/InputControl.kt | 205 +++++++++++------- .../reactiveviewmodel/widget/RatingControl.kt | 101 +++++++-- 14 files changed, 469 insertions(+), 414 deletions(-) delete mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmPropertyReadOnlyDelegate.kt create mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyReadOnlyDelegate.kt diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmComponentsSupport.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmComponentsSupport.kt index 63cecec..6e7016d 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmComponentsSupport.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmComponentsSupport.kt @@ -2,7 +2,11 @@ package com.alexdeww.reactiveviewmodel.core import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker -import com.alexdeww.reactiveviewmodel.core.property.* +import com.alexdeww.reactiveviewmodel.core.property.RvmAction +import com.alexdeww.reactiveviewmodel.core.property.RvmConfirmationEvent +import com.alexdeww.reactiveviewmodel.core.property.RvmEvent +import com.alexdeww.reactiveviewmodel.core.property.RvmState +import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyReadOnlyDelegate import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.functions.Consumer import java.util.* @@ -45,7 +49,7 @@ interface RvmComponentsSupport { fun RvmComponentsSupport.state( initValue: T? = null, debounceInterval: Long? = null -): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( +): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( property = RvmState(initValue, debounceInterval) ) @@ -54,7 +58,7 @@ fun RvmComponentsSupport.state( fun RvmComponentsSupport.progressState( initValue: Boolean? = null, debounceInterval: Long = DEF_PROGRESS_DEBOUNCE_INTERVAL -): ReadOnlyProperty> = state( +): ReadOnlyProperty> = state( initValue = initValue, debounceInterval = debounceInterval ) @@ -63,7 +67,7 @@ fun RvmComponentsSupport.progressState( @RvmDslMarker fun RvmComponentsSupport.event( debounceInterval: Long? = null -): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( +): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( property = RvmEvent(debounceInterval) ) @@ -71,7 +75,7 @@ fun RvmComponentsSupport.event( @RvmDslMarker fun RvmComponentsSupport.eventNone( debounceInterval: Long? = null -): ReadOnlyProperty> = event( +): ReadOnlyProperty> = event( debounceInterval = debounceInterval ) @@ -79,7 +83,7 @@ fun RvmComponentsSupport.eventNone( @RvmDslMarker fun RvmComponentsSupport.confirmationEvent( debounceInterval: Long? = null -): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( +): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( property = RvmConfirmationEvent(debounceInterval) ) @@ -87,7 +91,7 @@ fun RvmComponentsSupport.confirmationEvent( @RvmDslMarker fun RvmComponentsSupport.confirmationEventNone( debounceInterval: Long? = null -): ReadOnlyProperty> = confirmationEvent( +): ReadOnlyProperty> = confirmationEvent( debounceInterval = debounceInterval ) @@ -95,7 +99,7 @@ fun RvmComponentsSupport.confirmationEventNone( @RvmDslMarker fun RvmComponentsSupport.action( debounceInterval: Long? = null -): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( +): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( property = RvmAction(debounceInterval) ) @@ -103,7 +107,7 @@ fun RvmComponentsSupport.action( @RvmDslMarker fun RvmComponentsSupport.actionNone( debounceInterval: Long? = null -): ReadOnlyProperty> = action( +): ReadOnlyProperty> = action( debounceInterval = debounceInterval ) @@ -111,7 +115,7 @@ fun RvmComponentsSupport.actionNone( @RvmDslMarker fun RvmComponentsSupport.debouncedAction( debounceInterval: Long = DEF_ACTION_DEBOUNCE_INTERVAL -): ReadOnlyProperty> = action( +): ReadOnlyProperty> = action( debounceInterval = debounceInterval ) @@ -119,6 +123,6 @@ fun RvmComponentsSupport.debouncedAction( @RvmDslMarker fun RvmComponentsSupport.debouncedActionNone( debounceInterval: Long = DEF_ACTION_DEBOUNCE_INTERVAL -): ReadOnlyProperty> = action( +): ReadOnlyProperty> = action( debounceInterval = debounceInterval ) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmSavedStateDelegates.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmSavedStateDelegates.kt index 0338935..4f3ecf5 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmSavedStateDelegates.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmSavedStateDelegates.kt @@ -1,167 +1,61 @@ package com.alexdeww.reactiveviewmodel.core import androidx.lifecycle.SavedStateHandle -import com.alexdeww.reactiveviewmodel.component.ReactiveViewModel import com.alexdeww.reactiveviewmodel.core.property.RvmState -import com.alexdeww.reactiveviewmodel.widget.* import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty fun SavedStateHandle.delegate( - initValue: (thisRef: ReactiveViewModel, stateHandle: SavedStateHandle, key: String) -> T -): ReadWriteProperty = SavedStateProperty(this, initValue) + initValue: (thisRef: RvmViewModelComponent, stateHandle: SavedStateHandle, key: String) -> T +): ReadWriteProperty = SavedStateProperty(this, initValue) fun SavedStateHandle.value( initialValue: T? = null -): ReadWriteProperty = delegate { _, stateHandle, key -> +): ReadWriteProperty = delegate { _, stateHandle, key -> if (stateHandle.contains(key)) stateHandle[key] else initialValue } fun SavedStateHandle.valueNonNull( defaultValue: T -): ReadWriteProperty = delegate { _, stateHandle, key -> +): ReadWriteProperty = delegate { _, stateHandle, key -> stateHandle[key] ?: defaultValue } fun SavedStateHandle.state( initialValue: T? = null, debounceInterval: Long? = null -): ReadOnlyProperty> = delegate { thisRef, stateHandle, key -> +): ReadOnlyProperty> = delegate { thisRef, stateHandle, key -> val state = RvmState(stateHandle[key] ?: initialValue, debounceInterval) thisRef.run { state.viewFlowable.subscribe { stateHandle[key] = it }.autoDispose() } state } -fun SavedStateHandle.inputControl( - initialText: String = "", - hideErrorOnUserInput: Boolean = true, - formatter: FormatterAction? = null, - initialEnabled: Boolean = true, - initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = delegate { thisRef, stateHandle, key -> - val textKey = "$key.text" - val errorKey = "$key.error" - val enabledKey = "$key.enabled" - val visibilityKey = "$key.visibility" - val control = InputControl( - initialText = stateHandle[textKey] ?: initialText, - hideErrorOnUserInput = hideErrorOnUserInput, - formatter = formatter, - initialEnabled = stateHandle[enabledKey] ?: initialEnabled, - initialVisibility = stateHandle[visibilityKey] ?: initialVisibility - ) - thisRef.run { - control.value.viewFlowable - .subscribe { stateHandle[textKey] = it } - .autoDispose() - control.error.viewFlowable - .subscribe { stateHandle[errorKey] = it } - .autoDispose() - control.enabled.viewFlowable - .subscribe { stateHandle[enabledKey] = it } - .autoDispose() - control.visibility.viewFlowable - .subscribe { stateHandle[visibilityKey] = it } - .autoDispose() - } - control -} - -fun SavedStateHandle.ratingControl( - initialValue: Float = 0f, - initialEnabled: Boolean = true, - initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = delegate { thisRef, stateHandle, key -> - val ratingKey = "$key.rating" - val enabledKey = "$key.enabled" - val visibilityKey = "$key.visibility" - val control = RatingControl( - initialValue = stateHandle[ratingKey] ?: initialValue, - initialEnabled = stateHandle[enabledKey] ?: initialEnabled, - initialVisibility = stateHandle[visibilityKey] ?: initialVisibility - ) - thisRef.run { - control.value.viewFlowable - .subscribe { stateHandle[ratingKey] = it } - .autoDispose() - control.enabled.viewFlowable - .subscribe { stateHandle[enabledKey] = it } - .autoDispose() - control.visibility.viewFlowable - .subscribe { stateHandle[visibilityKey] = it } - .autoDispose() - } - control -} - -fun SavedStateHandle.displayableControl( - debounceInterval: Long? = null -): ReadOnlyProperty> = - delegate { thisRef, stateHandle, key -> - val actionKey = "$key.action" - val control = DisplayableControl(debounceInterval) - thisRef.run { - control.action.setValue(stateHandle[actionKey] ?: DisplayableControl.Action.Hide) - control.action.viewFlowable - .subscribe { stateHandle[actionKey] = it } - .autoDispose() - } - control - } - -fun SavedStateHandle.checkControl( - initialChecked: Boolean = false, - initialEnabled: Boolean = true, - initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = delegate { thisRef, stateHandle, key -> - val checkedKey = "$key.checked" - val enabledKey = "$key.enabled" - val visibilityKey = "$key.visibility" - val control = CheckControl( - initialChecked = stateHandle[checkedKey] ?: initialChecked, - initialEnabled = stateHandle[enabledKey] ?: initialEnabled, - initialVisibility = stateHandle[visibilityKey] ?: initialVisibility - ) - thisRef.run { - control.value.viewFlowable - .subscribe { stateHandle[checkedKey] = it } - .autoDispose() - control.enabled.viewFlowable - .subscribe { stateHandle[enabledKey] = it } - .autoDispose() - control.visibility.viewFlowable - .subscribe { stateHandle[visibilityKey] = it } - .autoDispose() - } - control -} - @PublishedApi internal class SavedStateProperty( private val savedStateHandle: SavedStateHandle, - private val initValue: (thisRef: ReactiveViewModel, stateHandle: SavedStateHandle, key: String) -> T -) : ReadWriteProperty { + private val initValue: (thisRef: RvmViewModelComponent, stateHandle: SavedStateHandle, key: String) -> T +) : ReadWriteProperty { private object NoneValue private var value: Any? = NoneValue @Suppress("UNCHECKED_CAST") - override fun getValue(thisRef: ReactiveViewModel, property: KProperty<*>): T { + override fun getValue(thisRef: RvmViewModelComponent, property: KProperty<*>): T { if (value === NoneValue) { value = initValue(thisRef, savedStateHandle, getStateKey(thisRef, property)) } return value as T } - override fun setValue(thisRef: ReactiveViewModel, property: KProperty<*>, value: T) { + override fun setValue(thisRef: RvmViewModelComponent, property: KProperty<*>, value: T) { this.value = value savedStateHandle[getStateKey(thisRef, property)] = value } - private fun getStateKey(thisRef: ReactiveViewModel, property: KProperty<*>): String = + private fun getStateKey(thisRef: RvmViewModelComponent, property: KProperty<*>): String = "${thisRef::class.java.simpleName}.${property.name}" } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt index bbe4abd..2cf0f2f 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt @@ -1,9 +1,5 @@ package com.alexdeww.reactiveviewmodel.core -import android.app.Dialog -import android.widget.CompoundButton -import android.widget.EditText -import android.widget.RatingBar import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.Observer @@ -11,8 +7,7 @@ import com.alexdeww.reactiveviewmodel.core.RvmAutoDisposableSupport.StoreKey import com.alexdeww.reactiveviewmodel.core.property.RvmConfirmationEvent import com.alexdeww.reactiveviewmodel.core.property.RvmEvent import com.alexdeww.reactiveviewmodel.core.property.RvmState -import com.alexdeww.reactiveviewmodel.widget.* -import com.google.android.material.textfield.TextInputLayout +import com.alexdeww.reactiveviewmodel.widget.BaseControl import io.reactivex.rxjava3.disposables.Disposable interface RvmViewComponent : RvmAutoDisposableSupport { @@ -44,75 +39,7 @@ interface RvmViewComponent : RvmAutoDisposableSupport { action: OnLiveDataAction ): Observer = observe(componentLifecycleOwner, action) - fun DisplayableControl.observe( - action: DisplayableAction - ): Observer> = this@observe.action.observe { - action.invoke(it.isShowing, it.getShowingValue()) - } - - fun DisplayableControl.observe( - onShow: (T) -> Unit, - onHide: () -> Unit - ): Observer> = this@observe.action.observe { - when (it) { - is DisplayableControl.Action.Show -> onShow.invoke(it.data) - else -> onHide.invoke() - } - } - - fun CheckControl.bindTo( - compoundButton: CompoundButton, - bindEnable: Boolean = true, - bindVisible: Boolean = true - ) = bindTo( - rvmViewComponent = this@RvmViewComponent, - compoundButton = compoundButton, - bindEnable = bindEnable, - bindVisible = bindVisible - ) - - fun DialogControl.bindTo( - dialogCreator: DialogCreator - ) = bindTo( - rvmViewComponent = this@RvmViewComponent, - dialogCreator = dialogCreator - ) - - fun InputControl.bindTo( - editText: EditText, - bindError: Boolean = false, - bindEnable: Boolean = true, - bindVisible: Boolean = true - ) = bindTo( - rvmViewComponent = this@RvmViewComponent, - editText = editText, - bindError = bindError, - bindEnable = bindEnable, - bindVisible = bindVisible - ) - - fun InputControl.bindTo( - textInputLayout: TextInputLayout, - bindError: Boolean = false, - bindEnable: Boolean = true, - bindVisible: Boolean = true - ) = bindTo( - rvmViewComponent = this@RvmViewComponent, - textInputLayout = textInputLayout, - bindError = bindError, - bindEnable = bindEnable, - bindVisible = bindVisible - ) - - fun RatingControl.bindTo( - ratingBar: RatingBar, - bindEnable: Boolean = true, - bindVisible: Boolean = true - ) = bindTo( - rvmViewComponent = this@RvmViewComponent, - ratingBar = ratingBar, - bindEnable = bindEnable, - bindVisible = bindVisible - ) + val > C.binder: B + get() = getBinder(this@RvmViewComponent) } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt index 0733a8a..d90bc72 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt @@ -8,8 +8,6 @@ import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.subjects.BehaviorSubject -@RvmDslMarker -@RvmBinderDslMarker interface RvmViewModelComponent : RvmComponentsSupport, RvmWidgetsSupport, RvmAutoDisposableSupport { @@ -39,7 +37,7 @@ interface RvmViewModelComponent : RvmComponentsSupport, RvmWidgetsSupport, fun RvmViewModelComponent.invocable( block: (params: T) -> Completable ): Lazy> = lazy { - val action by action() + val action = RvmAction() val isExecuteSubj: BehaviorSubject = BehaviorSubject.createDefault(false) action bind { this.switchMapCompletable { params -> block(params).bindProgress(isExecuteSubj::onNext) } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt index 652c59d..3770be1 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt @@ -1,3 +1,3 @@ package com.alexdeww.reactiveviewmodel.core -interface RvmWidgetsSupport +interface RvmWidgetsSupport : RvmComponentsSupport diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmPropertyReadOnlyDelegate.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmPropertyReadOnlyDelegate.kt deleted file mode 100644 index 872c978..0000000 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmPropertyReadOnlyDelegate.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.alexdeww.reactiveviewmodel.core.property - -import kotlin.properties.ReadOnlyProperty -import kotlin.reflect.KProperty - -class RvmPropertyReadOnlyDelegate

( - private val property: P -) : ReadOnlyProperty { - override fun getValue(thisRef: Any?, property: KProperty<*>): P = this.property -} diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyReadOnlyDelegate.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyReadOnlyDelegate.kt new file mode 100644 index 0000000..3ce8e06 --- /dev/null +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyReadOnlyDelegate.kt @@ -0,0 +1,11 @@ +package com.alexdeww.reactiveviewmodel.core.utils + +import com.alexdeww.reactiveviewmodel.core.RvmComponentsSupport +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +class RvmPropertyReadOnlyDelegate

( + private val property: P +) : ReadOnlyProperty { + override fun getValue(thisRef: RvmComponentsSupport, property: KProperty<*>): P = this.property +} diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt index 6c66dd2..e5b601d 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt @@ -1,6 +1,17 @@ package com.alexdeww.reactiveviewmodel.widget import com.alexdeww.reactiveviewmodel.core.RvmComponentsSupport +import com.alexdeww.reactiveviewmodel.core.RvmViewComponent +import java.lang.ref.WeakReference @Suppress("UnnecessaryAbstractClass") -abstract class BaseControl : RvmComponentsSupport +abstract class BaseControl : RvmComponentsSupport { + + abstract class ViewBinder(rvmViewComponent: RvmViewComponent) { + protected val rvmViewComponentRef: WeakReference = + WeakReference(rvmViewComponent) + } + + internal abstract fun getBinder(rvmViewComponent: RvmViewComponent): B + +} diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt index 58c3028..341763b 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt @@ -9,11 +9,44 @@ import com.alexdeww.reactiveviewmodel.core.state import io.reactivex.rxjava3.functions.Consumer import java.lang.ref.WeakReference -abstract class BaseVisualControl( +typealias ActionOnValueChanged = (newValue: T) -> Unit +typealias ActionOnActive = VisualControlLiveDataMediator.() -> Unit +typealias ActionOnInactive = VisualControlLiveDataMediator.() -> Unit + +abstract class BaseVisualControl>( initialValue: T, initialEnabled: Boolean, initialVisibility: Visibility -) : BaseControl() { +) : BaseControl() { + + abstract class BaseBinder( + rvmViewComponent: RvmViewComponent + ) : ViewBinder(rvmViewComponent) { + + protected abstract val control: BaseVisualControl + + @Suppress("LongParameterList") + protected fun bindTo( + view: V, + bindEnable: Boolean, + bindVisible: Boolean, + onValueChanged: ActionOnValueChanged, + onActiveAction: ActionOnActive, + onInactiveAction: ActionOnInactive + ) { + val liveData = VisualControlLiveDataMediator( + control = control, + view = view, + bindEnable = bindEnable, + bindVisible = bindVisible, + onValueChanged = onValueChanged, + onActiveAction = onActiveAction, + onInactiveAction = onInactiveAction + ) + rvmViewComponentRef.get()?.run { liveData.observe { /* empty */ } } + } + + } enum class Visibility(val value: Int) { VISIBLE(View.VISIBLE), @@ -40,33 +73,9 @@ abstract class BaseVisualControl( } -typealias ActionOnValueChanged = (newValue: T) -> Unit -typealias ActionOnActive = VisualControlLiveDataMediator.() -> Unit -typealias ActionOnInactive = VisualControlLiveDataMediator.() -> Unit - -fun , T : Any, V : View> C.baseBindTo( - rvmViewComponent: RvmViewComponent, - view: V, - bindEnable: Boolean, - bindVisible: Boolean, - onValueChanged: ActionOnValueChanged, - onActiveAction: ActionOnActive, - onInactiveAction: ActionOnInactive -) { - val liveData = VisualControlLiveDataMediator( - control = this@baseBindTo, - view = view, - bindEnable = bindEnable, - bindVisible = bindVisible, - onValueChanged = onValueChanged, - onActiveAction = onActiveAction, - onInactiveAction = onInactiveAction - ) - rvmViewComponent.run { liveData.observe { /* empty */ } } -} - -class VisualControlLiveDataMediator( - control: BaseVisualControl, +@Suppress("LongParameterList") +class VisualControlLiveDataMediator internal constructor( + control: BaseVisualControl, view: View, private val bindEnable: Boolean, private val bindVisible: Boolean, @@ -78,7 +87,7 @@ class VisualControlLiveDataMediator( private val viewRef = WeakReference(view) private val view: View? get() = viewRef.get() private val controlRef = WeakReference(control) - private val control: BaseVisualControl? get() = controlRef.get() + private val control: BaseVisualControl? get() = controlRef.get() private var isEditing: Boolean = false val changeValueConsumer = Consumer { diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt index 25f9ba4..2f1f2ba 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt @@ -1,21 +1,57 @@ package com.alexdeww.reactiveviewmodel.widget import android.widget.CompoundButton +import androidx.lifecycle.SavedStateHandle import com.alexdeww.reactiveviewmodel.core.RvmViewComponent +import com.alexdeww.reactiveviewmodel.core.RvmViewModelComponent import com.alexdeww.reactiveviewmodel.core.RvmWidgetsSupport +import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker -import com.alexdeww.reactiveviewmodel.core.property.RvmPropertyReadOnlyDelegate +import com.alexdeww.reactiveviewmodel.core.delegate +import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyReadOnlyDelegate import kotlin.properties.ReadOnlyProperty class CheckControl internal constructor( initialChecked: Boolean, initialEnabled: Boolean, initialVisibility: Visibility -) : BaseVisualControl( - initialChecked, - initialEnabled, - initialVisibility -) +) : BaseVisualControl( + initialValue = initialChecked, + initialEnabled = initialEnabled, + initialVisibility = initialVisibility +) { + + override fun getBinder(rvmViewComponent: RvmViewComponent): Binder = Binder(rvmViewComponent) + + inner class Binder internal constructor( + rvmViewComponent: RvmViewComponent + ) : BaseBinder(rvmViewComponent) { + + override val control: BaseVisualControl get() = this@CheckControl + + @RvmBinderDslMarker + fun bindTo( + compoundButton: CompoundButton, + bindEnable: Boolean = true, + bindVisible: Boolean = true + ) { + bindTo( + view = compoundButton, + bindEnable = bindEnable, + bindVisible = bindVisible, + onValueChanged = { compoundButton.isChecked = it }, + onActiveAction = { + compoundButton.setOnCheckedChangeListener { _, isChecked -> + changeValueConsumer.accept(isChecked) + } + }, + onInactiveAction = { compoundButton.setOnCheckedChangeListener(null) } + ) + } + + } + +} @Suppress("unused") @RvmDslMarker @@ -23,7 +59,7 @@ fun RvmWidgetsSupport.checkControl( initialChecked: Boolean = false, initialEnabled: Boolean = true, initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = RvmPropertyReadOnlyDelegate( +): ReadOnlyProperty = RvmPropertyReadOnlyDelegate( property = CheckControl( initialChecked = initialChecked, initialEnabled = initialEnabled, @@ -31,21 +67,30 @@ fun RvmWidgetsSupport.checkControl( ) ) -fun CheckControl.bindTo( - rvmViewComponent: RvmViewComponent, - compoundButton: CompoundButton, - bindEnable: Boolean = true, - bindVisible: Boolean = true -) = baseBindTo( - rvmViewComponent = rvmViewComponent, - view = compoundButton, - bindEnable = bindEnable, - bindVisible = bindVisible, - onValueChanged = { compoundButton.isChecked = it }, - onActiveAction = { - compoundButton.setOnCheckedChangeListener { _, isChecked -> - changeValueConsumer.accept(isChecked) - } - }, - onInactiveAction = { compoundButton.setOnCheckedChangeListener(null) } -) +@RvmDslMarker +fun SavedStateHandle.checkControl( + initialChecked: Boolean = false, + initialEnabled: Boolean = true, + initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE +): ReadOnlyProperty = delegate { thisRef, stateHandle, key -> + val checkedKey = "$key.checked" + val enabledKey = "$key.enabled" + val visibilityKey = "$key.visibility" + val control = CheckControl( + initialChecked = stateHandle[checkedKey] ?: initialChecked, + initialEnabled = stateHandle[enabledKey] ?: initialEnabled, + initialVisibility = stateHandle[visibilityKey] ?: initialVisibility + ) + thisRef.run { + control.value.viewFlowable + .subscribe { stateHandle[checkedKey] = it } + .autoDispose() + control.enabled.viewFlowable + .subscribe { stateHandle[enabledKey] = it } + .autoDispose() + control.visibility.viewFlowable + .subscribe { stateHandle[visibilityKey] = it } + .autoDispose() + } + control +} diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt index abd4920..fee2a2b 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt @@ -3,16 +3,22 @@ package com.alexdeww.reactiveviewmodel.widget import android.app.Dialog import androidx.lifecycle.MediatorLiveData import com.alexdeww.reactiveviewmodel.core.RvmViewComponent +import com.alexdeww.reactiveviewmodel.core.RvmWidgetsSupport import com.alexdeww.reactiveviewmodel.core.action +import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker +import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker +import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyReadOnlyDelegate import com.alexdeww.reactiveviewmodel.core.state import io.reactivex.rxjava3.core.Maybe +import kotlin.properties.ReadOnlyProperty sealed class DialogResult { object Accept : DialogResult() object Cancel : DialogResult() } -class DialogControl internal constructor() : BaseControl() { +class DialogControl internal constructor() : + BaseControl.Binder>() { sealed class Display { data class Displayed(val data: T) : Display() @@ -47,6 +53,33 @@ class DialogControl internal constructor() : BaseControl() { if (isShowing) displayed.consumer.accept(Display.Absent) } + override fun getBinder(rvmViewComponent: RvmViewComponent): Binder = Binder(rvmViewComponent) + + inner class Binder internal constructor( + rvmViewComponent: RvmViewComponent + ) : ViewBinder(rvmViewComponent) { + + @RvmBinderDslMarker + fun bindTo( + dialogHandlerListener: DialogHandlerListener, + dialogCreator: DialogCreator, + ) { + val liveData = DialogLiveDataMediator( + control = this@DialogControl, + dialogCreator = dialogCreator, + dialogHandlerListener = dialogHandlerListener + ) + rvmViewComponentRef.get()?.run { liveData.observe { /* empty */ } } + } + + @RvmBinderDslMarker + fun bindTo(dialogCreator: DialogCreator) = bindTo( + dialogHandlerListener = OrdinaryDialogHandlerListener(), + dialogCreator = dialogCreator + ) + + } + } class DialogControlResult internal constructor( @@ -68,9 +101,17 @@ class DialogControlResult internal constructor( } -fun dialogControl(): DialogControl = DialogControl() +@Suppress("unused") +@RvmDslMarker +fun RvmWidgetsSupport.dialogControl(): ReadOnlyProperty< + RvmWidgetsSupport, DialogControl> = + RvmPropertyReadOnlyDelegate(property = DialogControl()) -fun dialogControlWithResult(): DialogControl = DialogControl() +@Suppress("unused") +@RvmDslMarker +fun RvmWidgetsSupport.dialogControlWithResult(): ReadOnlyProperty< + RvmWidgetsSupport, DialogControl> = + dialogControl() typealias DialogCreator = (data: T, dc: DialogControlResult) -> D @@ -88,20 +129,6 @@ interface DialogHandlerListener { } -fun DialogControl.bindToEx( - rvmViewComponent: RvmViewComponent, - dialogCreator: DialogCreator, - dialogHandlerListener: DialogHandlerListener -) { - val liveData = DialogLiveDataMediator(this, dialogCreator, dialogHandlerListener) - rvmViewComponent.run { liveData.observe { /* empty */ } } -} - -fun DialogControl.bindTo( - rvmViewComponent: RvmViewComponent, - dialogCreator: DialogCreator -) = bindToEx(rvmViewComponent, dialogCreator, OrdinaryDialogHandlerListener()) - private class DialogLiveDataMediator( private val control: DialogControl, private val dialogCreator: DialogCreator, diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt index d6c5033..720716b 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt @@ -1,15 +1,20 @@ package com.alexdeww.reactiveviewmodel.widget import android.os.Parcelable -import androidx.lifecycle.Observer -import com.alexdeww.reactiveviewmodel.core.RvmViewComponent -import com.alexdeww.reactiveviewmodel.core.state +import androidx.lifecycle.SavedStateHandle +import com.alexdeww.reactiveviewmodel.core.* +import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker +import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker +import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyReadOnlyDelegate import kotlinx.parcelize.Parcelize import kotlinx.parcelize.RawValue +import kotlin.properties.ReadOnlyProperty + +typealias DisplayableAction = (isVisible: Boolean, data: T?) -> Unit class DisplayableControl internal constructor( debounceInterval: Long? = null -) : BaseControl() { +) : BaseControl.Binder>() { sealed class Action : Parcelable { @@ -35,29 +40,59 @@ class DisplayableControl internal constructor( action.consumer.accept(Action.Hide) } -} + override fun getBinder(rvmViewComponent: RvmViewComponent): Binder = Binder(rvmViewComponent) -fun displayableControl(debounceInterval: Long? = null): DisplayableControl = - DisplayableControl(debounceInterval) + inner class Binder internal constructor( + rvmViewComponent: RvmViewComponent + ) : ViewBinder(rvmViewComponent) { -typealias DisplayableAction = (isVisible: Boolean, data: T?) -> Unit + @RvmBinderDslMarker + fun bind(action: DisplayableAction) { + rvmViewComponentRef.get()?.run { + this@DisplayableControl.action.observe { + action.invoke(it.isShowing, it.getShowingValue()) + } + } + } + + @RvmBinderDslMarker + fun bind( + onShow: (T) -> Unit, + onHide: () -> Unit + ) { + rvmViewComponentRef.get()?.run { + this@DisplayableControl.action.observe { + when (it) { + is Action.Show -> onShow.invoke(it.data) + else -> onHide.invoke() + } + } + } + } + + } -fun DisplayableControl.observe( - rvmViewComponent: RvmViewComponent, - action: DisplayableAction -): Observer> = rvmViewComponent.run { - this@observe.action.observe { action.invoke(it.isShowing, it.getShowingValue()) } } -fun DisplayableControl.observe( - rvmViewComponent: RvmViewComponent, - onShow: (T) -> Unit, - onHide: () -> Unit -): Observer> = rvmViewComponent.run { - this@observe.action.observe { - when (it) { - is DisplayableControl.Action.Show -> onShow.invoke(it.data) - else -> onHide.invoke() - } +@Suppress("unused") +@RvmDslMarker +fun RvmWidgetsSupport.displayableControl( + debounceInterval: Long? = null +): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( + property = DisplayableControl(debounceInterval) +) + +@RvmDslMarker +fun SavedStateHandle.displayableControl( + debounceInterval: Long? = null +): ReadOnlyProperty> = delegate { thisRef, sh, key -> + val actionKey = "$key.action" + val control = DisplayableControl(debounceInterval) + thisRef.run { + control.action.setValue(sh[actionKey] ?: DisplayableControl.Action.Hide) + control.action.viewFlowable + .subscribe { sh[actionKey] = it } + .autoDispose() } + control } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt index 9e6e91d..34dbe55 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt @@ -3,9 +3,13 @@ package com.alexdeww.reactiveviewmodel.widget import android.text.* import android.view.View import android.widget.EditText -import com.alexdeww.reactiveviewmodel.core.RvmViewComponent -import com.alexdeww.reactiveviewmodel.core.state +import androidx.lifecycle.SavedStateHandle +import com.alexdeww.reactiveviewmodel.core.* +import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker +import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker +import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyReadOnlyDelegate import com.google.android.material.textfield.TextInputLayout +import kotlin.properties.ReadOnlyProperty typealias FormatterAction = (text: String) -> String @@ -15,7 +19,11 @@ class InputControl internal constructor( formatter: FormatterAction?, initialEnabled: Boolean, initialVisibility: Visibility -) : BaseVisualControl(initialText, initialEnabled, initialVisibility) { +) : BaseVisualControl( + initialValue = initialText, + initialEnabled = initialEnabled, + initialVisibility = initialVisibility +) { init { value.valueChangesHook = formatter @@ -28,92 +36,131 @@ class InputControl internal constructor( super.onChangedValue(newValue) } + override fun getBinder(rvmViewComponent: RvmViewComponent): Binder = Binder(rvmViewComponent) + + inner class Binder internal constructor( + rvmViewComponent: RvmViewComponent + ) : BaseBinder(rvmViewComponent) { + + override val control: BaseVisualControl get() = this@InputControl + + @RvmBinderDslMarker + fun bindTo( + editText: EditText, + bindError: Boolean = false, + bindEnable: Boolean = true, + bindVisible: Boolean = true + ) = bindTo( + view = editText, + editText = editText, + actionOnError = { editText.error = it }, + bindError = bindError, + bindEnable = bindEnable, + bindVisible = bindVisible + ) + + @RvmBinderDslMarker + fun bindTo( + textInputLayout: TextInputLayout, + bindError: Boolean = false, + bindEnable: Boolean = true, + bindVisible: Boolean = true + ) = bindTo( + view = textInputLayout, + editText = textInputLayout.editText!!, + actionOnError = { textInputLayout.error = it }, + bindError = bindError, + bindEnable = bindEnable, + bindVisible = bindVisible + ) + + @Suppress("LongParameterList") + private fun InputControl.bindTo( + view: View, + editText: EditText, + actionOnError: (String) -> Unit, + bindError: Boolean = false, + bindEnable: Boolean = true, + bindVisible: Boolean = true + ) { + var textWatcher: TextWatcher? = null + bindTo( + view = view, + bindEnable = bindEnable, + bindVisible = bindVisible, + onValueChanged = { newValue -> + val editable = editText.text + if (editable != null && !newValue.contentEquals(editable)) { + val ss = SpannableString(newValue) + TextUtils.copySpansFrom(editable, 0, ss.length, null, ss, 0) + editable.replace(0, editable.length, ss) + } + }, + onActiveAction = { + if (bindError) addSource(error.liveData) { actionOnError.invoke(it) } + textWatcher = onTextChangedWatcher { changeValueConsumer.accept(it.toString()) } + editText.addTextChangedListener(textWatcher) + }, + onInactiveAction = { + if (bindError) removeSource(error.liveData) + textWatcher?.let { editText.removeTextChangedListener(it) } + textWatcher = null + } + ) + } + + } + } -fun inputControl( +@Suppress("unused") +@RvmDslMarker +fun RvmWidgetsSupport.inputControl( initialText: String = "", hideErrorOnUserInput: Boolean = true, formatter: FormatterAction? = null, initialEnabled: Boolean = true, initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): InputControl = InputControl( - initialText = initialText, - hideErrorOnUserInput = hideErrorOnUserInput, - formatter = formatter, - initialEnabled = initialEnabled, - initialVisibility = initialVisibility -) - -fun InputControl.bindTo( - rvmViewComponent: RvmViewComponent, - editText: EditText, - bindError: Boolean = false, - bindEnable: Boolean = true, - bindVisible: Boolean = true -) = bindTo( - rvmViewComponent = rvmViewComponent, - view = editText, - editText = editText, - actionOnError = { editText.error = it }, - bindError = bindError, - bindEnable = bindEnable, - bindVisible = bindVisible -) - -fun InputControl.bindTo( - rvmViewComponent: RvmViewComponent, - textInputLayout: TextInputLayout, - bindError: Boolean = false, - bindEnable: Boolean = true, - bindVisible: Boolean = true -) = bindTo( - rvmViewComponent = rvmViewComponent, - view = textInputLayout, - editText = textInputLayout.editText!!, - actionOnError = { textInputLayout.error = it }, - bindError = bindError, - bindEnable = bindEnable, - bindVisible = bindVisible +): ReadOnlyProperty = RvmPropertyReadOnlyDelegate( + property = InputControl( + initialText = initialText, + hideErrorOnUserInput = hideErrorOnUserInput, + formatter = formatter, + initialEnabled = initialEnabled, + initialVisibility = initialVisibility + ) ) -internal fun InputControl.bindTo( - rvmViewComponent: RvmViewComponent, - view: View, - editText: EditText, - actionOnError: (String) -> Unit, - bindError: Boolean = false, - bindEnable: Boolean = true, - bindVisible: Boolean = true -) { - var textWatcher: TextWatcher? = null - baseBindTo( - rvmViewComponent = rvmViewComponent, - view = view, - bindEnable = bindEnable, - bindVisible = bindVisible, - onValueChanged = { newValue -> - val editable = editText.text - if (!newValue.contentEquals(editable)) { - if (editable is Spanned) { - val ss = SpannableString(newValue) - TextUtils.copySpansFrom(editable, 0, ss.length, null, ss, 0) - editable.replace(0, editable.length, ss) - } else { - editable.replace(0, editable.length, newValue) - } - } - }, - onActiveAction = { - if (bindError) addSource(error.liveData) { actionOnError.invoke(it) } - textWatcher = onTextChangedWatcher { changeValueConsumer.accept(it.toString()) } - editText.addTextChangedListener(textWatcher) - }, - onInactiveAction = { - if (bindError) removeSource(error.liveData) - textWatcher?.let { editText.removeTextChangedListener(it) } - textWatcher = null - } +@RvmDslMarker +fun SavedStateHandle.inputControl( + initialText: String = "", + hideErrorOnUserInput: Boolean = true, + formatter: FormatterAction? = null, + initialEnabled: Boolean = true, + initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE +): ReadOnlyProperty = delegate { thisRef, stateHandle, key -> + val textKey = "$key.text" + val enabledKey = "$key.enabled" + val visibilityKey = "$key.visibility" + val control = InputControl( + initialText = stateHandle[textKey] ?: initialText, + hideErrorOnUserInput = hideErrorOnUserInput, + formatter = formatter, + initialEnabled = stateHandle[enabledKey] ?: initialEnabled, + initialVisibility = stateHandle[visibilityKey] ?: initialVisibility ) + thisRef.run { + control.value.viewFlowable + .subscribe { stateHandle[textKey] = it } + .autoDispose() + control.enabled.viewFlowable + .subscribe { stateHandle[enabledKey] = it } + .autoDispose() + control.visibility.viewFlowable + .subscribe { stateHandle[visibilityKey] = it } + .autoDispose() + } + control } private fun onTextChangedWatcher( diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt index 3a3ddb1..c4ff2e6 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt @@ -2,38 +2,95 @@ package com.alexdeww.reactiveviewmodel.widget import android.annotation.SuppressLint import android.widget.RatingBar +import androidx.lifecycle.SavedStateHandle +import com.alexdeww.reactiveviewmodel.component.ReactiveViewModel import com.alexdeww.reactiveviewmodel.core.RvmViewComponent +import com.alexdeww.reactiveviewmodel.core.RvmWidgetsSupport +import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker +import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker +import com.alexdeww.reactiveviewmodel.core.delegate +import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyReadOnlyDelegate +import kotlin.properties.ReadOnlyProperty @SuppressLint("CheckResult") class RatingControl internal constructor( initialValue: Float, initialEnabled: Boolean, initialVisibility: Visibility -) : BaseVisualControl(initialValue, initialEnabled, initialVisibility) +) : BaseVisualControl( + initialValue = initialValue, + initialEnabled = initialEnabled, + initialVisibility = initialVisibility +) { + + override fun getBinder(rvmViewComponent: RvmViewComponent): Binder = Binder(rvmViewComponent) + + inner class Binder internal constructor( + rvmViewComponent: RvmViewComponent + ) : BaseBinder(rvmViewComponent) { + + override val control: BaseVisualControl get() = this@RatingControl + + @RvmBinderDslMarker + fun bindTo( + ratingBar: RatingBar, + bindEnable: Boolean = true, + bindVisible: Boolean = true + ) = bindTo( + view = ratingBar, + bindEnable = bindEnable, + bindVisible = bindVisible, + onValueChanged = { ratingBar.rating = it }, + onActiveAction = { + ratingBar.setOnRatingBarChangeListener { _, rating, _ -> + changeValueConsumer.accept(rating) + } + }, + onInactiveAction = { ratingBar.onRatingBarChangeListener = null } + ) + + } -fun ratingControl( +} + +@Suppress("unused") +@RvmDslMarker +fun RvmWidgetsSupport.ratingControl( initialValue: Float = 0f, initialEnabled: Boolean = true, initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): RatingControl = RatingControl( - initialValue = initialValue, - initialEnabled = initialEnabled, - initialVisibility = initialVisibility +): ReadOnlyProperty = RvmPropertyReadOnlyDelegate( + property = RatingControl( + initialValue = initialValue, + initialEnabled = initialEnabled, + initialVisibility = initialVisibility + ) ) -fun RatingControl.bindTo( - rvmViewComponent: RvmViewComponent, - ratingBar: RatingBar, - bindEnable: Boolean = true, - bindVisible: Boolean = true -) = baseBindTo( - rvmViewComponent = rvmViewComponent, - view = ratingBar, - bindEnable = bindEnable, - bindVisible = bindVisible, - onValueChanged = { ratingBar.rating = it }, - onActiveAction = { - ratingBar.setOnRatingBarChangeListener { _, rating, _ -> changeValueConsumer.accept(rating) } - }, - onInactiveAction = { ratingBar.onRatingBarChangeListener = null } -) +@RvmDslMarker +fun SavedStateHandle.ratingControl( + initialValue: Float = 0f, + initialEnabled: Boolean = true, + initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE +): ReadOnlyProperty = delegate { thisRef, stateHandle, key -> + val ratingKey = "$key.rating" + val enabledKey = "$key.enabled" + val visibilityKey = "$key.visibility" + val control = RatingControl( + initialValue = stateHandle[ratingKey] ?: initialValue, + initialEnabled = stateHandle[enabledKey] ?: initialEnabled, + initialVisibility = stateHandle[visibilityKey] ?: initialVisibility + ) + thisRef.run { + control.value.viewFlowable + .subscribe { stateHandle[ratingKey] = it } + .autoDispose() + control.enabled.viewFlowable + .subscribe { stateHandle[enabledKey] = it } + .autoDispose() + control.visibility.viewFlowable + .subscribe { stateHandle[visibilityKey] = it } + .autoDispose() + } + control +} From b325351266b90ceda0145bd4cb5abaf3223eafac Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 4 Jan 2023 22:13:01 +0700 Subject: [PATCH 03/31] add widget bind shortcut --- .../component/ReactiveActivity.kt | 3 +- .../component/ReactiveFragment.kt | 3 +- ...entsSupport.kt => RvmPropertiesSupport.kt} | 44 ++++++------- .../core/RvmViewModelComponent.kt | 4 +- .../core/RvmWidgetsSupport.kt | 2 +- .../core/utils/RvmPropertyReadOnlyDelegate.kt | 6 +- .../reactiveviewmodel/widget/BaseControl.kt | 4 +- .../widget/DisplayableControl.kt | 1 + .../widget/RvmWidgetBindShortcut.kt | 66 +++++++++++++++++++ 9 files changed, 100 insertions(+), 33 deletions(-) rename reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/{RvmComponentsSupport.kt => RvmPropertiesSupport.kt} (72%) create mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmWidgetBindShortcut.kt diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveActivity.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveActivity.kt index 433b847..8066689 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveActivity.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveActivity.kt @@ -6,9 +6,10 @@ import androidx.lifecycle.LifecycleOwner import com.alexdeww.reactiveviewmodel.core.DefaultRvmDisposableStore import com.alexdeww.reactiveviewmodel.core.RvmAutoDisposableStore import com.alexdeww.reactiveviewmodel.core.RvmViewComponent +import com.alexdeww.reactiveviewmodel.widget.RvmWidgetBindShortcut import io.reactivex.rxjava3.disposables.Disposable -abstract class ReactiveActivity : AppCompatActivity(), RvmViewComponent, +abstract class ReactiveActivity : AppCompatActivity(), RvmViewComponent, RvmWidgetBindShortcut, RvmAutoDisposableStore by DefaultRvmDisposableStore() { override val componentLifecycleOwner: LifecycleOwner get() = this diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveFragment.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveFragment.kt index df261c8..d609efe 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveFragment.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveFragment.kt @@ -7,10 +7,11 @@ import androidx.lifecycle.LifecycleOwner import com.alexdeww.reactiveviewmodel.core.DefaultRvmDisposableStore import com.alexdeww.reactiveviewmodel.core.RvmAutoDisposableStore import com.alexdeww.reactiveviewmodel.core.RvmViewComponent +import com.alexdeww.reactiveviewmodel.widget.RvmWidgetBindShortcut abstract class ReactiveFragment( @LayoutRes layoutId: Int = 0 -) : Fragment(layoutId), RvmViewComponent, +) : Fragment(layoutId), RvmViewComponent, RvmWidgetBindShortcut, RvmAutoDisposableStore by DefaultRvmDisposableStore() { override val componentLifecycleOwner: LifecycleOwner get() = viewLifecycleOwner diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmComponentsSupport.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt similarity index 72% rename from reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmComponentsSupport.kt rename to reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt index 6e7016d..dc3a542 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmComponentsSupport.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt @@ -1,6 +1,5 @@ package com.alexdeww.reactiveviewmodel.core -import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker import com.alexdeww.reactiveviewmodel.core.property.RvmAction import com.alexdeww.reactiveviewmodel.core.property.RvmConfirmationEvent @@ -13,8 +12,7 @@ import java.util.* import kotlin.properties.ReadOnlyProperty @RvmDslMarker -@RvmBinderDslMarker -interface RvmComponentsSupport { +interface RvmPropertiesSupport { // State val RvmState.consumer: Consumer get() = this.consumer @@ -46,83 +44,83 @@ interface RvmComponentsSupport { @Suppress("unused") @RvmDslMarker -fun RvmComponentsSupport.state( +fun RvmPropertiesSupport.state( initValue: T? = null, debounceInterval: Long? = null -): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( +): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( property = RvmState(initValue, debounceInterval) ) @Suppress("unused") @RvmDslMarker -fun RvmComponentsSupport.progressState( +fun RvmPropertiesSupport.progressState( initValue: Boolean? = null, debounceInterval: Long = DEF_PROGRESS_DEBOUNCE_INTERVAL -): ReadOnlyProperty> = state( +): ReadOnlyProperty> = state( initValue = initValue, debounceInterval = debounceInterval ) @Suppress("unused") @RvmDslMarker -fun RvmComponentsSupport.event( +fun RvmPropertiesSupport.event( debounceInterval: Long? = null -): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( +): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( property = RvmEvent(debounceInterval) ) @Suppress("unused") @RvmDslMarker -fun RvmComponentsSupport.eventNone( +fun RvmPropertiesSupport.eventNone( debounceInterval: Long? = null -): ReadOnlyProperty> = event( +): ReadOnlyProperty> = event( debounceInterval = debounceInterval ) @Suppress("unused") @RvmDslMarker -fun RvmComponentsSupport.confirmationEvent( +fun RvmPropertiesSupport.confirmationEvent( debounceInterval: Long? = null -): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( +): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( property = RvmConfirmationEvent(debounceInterval) ) @Suppress("unused") @RvmDslMarker -fun RvmComponentsSupport.confirmationEventNone( +fun RvmPropertiesSupport.confirmationEventNone( debounceInterval: Long? = null -): ReadOnlyProperty> = confirmationEvent( +): ReadOnlyProperty> = confirmationEvent( debounceInterval = debounceInterval ) @Suppress("unused") @RvmDslMarker -fun RvmComponentsSupport.action( +fun RvmPropertiesSupport.action( debounceInterval: Long? = null -): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( +): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( property = RvmAction(debounceInterval) ) @Suppress("unused") @RvmDslMarker -fun RvmComponentsSupport.actionNone( +fun RvmPropertiesSupport.actionNone( debounceInterval: Long? = null -): ReadOnlyProperty> = action( +): ReadOnlyProperty> = action( debounceInterval = debounceInterval ) @Suppress("unused") @RvmDslMarker -fun RvmComponentsSupport.debouncedAction( +fun RvmPropertiesSupport.debouncedAction( debounceInterval: Long = DEF_ACTION_DEBOUNCE_INTERVAL -): ReadOnlyProperty> = action( +): ReadOnlyProperty> = action( debounceInterval = debounceInterval ) @Suppress("unused") @RvmDslMarker -fun RvmComponentsSupport.debouncedActionNone( +fun RvmPropertiesSupport.debouncedActionNone( debounceInterval: Long = DEF_ACTION_DEBOUNCE_INTERVAL -): ReadOnlyProperty> = action( +): ReadOnlyProperty> = action( debounceInterval = debounceInterval ) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt index d90bc72..62ca640 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt @@ -8,7 +8,8 @@ import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.subjects.BehaviorSubject -interface RvmViewModelComponent : RvmComponentsSupport, RvmWidgetsSupport, +@RvmBinderDslMarker +interface RvmViewModelComponent : RvmPropertiesSupport, RvmWidgetsSupport, RvmAutoDisposableSupport { interface Invocable { @@ -73,4 +74,3 @@ internal fun RvmViewModelComponent.bindState( .subscribe() .autoDispose() } - diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt index 3770be1..74d1cda 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt @@ -1,3 +1,3 @@ package com.alexdeww.reactiveviewmodel.core -interface RvmWidgetsSupport : RvmComponentsSupport +interface RvmWidgetsSupport : RvmPropertiesSupport diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyReadOnlyDelegate.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyReadOnlyDelegate.kt index 3ce8e06..d005610 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyReadOnlyDelegate.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyReadOnlyDelegate.kt @@ -1,11 +1,11 @@ package com.alexdeww.reactiveviewmodel.core.utils -import com.alexdeww.reactiveviewmodel.core.RvmComponentsSupport +import com.alexdeww.reactiveviewmodel.core.RvmPropertiesSupport import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty class RvmPropertyReadOnlyDelegate

( private val property: P -) : ReadOnlyProperty { - override fun getValue(thisRef: RvmComponentsSupport, property: KProperty<*>): P = this.property +) : ReadOnlyProperty { + override fun getValue(thisRef: RvmPropertiesSupport, property: KProperty<*>): P = this.property } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt index e5b601d..111a9b1 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt @@ -1,11 +1,11 @@ package com.alexdeww.reactiveviewmodel.widget -import com.alexdeww.reactiveviewmodel.core.RvmComponentsSupport +import com.alexdeww.reactiveviewmodel.core.RvmPropertiesSupport import com.alexdeww.reactiveviewmodel.core.RvmViewComponent import java.lang.ref.WeakReference @Suppress("UnnecessaryAbstractClass") -abstract class BaseControl : RvmComponentsSupport { +abstract class BaseControl : RvmPropertiesSupport { abstract class ViewBinder(rvmViewComponent: RvmViewComponent) { protected val rvmViewComponentRef: WeakReference = diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt index 720716b..740cb2f 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt @@ -26,6 +26,7 @@ class DisplayableControl internal constructor( val isShowing: Boolean get() = this is Show fun getShowingValue(): T? = (this as? Show)?.data + } val action by state>(Action.Hide, debounceInterval) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmWidgetBindShortcut.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmWidgetBindShortcut.kt new file mode 100644 index 0000000..699669c --- /dev/null +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmWidgetBindShortcut.kt @@ -0,0 +1,66 @@ +package com.alexdeww.reactiveviewmodel.widget + +import android.app.Dialog +import android.widget.CompoundButton +import android.widget.EditText +import android.widget.RatingBar +import com.alexdeww.reactiveviewmodel.core.RvmViewComponent +import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker +import com.google.android.material.textfield.TextInputLayout + +@RvmBinderDslMarker +interface RvmWidgetBindShortcut : RvmViewComponent { + + @RvmBinderDslMarker + fun CheckControl.bindTo( + compoundButton: CompoundButton, + bindEnable: Boolean = true, + bindVisible: Boolean = true + ) = binder.bindTo(compoundButton, bindEnable, bindVisible) + + @RvmBinderDslMarker + fun DialogControl.bindTo( + dialogHandlerListener: DialogHandlerListener, + dialogCreator: DialogCreator, + ) = binder.bindTo(dialogHandlerListener, dialogCreator) + + @RvmBinderDslMarker + fun DialogControl.bindTo( + dialogCreator: DialogCreator + ) = binder.bindTo(dialogCreator) + + @RvmBinderDslMarker + fun DisplayableControl.bind( + action: DisplayableAction + ) = binder.bind(action) + + @RvmBinderDslMarker + fun DisplayableControl.bind( + onShow: (T) -> Unit, + onHide: () -> Unit + ) = binder.bind(onShow, onHide) + + @RvmBinderDslMarker + fun InputControl.bindTo( + editText: EditText, + bindError: Boolean = false, + bindEnable: Boolean = true, + bindVisible: Boolean = true + ) = binder.bindTo(editText, bindError, bindEnable, bindVisible) + + @RvmBinderDslMarker + fun InputControl.bindTo( + textInputLayout: TextInputLayout, + bindError: Boolean = false, + bindEnable: Boolean = true, + bindVisible: Boolean = true + ) = binder.bindTo(textInputLayout, bindError, bindEnable, bindVisible) + + @RvmBinderDslMarker + fun RatingControl.bindTo( + ratingBar: RatingBar, + bindEnable: Boolean = true, + bindVisible: Boolean = true + ) = binder.bindTo(ratingBar, bindEnable, bindVisible) + +} From 59547f01bae74e6d4338613ad909108af3ab94b5 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 5 Jan 2023 07:24:06 +0700 Subject: [PATCH 04/31] reformat code --- .../core/annotation/RvmBinderDslMarker.kt | 7 ++++++- .../reactiveviewmodel/core/annotation/RvmDslMarker.kt | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/annotation/RvmBinderDslMarker.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/annotation/RvmBinderDslMarker.kt index 053569e..f990462 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/annotation/RvmBinderDslMarker.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/annotation/RvmBinderDslMarker.kt @@ -1,5 +1,10 @@ package com.alexdeww.reactiveviewmodel.core.annotation @DslMarker -@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPEALIAS, AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) +@Target( + AnnotationTarget.CLASS, + AnnotationTarget.TYPEALIAS, + AnnotationTarget.TYPE, + AnnotationTarget.FUNCTION +) annotation class RvmBinderDslMarker diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/annotation/RvmDslMarker.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/annotation/RvmDslMarker.kt index 35adf96..6ee4350 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/annotation/RvmDslMarker.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/annotation/RvmDslMarker.kt @@ -1,5 +1,10 @@ package com.alexdeww.reactiveviewmodel.core.annotation @DslMarker -@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPEALIAS, AnnotationTarget.TYPE, AnnotationTarget.FUNCTION) +@Target( + AnnotationTarget.CLASS, + AnnotationTarget.TYPEALIAS, + AnnotationTarget.TYPE, + AnnotationTarget.FUNCTION +) annotation class RvmDslMarker From 68b47ff1636c4e20fa1a32ed1f6f1b61c860141b Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 5 Jan 2023 08:33:37 +0700 Subject: [PATCH 05/31] add RVM context for creation properties and widgets --- .../alexdeww/reactiveviewmodel/core/RVM.kt | 3 ++ .../core/RvmPropertiesSupport.kt | 21 ++++---- .../core/RvmViewModelComponent.kt | 49 +++++++++++++------ .../core/RvmWidgetsSupport.kt | 2 +- .../core/utils/RvmPropertyReadOnlyDelegate.kt | 5 +- .../widget/BaseVisualControl.kt | 9 ++-- .../reactiveviewmodel/widget/CheckControl.kt | 7 +-- .../reactiveviewmodel/widget/DialogControl.kt | 15 ++---- .../widget/DisplayableControl.kt | 4 +- .../reactiveviewmodel/widget/InputControl.kt | 4 +- .../reactiveviewmodel/widget/RatingControl.kt | 3 +- 11 files changed, 69 insertions(+), 53 deletions(-) create mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RVM.kt diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RVM.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RVM.kt new file mode 100644 index 0000000..49535c7 --- /dev/null +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RVM.kt @@ -0,0 +1,3 @@ +package com.alexdeww.reactiveviewmodel.core + +object RVM diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt index dc3a542..9972e28 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt @@ -8,7 +8,6 @@ import com.alexdeww.reactiveviewmodel.core.property.RvmState import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyReadOnlyDelegate import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.functions.Consumer -import java.util.* import kotlin.properties.ReadOnlyProperty @RvmDslMarker @@ -44,7 +43,7 @@ interface RvmPropertiesSupport { @Suppress("unused") @RvmDslMarker -fun RvmPropertiesSupport.state( +fun RVM.state( initValue: T? = null, debounceInterval: Long? = null ): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( @@ -53,7 +52,7 @@ fun RvmPropertiesSupport.state( @Suppress("unused") @RvmDslMarker -fun RvmPropertiesSupport.progressState( +fun RVM.progressState( initValue: Boolean? = null, debounceInterval: Long = DEF_PROGRESS_DEBOUNCE_INTERVAL ): ReadOnlyProperty> = state( @@ -63,7 +62,7 @@ fun RvmPropertiesSupport.progressState( @Suppress("unused") @RvmDslMarker -fun RvmPropertiesSupport.event( +fun RVM.event( debounceInterval: Long? = null ): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( property = RvmEvent(debounceInterval) @@ -71,7 +70,7 @@ fun RvmPropertiesSupport.event( @Suppress("unused") @RvmDslMarker -fun RvmPropertiesSupport.eventNone( +fun RVM.eventNone( debounceInterval: Long? = null ): ReadOnlyProperty> = event( debounceInterval = debounceInterval @@ -79,7 +78,7 @@ fun RvmPropertiesSupport.eventNone( @Suppress("unused") @RvmDslMarker -fun RvmPropertiesSupport.confirmationEvent( +fun RVM.confirmationEvent( debounceInterval: Long? = null ): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( property = RvmConfirmationEvent(debounceInterval) @@ -87,7 +86,7 @@ fun RvmPropertiesSupport.confirmationEvent( @Suppress("unused") @RvmDslMarker -fun RvmPropertiesSupport.confirmationEventNone( +fun RVM.confirmationEventNone( debounceInterval: Long? = null ): ReadOnlyProperty> = confirmationEvent( debounceInterval = debounceInterval @@ -95,7 +94,7 @@ fun RvmPropertiesSupport.confirmationEventNone( @Suppress("unused") @RvmDslMarker -fun RvmPropertiesSupport.action( +fun RVM.action( debounceInterval: Long? = null ): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( property = RvmAction(debounceInterval) @@ -103,7 +102,7 @@ fun RvmPropertiesSupport.action( @Suppress("unused") @RvmDslMarker -fun RvmPropertiesSupport.actionNone( +fun RVM.actionNone( debounceInterval: Long? = null ): ReadOnlyProperty> = action( debounceInterval = debounceInterval @@ -111,7 +110,7 @@ fun RvmPropertiesSupport.actionNone( @Suppress("unused") @RvmDslMarker -fun RvmPropertiesSupport.debouncedAction( +fun RVM.debouncedAction( debounceInterval: Long = DEF_ACTION_DEBOUNCE_INTERVAL ): ReadOnlyProperty> = action( debounceInterval = debounceInterval @@ -119,7 +118,7 @@ fun RvmPropertiesSupport.debouncedAction( @Suppress("unused") @RvmDslMarker -fun RvmPropertiesSupport.debouncedActionNone( +fun RVM.debouncedActionNone( debounceInterval: Long = DEF_ACTION_DEBOUNCE_INTERVAL ): ReadOnlyProperty> = action( debounceInterval = debounceInterval diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt index 62ca640..2b191f3 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt @@ -7,6 +7,8 @@ import com.alexdeww.reactiveviewmodel.core.property.RvmState import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.subjects.BehaviorSubject +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty @RvmBinderDslMarker interface RvmViewModelComponent : RvmPropertiesSupport, RvmWidgetsSupport, @@ -35,21 +37,10 @@ interface RvmViewModelComponent : RvmPropertiesSupport, RvmWidgetsSupport, @Suppress("unused") @RvmDslMarker -fun RvmViewModelComponent.invocable( +fun RVM.invocable( block: (params: T) -> Completable -): Lazy> = lazy { - val action = RvmAction() - val isExecuteSubj: BehaviorSubject = BehaviorSubject.createDefault(false) - action bind { - this.switchMapCompletable { params -> block(params).bindProgress(isExecuteSubj::onNext) } - .toObservable() - } - object : RvmViewModelComponent.Invocable { - override val isExecute: Boolean get() = isExecuteSubj.value ?: false - override val isExecuteObservable: Observable = isExecuteSubj.serialize() - override fun invoke(params: T) = action.consumer.accept(params) - } -} +): ReadOnlyProperty> = + InvocableDelegate(block) internal fun RvmViewModelComponent.bindAction( action: RvmAction, @@ -74,3 +65,33 @@ internal fun RvmViewModelComponent.bindState( .subscribe() .autoDispose() } + +private class InvocableDelegate( + private val block: (params: T) -> Completable +) : ReadOnlyProperty> { + + private var value: RvmViewModelComponent.Invocable? = null + + override fun getValue( + thisRef: RvmViewModelComponent, + property: KProperty<*> + ): RvmViewModelComponent.Invocable { + if (value == null) { + val action = RvmAction() + val isExecuteSubj: BehaviorSubject = BehaviorSubject.createDefault(false) + thisRef.run { + action bind { + this.switchMapCompletable { params -> block(params).bindProgress(isExecuteSubj::onNext) } + .toObservable() + } + } + value = object : RvmViewModelComponent.Invocable { + override val isExecute: Boolean get() = isExecuteSubj.value ?: false + override val isExecuteObservable: Observable = isExecuteSubj.serialize() + override fun invoke(params: T) = action.consumer.accept(params) + } + } + return value!! + } + +} diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt index 74d1cda..652c59d 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt @@ -1,3 +1,3 @@ package com.alexdeww.reactiveviewmodel.core -interface RvmWidgetsSupport : RvmPropertiesSupport +interface RvmWidgetsSupport diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyReadOnlyDelegate.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyReadOnlyDelegate.kt index d005610..5731db5 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyReadOnlyDelegate.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyReadOnlyDelegate.kt @@ -1,11 +1,10 @@ package com.alexdeww.reactiveviewmodel.core.utils -import com.alexdeww.reactiveviewmodel.core.RvmPropertiesSupport import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty class RvmPropertyReadOnlyDelegate

( private val property: P -) : ReadOnlyProperty { - override fun getValue(thisRef: RvmPropertiesSupport, property: KProperty<*>): P = this.property +) : ReadOnlyProperty { + override fun getValue(thisRef: Any, property: KProperty<*>): P = this.property } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt index 341763b..b9c3354 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt @@ -3,6 +3,7 @@ package com.alexdeww.reactiveviewmodel.widget import android.view.View import androidx.annotation.CallSuper import androidx.lifecycle.MediatorLiveData +import com.alexdeww.reactiveviewmodel.core.RVM import com.alexdeww.reactiveviewmodel.core.RvmViewComponent import com.alexdeww.reactiveviewmodel.core.action import com.alexdeww.reactiveviewmodel.core.state @@ -54,11 +55,11 @@ abstract class BaseVisualControl GONE(View.GONE) } - val value by state(initialValue) - val enabled by state(initialEnabled) - val visibility by state(initialVisibility) + val value by RVM.state(initialValue) + val enabled by RVM.state(initialEnabled) + val visibility by RVM.state(initialVisibility) - val actionChangeValue by action() + val actionChangeValue by RVM.action() init { actionChangeValue.observable diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt index 2f1f2ba..2bc57c8 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt @@ -2,12 +2,9 @@ package com.alexdeww.reactiveviewmodel.widget import android.widget.CompoundButton import androidx.lifecycle.SavedStateHandle -import com.alexdeww.reactiveviewmodel.core.RvmViewComponent -import com.alexdeww.reactiveviewmodel.core.RvmViewModelComponent -import com.alexdeww.reactiveviewmodel.core.RvmWidgetsSupport +import com.alexdeww.reactiveviewmodel.core.* import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker -import com.alexdeww.reactiveviewmodel.core.delegate import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyReadOnlyDelegate import kotlin.properties.ReadOnlyProperty @@ -55,7 +52,7 @@ class CheckControl internal constructor( @Suppress("unused") @RvmDslMarker -fun RvmWidgetsSupport.checkControl( +fun RVM.checkControl( initialChecked: Boolean = false, initialEnabled: Boolean = true, initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt index fee2a2b..c1593c9 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt @@ -2,13 +2,10 @@ package com.alexdeww.reactiveviewmodel.widget import android.app.Dialog import androidx.lifecycle.MediatorLiveData -import com.alexdeww.reactiveviewmodel.core.RvmViewComponent -import com.alexdeww.reactiveviewmodel.core.RvmWidgetsSupport -import com.alexdeww.reactiveviewmodel.core.action +import com.alexdeww.reactiveviewmodel.core.* import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyReadOnlyDelegate -import com.alexdeww.reactiveviewmodel.core.state import io.reactivex.rxjava3.core.Maybe import kotlin.properties.ReadOnlyProperty @@ -25,9 +22,9 @@ class DialogControl internal constructor() : object Absent : Display() } - internal val result by action() + internal val result by RVM.action() - val displayed by state>(Display.Absent) + val displayed by RVM.state>(Display.Absent) val isShowing get() = displayed.value is Display.Displayed fun show(data: T) { @@ -103,14 +100,12 @@ class DialogControlResult internal constructor( @Suppress("unused") @RvmDslMarker -fun RvmWidgetsSupport.dialogControl(): ReadOnlyProperty< - RvmWidgetsSupport, DialogControl> = +fun RVM.dialogControl(): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate(property = DialogControl()) @Suppress("unused") @RvmDslMarker -fun RvmWidgetsSupport.dialogControlWithResult(): ReadOnlyProperty< - RvmWidgetsSupport, DialogControl> = +fun RVM.dialogControlWithResult(): ReadOnlyProperty> = dialogControl() typealias DialogCreator = (data: T, dc: DialogControlResult) -> D diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt index 740cb2f..e0968b4 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt @@ -29,7 +29,7 @@ class DisplayableControl internal constructor( } - val action by state>(Action.Hide, debounceInterval) + val action by RVM.state>(Action.Hide, debounceInterval) val isShowing get() = action.value?.isShowing ?: false val showingValue: T? get() = action.value?.getShowingValue() @@ -77,7 +77,7 @@ class DisplayableControl internal constructor( @Suppress("unused") @RvmDslMarker -fun RvmWidgetsSupport.displayableControl( +fun RVM.displayableControl( debounceInterval: Long? = null ): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( property = DisplayableControl(debounceInterval) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt index 34dbe55..e9531db 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt @@ -29,7 +29,7 @@ class InputControl internal constructor( value.valueChangesHook = formatter } - val error by state() + val error by RVM.state() override fun onChangedValue(newValue: String) { if (hideErrorOnUserInput) error.consumer.accept("") @@ -115,7 +115,7 @@ class InputControl internal constructor( @Suppress("unused") @RvmDslMarker -fun RvmWidgetsSupport.inputControl( +fun RVM.inputControl( initialText: String = "", hideErrorOnUserInput: Boolean = true, formatter: FormatterAction? = null, diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt index c4ff2e6..a502b05 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.widget.RatingBar import androidx.lifecycle.SavedStateHandle import com.alexdeww.reactiveviewmodel.component.ReactiveViewModel +import com.alexdeww.reactiveviewmodel.core.RVM import com.alexdeww.reactiveviewmodel.core.RvmViewComponent import com.alexdeww.reactiveviewmodel.core.RvmWidgetsSupport import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker @@ -55,7 +56,7 @@ class RatingControl internal constructor( @Suppress("unused") @RvmDslMarker -fun RvmWidgetsSupport.ratingControl( +fun RVM.ratingControl( initialValue: Float = 0f, initialEnabled: Boolean = true, initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE From 7829fd61da3ba6e3e742d475ba250e639d247cb7 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 5 Jan 2023 09:19:20 +0700 Subject: [PATCH 06/31] rename "value" property to "data" on BaseVisualControl --- .../widget/BaseVisualControl.kt | 20 +++++++++---------- .../reactiveviewmodel/widget/CheckControl.kt | 2 +- .../reactiveviewmodel/widget/InputControl.kt | 8 ++++---- .../reactiveviewmodel/widget/RatingControl.kt | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt index b9c3354..10fa117 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt @@ -55,21 +55,21 @@ abstract class BaseVisualControl GONE(View.GONE) } - val value by RVM.state(initialValue) + val data by RVM.state(initialValue) val enabled by RVM.state(initialEnabled) val visibility by RVM.state(initialVisibility) - val actionChangeValue by RVM.action() + val actionChangeDataValue by RVM.action() init { - actionChangeValue.observable - .filter { it != value.value } - .subscribe(::onChangedValue) + actionChangeDataValue.observable + .filter { it != data.value } + .subscribe(::onDataValueChanged) } @CallSuper - protected open fun onChangedValue(newValue: T) { - value.consumer.accept(newValue) + protected open fun onDataValueChanged(newValue: T) { + data.consumer.accept(newValue) } } @@ -92,7 +92,7 @@ class VisualControlLiveDataMediator internal constructor( private var isEditing: Boolean = false val changeValueConsumer = Consumer { - if (!isEditing) this.control?.actionChangeValue?.call(it) + if (!isEditing) this.control?.actionChangeDataValue?.call(it) } override fun onActive() { @@ -100,7 +100,7 @@ class VisualControlLiveDataMediator internal constructor( control?.apply { if (bindEnable) addSource(enabled.liveData) { view?.isEnabled = it } if (bindVisible) addSource(visibility.liveData) { view?.visibility = it.value } - addSource(value.liveData) { newValue -> + addSource(data.liveData) { newValue -> isEditing = true onValueChanged(newValue) isEditing = false @@ -113,7 +113,7 @@ class VisualControlLiveDataMediator internal constructor( control?.apply { if (bindEnable) removeSource(enabled.liveData) if (bindVisible) removeSource(visibility.liveData) - removeSource(value.liveData) + removeSource(data.liveData) } onInactiveAction.invoke(this) super.onInactive() diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt index 2bc57c8..1944f0d 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt @@ -79,7 +79,7 @@ fun SavedStateHandle.checkControl( initialVisibility = stateHandle[visibilityKey] ?: initialVisibility ) thisRef.run { - control.value.viewFlowable + control.data.viewFlowable .subscribe { stateHandle[checkedKey] = it } .autoDispose() control.enabled.viewFlowable diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt index e9531db..55cfcf6 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt @@ -26,14 +26,14 @@ class InputControl internal constructor( ) { init { - value.valueChangesHook = formatter + data.valueChangesHook = formatter } val error by RVM.state() - override fun onChangedValue(newValue: String) { + override fun onDataValueChanged(newValue: String) { if (hideErrorOnUserInput) error.consumer.accept("") - super.onChangedValue(newValue) + super.onDataValueChanged(newValue) } override fun getBinder(rvmViewComponent: RvmViewComponent): Binder = Binder(rvmViewComponent) @@ -150,7 +150,7 @@ fun SavedStateHandle.inputControl( initialVisibility = stateHandle[visibilityKey] ?: initialVisibility ) thisRef.run { - control.value.viewFlowable + control.data.viewFlowable .subscribe { stateHandle[textKey] = it } .autoDispose() control.enabled.viewFlowable diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt index a502b05..f13d76f 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt @@ -83,7 +83,7 @@ fun SavedStateHandle.ratingControl( initialVisibility = stateHandle[visibilityKey] ?: initialVisibility ) thisRef.run { - control.value.viewFlowable + control.data.viewFlowable .subscribe { stateHandle[ratingKey] = it } .autoDispose() control.enabled.viewFlowable From 3fc3fdbc5634e89e9e48efcb68e626109ef3511a Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 5 Jan 2023 10:44:20 +0700 Subject: [PATCH 07/31] add projection for state --- .../reactiveviewmodel/core/RvmExtensions.kt | 5 +++ .../core/RvmPropertiesSupport.kt | 14 ++++++++ .../core/RvmViewComponent.kt | 4 +++ .../core/property/RvmAction.kt | 2 +- .../core/property/RvmEvent.kt | 2 +- .../core/property/RvmState.kt | 33 ++++++++++++++++--- 6 files changed, 54 insertions(+), 6 deletions(-) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmExtensions.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmExtensions.kt index 6ef630f..64943f5 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmExtensions.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmExtensions.kt @@ -36,6 +36,11 @@ fun RvmState.observe( action: OnLiveDataAction ): Observer = liveData.observe(owner = owner, action = action) +fun RvmState<*>.Projection.observe( + owner: LifecycleOwner, + action: OnLiveDataAction +): Observer = liveData.observe(owner = owner, action = action) + fun RvmAction.call() = call(Unit) typealias ActionOnClick = () -> Unit diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt index 9972e28..9b24a12 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt @@ -21,6 +21,20 @@ interface RvmPropertiesSupport { if (this.value != value) setValue(value) } + @RvmDslMarker + fun RvmState.projectionEx( + distinctUntilChanged: Boolean = true, + projectionBlock: (value: T, consumer: Consumer) -> Unit + ): ReadOnlyProperty.Projection> = + RvmPropertyReadOnlyDelegate(property = Projection(distinctUntilChanged, projectionBlock)) + + @RvmDslMarker + fun RvmState.projection( + distinctUntilChanged: Boolean = true, + mapBlock: (value: T) -> R + ): ReadOnlyProperty.Projection> = + projectionEx(distinctUntilChanged) { value, consumer -> consumer.accept(mapBlock(value)) } + // Event val RvmEvent.consumer: Consumer get() = this.consumer diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt index 2cf0f2f..4d91df4 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt @@ -31,6 +31,10 @@ interface RvmViewComponent : RvmAutoDisposableSupport { action: OnLiveDataAction ): Observer = observe(componentLifecycleOwner, action) + fun RvmState<*>.Projection.observe( + action: OnLiveDataAction + ): Observer = observe(componentLifecycleOwner, action) + fun RvmEvent.observe( action: OnLiveDataAction ): Observer = observe(componentLifecycleOwner, action) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmAction.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmAction.kt index 10694a5..157604a 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmAction.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmAction.kt @@ -14,4 +14,4 @@ class RvmAction internal constructor(debounceInterval: Long? = null) { fun call(value: T) = consumer.accept(value) -} \ No newline at end of file +} diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmEvent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmEvent.kt index 46b4dc7..502cef5 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmEvent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmEvent.kt @@ -51,4 +51,4 @@ class RvmEvent internal constructor(debounceInterval: Long? = null) { } -} \ No newline at end of file +} diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt index 2fb223b..60e619a 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt @@ -28,17 +28,42 @@ class RvmState internal constructor( val valueNonNull: T get() = value!! val hasValue: Boolean get() = value != null - val liveData: RvmLiveData by lazy { StateLiveData() } val viewFlowable: Flowable by lazy { observable.toViewFlowable() } + val liveData: RvmLiveData by lazy { StateLiveData(viewFlowable) } fun getValueOrDef(actionDefValue: () -> T): T = value ?: actionDefValue() fun getValueOrDef(defValue: T): T = getValueOrDef { defValue } + inner class Projection internal constructor( + distinctUntilChanged: Boolean, + projectionBlock: (value: T, consumer: Consumer) -> Unit + ) { + private val projectionSubject = BehaviorSubject.create() + private val projectionSource = projectionSubject.run { + if (distinctUntilChanged) distinctUntilChanged() + else this + } + + val value: R? get() = projectionSubject.value + val valueNonNull: R get() = value!! + val hasValue: Boolean get() = value != null + + val viewFlowable: Flowable by lazy { projectionSource.toViewFlowable() } + val liveData: RvmLiveData by lazy { StateLiveData(viewFlowable) } + + fun getValueOrDef(actionDefValue: () -> R): R = value ?: actionDefValue() + fun getValueOrDef(defValue: R): R = getValueOrDef { defValue } + + init { + observable.subscribe { projectionBlock(it, projectionSubject::onNext) } + } + } + @SuppressLint("CheckResult") - private inner class StateLiveData : RvmLiveData() { + private class StateLiveData(source: Flowable) : RvmLiveData() { init { - viewFlowable.subscribe { value = it } + source.subscribe { value = it } } } -} \ No newline at end of file +} From bfbefdeaef74bdf122e67c4d1a392ee8f53e028f Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 5 Jan 2023 20:15:51 +0700 Subject: [PATCH 08/31] autoDispose refactoring --- .../component/ReactiveActivity.kt | 15 ++++++++++----- .../component/ReactiveFragment.kt | 18 ++++++++++++------ .../component/ReactiveViewModel.kt | 15 +++++++++++---- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveActivity.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveActivity.kt index 8066689..ef39b61 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveActivity.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveActivity.kt @@ -4,25 +4,30 @@ import androidx.annotation.CallSuper import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.LifecycleOwner import com.alexdeww.reactiveviewmodel.core.DefaultRvmDisposableStore -import com.alexdeww.reactiveviewmodel.core.RvmAutoDisposableStore +import com.alexdeww.reactiveviewmodel.core.RvmAutoDisposableSupport import com.alexdeww.reactiveviewmodel.core.RvmViewComponent import com.alexdeww.reactiveviewmodel.widget.RvmWidgetBindShortcut import io.reactivex.rxjava3.disposables.Disposable -abstract class ReactiveActivity : AppCompatActivity(), RvmViewComponent, RvmWidgetBindShortcut, - RvmAutoDisposableStore by DefaultRvmDisposableStore() { +abstract class ReactiveActivity : AppCompatActivity(), RvmViewComponent, RvmWidgetBindShortcut { + private val rvmAutoDisposableStore by lazy { DefaultRvmDisposableStore() } override val componentLifecycleOwner: LifecycleOwner get() = this + final override fun Disposable.autoDispose( + tagKey: String, + storeKey: RvmAutoDisposableSupport.StoreKey? + ) = rvmAutoDisposableStore.run { this@autoDispose.autoDispose(tagKey, storeKey) } + @CallSuper override fun onStop() { - dispose(RvmViewComponent.onStopStoreKey) + rvmAutoDisposableStore.dispose(RvmViewComponent.onStopStoreKey) super.onStop() } @CallSuper override fun onDestroy() { - dispose() + rvmAutoDisposableStore.dispose() super.onDestroy() } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveFragment.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveFragment.kt index d609efe..9fad061 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveFragment.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveFragment.kt @@ -5,32 +5,38 @@ import androidx.annotation.LayoutRes import androidx.fragment.app.Fragment import androidx.lifecycle.LifecycleOwner import com.alexdeww.reactiveviewmodel.core.DefaultRvmDisposableStore -import com.alexdeww.reactiveviewmodel.core.RvmAutoDisposableStore +import com.alexdeww.reactiveviewmodel.core.RvmAutoDisposableSupport import com.alexdeww.reactiveviewmodel.core.RvmViewComponent import com.alexdeww.reactiveviewmodel.widget.RvmWidgetBindShortcut +import io.reactivex.rxjava3.disposables.Disposable abstract class ReactiveFragment( @LayoutRes layoutId: Int = 0 -) : Fragment(layoutId), RvmViewComponent, RvmWidgetBindShortcut, - RvmAutoDisposableStore by DefaultRvmDisposableStore() { +) : Fragment(layoutId), RvmViewComponent, RvmWidgetBindShortcut { + private val rvmAutoDisposableStore by lazy { DefaultRvmDisposableStore() } override val componentLifecycleOwner: LifecycleOwner get() = viewLifecycleOwner + final override fun Disposable.autoDispose( + tagKey: String, + storeKey: RvmAutoDisposableSupport.StoreKey? + ) = rvmAutoDisposableStore.run { this@autoDispose.autoDispose(tagKey, storeKey) } + @CallSuper override fun onStop() { - dispose(RvmViewComponent.onStopStoreKey) + rvmAutoDisposableStore.dispose(RvmViewComponent.onStopStoreKey) super.onStop() } @CallSuper override fun onDestroyView() { - dispose(RvmViewComponent.onDestroyViewStoreKey) + rvmAutoDisposableStore.dispose(RvmViewComponent.onDestroyViewStoreKey) super.onDestroyView() } @CallSuper override fun onDestroy() { - dispose() + rvmAutoDisposableStore.dispose() super.onDestroy() } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt index 7718807..63055aa 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt @@ -3,20 +3,27 @@ package com.alexdeww.reactiveviewmodel.component import androidx.annotation.CallSuper import androidx.lifecycle.ViewModel import com.alexdeww.reactiveviewmodel.core.DefaultRvmDisposableStore -import com.alexdeww.reactiveviewmodel.core.RvmAutoDisposableStore +import com.alexdeww.reactiveviewmodel.core.RvmAutoDisposableSupport import com.alexdeww.reactiveviewmodel.core.RvmViewModelComponent +import io.reactivex.rxjava3.disposables.Disposable /** * Based on RxPM * https://github.com/dmdevgo/RxPM */ -abstract class ReactiveViewModel : ViewModel(), RvmViewModelComponent, - RvmAutoDisposableStore by DefaultRvmDisposableStore() { +abstract class ReactiveViewModel : ViewModel(), RvmViewModelComponent { + + private val rvmAutoDisposableStore by lazy { DefaultRvmDisposableStore() } + + final override fun Disposable.autoDispose( + tagKey: String, + storeKey: RvmAutoDisposableSupport.StoreKey? + ) = rvmAutoDisposableStore.run { this@autoDispose.autoDispose(tagKey, storeKey) } @CallSuper override fun onCleared() { - dispose() + rvmAutoDisposableStore.dispose() super.onCleared() } From d11c2b95b504e02a71db0d2af02a71528a709b35 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 6 Jan 2023 05:27:36 +0700 Subject: [PATCH 09/31] remove useless RvmWidgetsSupport.kt --- .../reactiveviewmodel/core/RvmViewModelComponent.kt | 3 +-- .../reactiveviewmodel/core/RvmWidgetsSupport.kt | 3 --- .../alexdeww/reactiveviewmodel/widget/CheckControl.kt | 2 +- .../alexdeww/reactiveviewmodel/widget/DialogControl.kt | 4 ++-- .../reactiveviewmodel/widget/DisplayableControl.kt | 2 +- .../alexdeww/reactiveviewmodel/widget/InputControl.kt | 2 +- .../alexdeww/reactiveviewmodel/widget/RatingControl.kt | 10 +++------- 7 files changed, 9 insertions(+), 17 deletions(-) delete mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt index 2b191f3..893c0c1 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt @@ -11,8 +11,7 @@ import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty @RvmBinderDslMarker -interface RvmViewModelComponent : RvmPropertiesSupport, RvmWidgetsSupport, - RvmAutoDisposableSupport { +interface RvmViewModelComponent : RvmPropertiesSupport, RvmAutoDisposableSupport { interface Invocable { val isExecute: Boolean diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt deleted file mode 100644 index 652c59d..0000000 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmWidgetsSupport.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.alexdeww.reactiveviewmodel.core - -interface RvmWidgetsSupport diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt index 1944f0d..aa7de61 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt @@ -56,7 +56,7 @@ fun RVM.checkControl( initialChecked: Boolean = false, initialEnabled: Boolean = true, initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = RvmPropertyReadOnlyDelegate( +): ReadOnlyProperty = RvmPropertyReadOnlyDelegate( property = CheckControl( initialChecked = initialChecked, initialEnabled = initialEnabled, diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt index c1593c9..71078dd 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt @@ -100,12 +100,12 @@ class DialogControlResult internal constructor( @Suppress("unused") @RvmDslMarker -fun RVM.dialogControl(): ReadOnlyProperty> = +fun RVM.dialogControl(): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate(property = DialogControl()) @Suppress("unused") @RvmDslMarker -fun RVM.dialogControlWithResult(): ReadOnlyProperty> = +fun RVM.dialogControlWithResult(): ReadOnlyProperty> = dialogControl() typealias DialogCreator = (data: T, dc: DialogControlResult) -> D diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt index e0968b4..7e1d207 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt @@ -79,7 +79,7 @@ class DisplayableControl internal constructor( @RvmDslMarker fun RVM.displayableControl( debounceInterval: Long? = null -): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( +): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( property = DisplayableControl(debounceInterval) ) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt index 55cfcf6..66df338 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt @@ -121,7 +121,7 @@ fun RVM.inputControl( formatter: FormatterAction? = null, initialEnabled: Boolean = true, initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = RvmPropertyReadOnlyDelegate( +): ReadOnlyProperty = RvmPropertyReadOnlyDelegate( property = InputControl( initialText = initialText, hideErrorOnUserInput = hideErrorOnUserInput, diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt index f13d76f..bd6ce3a 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt @@ -3,13 +3,9 @@ package com.alexdeww.reactiveviewmodel.widget import android.annotation.SuppressLint import android.widget.RatingBar import androidx.lifecycle.SavedStateHandle -import com.alexdeww.reactiveviewmodel.component.ReactiveViewModel -import com.alexdeww.reactiveviewmodel.core.RVM -import com.alexdeww.reactiveviewmodel.core.RvmViewComponent -import com.alexdeww.reactiveviewmodel.core.RvmWidgetsSupport +import com.alexdeww.reactiveviewmodel.core.* import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker -import com.alexdeww.reactiveviewmodel.core.delegate import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyReadOnlyDelegate import kotlin.properties.ReadOnlyProperty @@ -60,7 +56,7 @@ fun RVM.ratingControl( initialValue: Float = 0f, initialEnabled: Boolean = true, initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = RvmPropertyReadOnlyDelegate( +): ReadOnlyProperty = RvmPropertyReadOnlyDelegate( property = RatingControl( initialValue = initialValue, initialEnabled = initialEnabled, @@ -73,7 +69,7 @@ fun SavedStateHandle.ratingControl( initialValue: Float = 0f, initialEnabled: Boolean = true, initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = delegate { thisRef, stateHandle, key -> +): ReadOnlyProperty = delegate { thisRef, stateHandle, key -> val ratingKey = "$key.rating" val enabledKey = "$key.enabled" val visibilityKey = "$key.visibility" From e653bf3bba0c959f3b4754ab01c7a4ff282517ca Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 6 Jan 2023 07:01:36 +0700 Subject: [PATCH 10/31] up kotlin --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1527b73..12a1fa4 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { min_sdk_version = 17 sdk_version = 31 - kotlin_version = '1.7.0' + kotlin_version = '1.7.20' rxjava_version = '3.1.2' rxandroid_version = '3.0.0' archx_version = '2.2.0' From 65626c819449860635ab41e722d86f4ef9176741 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 6 Jan 2023 08:22:40 +0700 Subject: [PATCH 11/31] reduce rvm property extensions --- .../reactiveviewmodel/core/RvmExtensions.kt | 48 ++++-------------- .../core/RvmPropertiesSupport.kt | 49 +++++++++---------- .../core/RvmViewComponent.kt | 26 ++-------- .../core/property/RvmAction.kt | 9 ++-- .../core/property/RvmConfirmationEvent.kt | 12 +++-- .../core/property/RvmEvent.kt | 15 +++--- .../core/property/RvmProperty.kt | 32 ++++++++++++ .../core/property/RvmState.kt | 42 ++++++---------- 8 files changed, 103 insertions(+), 130 deletions(-) create mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmProperty.kt diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmExtensions.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmExtensions.kt index 64943f5..c10a28f 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmExtensions.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmExtensions.kt @@ -5,9 +5,8 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.Observer import com.alexdeww.reactiveviewmodel.core.property.RvmAction -import com.alexdeww.reactiveviewmodel.core.property.RvmConfirmationEvent -import com.alexdeww.reactiveviewmodel.core.property.RvmEvent -import com.alexdeww.reactiveviewmodel.core.property.RvmState +import com.alexdeww.reactiveviewmodel.core.property.RvmObservableProperty +import com.alexdeww.reactiveviewmodel.core.property.RvmPropertyInternal import io.reactivex.rxjava3.core.* import io.reactivex.rxjava3.functions.Consumer import io.reactivex.rxjava3.functions.Function @@ -21,22 +20,7 @@ fun LiveData.observe(owner: LifecycleOwner, action: OnLiveDataAction): return observer } -fun RvmEvent.observe( - owner: LifecycleOwner, - action: OnLiveDataAction -): Observer = liveData.observe(owner = owner, action = action) - -fun RvmConfirmationEvent.observe( - owner: LifecycleOwner, - action: OnLiveDataAction -): Observer = liveData.observe(owner = owner, action = action) - -fun RvmState.observe( - owner: LifecycleOwner, - action: OnLiveDataAction -): Observer = liveData.observe(owner = owner, action = action) - -fun RvmState<*>.Projection.observe( +fun RvmObservableProperty.observe( owner: LifecycleOwner, action: OnLiveDataAction ): Observer = liveData.observe(owner = owner, action = action) @@ -137,38 +121,24 @@ fun Completable.bindProgressAny(progressConsumer: Consumer): Completabl } fun Observable.untilOn( - vararg rvmAction: RvmAction -): Observable = takeUntil(Observable.merge(rvmAction.map { it.observable })) - -fun Observable.untilOn( - vararg event: RvmEvent -): Observable = takeUntil(Observable.merge(event.map { it.observable })) + vararg rvmProperty: RvmPropertyInternal<*> +): Observable = takeUntil(Observable.merge(rvmProperty.map { it.observable })) fun Observable.untilOn( vararg observable: Observable<*> ): Observable = takeUntil(Observable.merge(observable.toList())) fun Maybe.untilOn( - vararg action: RvmAction<*> -): Maybe = takeUntil(Maybe.merge(action.map { it.observable.firstElement() })) - -fun Maybe.untilOn( - vararg event: RvmEvent<*> -): Maybe = takeUntil(Maybe.merge(event.map { it.observable.firstElement() })) + vararg rvmProperty: RvmPropertyInternal<*> +): Maybe = takeUntil(Maybe.merge(rvmProperty.map { it.observable.firstElement() })) fun Maybe.untilOn( vararg maybe: Maybe<*> ): Maybe = takeUntil(Maybe.merge(maybe.toList())) fun Completable.untilOn( - vararg action: RvmAction<*> -): Completable = takeUntil(Completable.merge(action.map { - it.observable.firstElement().ignoreElement() -})) - -fun Completable.untilOn( - vararg event: RvmEvent<*> -): Completable = takeUntil(Completable.merge(event.map { + vararg rvmProperty: RvmPropertyInternal<*> +): Completable = takeUntil(Completable.merge(rvmProperty.map { it.observable.firstElement().ignoreElement() })) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt index 9b24a12..b8c6b76 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt @@ -1,10 +1,7 @@ package com.alexdeww.reactiveviewmodel.core import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker -import com.alexdeww.reactiveviewmodel.core.property.RvmAction -import com.alexdeww.reactiveviewmodel.core.property.RvmConfirmationEvent -import com.alexdeww.reactiveviewmodel.core.property.RvmEvent -import com.alexdeww.reactiveviewmodel.core.property.RvmState +import com.alexdeww.reactiveviewmodel.core.property.* import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyReadOnlyDelegate import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.functions.Consumer @@ -13,14 +10,30 @@ import kotlin.properties.ReadOnlyProperty @RvmDslMarker interface RvmPropertiesSupport { - // State - val RvmState.consumer: Consumer get() = this.consumer - val RvmState.observable: Observable get() = this.observable - fun RvmState.setValue(value: T) = consumer.accept(value) - fun RvmState.setValueIfChanged(value: T) { + // common + val RvmProperty.consumer: Consumer get() = this.consumer + val RvmProperty.observable: Observable get() = this.observable + + + // callable property + fun R.call(value: T) where R : RvmCallableProperty, + R : RvmProperty = consumer.accept(value) + + fun R.call() where R : RvmCallableProperty, + R : RvmProperty = call(Unit) + + + // mutable property + fun R.setValue(value: T) where R : RvmMutableValueProperty, + R : RvmProperty = consumer.accept(value) + + fun R.setValueIfChanged(value: T) where R : RvmMutableValueProperty, + R : RvmProperty { if (this.value != value) setValue(value) } + + // state @RvmDslMarker fun RvmState.projectionEx( distinctUntilChanged: Boolean = true, @@ -35,24 +48,6 @@ interface RvmPropertiesSupport { ): ReadOnlyProperty.Projection> = projectionEx(distinctUntilChanged) { value, consumer -> consumer.accept(mapBlock(value)) } - - // Event - val RvmEvent.consumer: Consumer get() = this.consumer - val RvmEvent.observable: Observable get() = this.observable - fun RvmEvent.call(value: T) = this.consumer.accept(value) - fun RvmEvent.call() = call(Unit) - - - // ConfirmationEvent - val RvmConfirmationEvent.consumer: Consumer get() = this.consumer - val RvmConfirmationEvent.observable: Observable get() = this.observable - fun RvmConfirmationEvent.call(value: T) = this.consumer.accept(value) - fun RvmConfirmationEvent.call() = call(Unit) - - - // Action - val RvmAction.observable: Observable get() = this.observable - } @Suppress("unused") diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt index 4d91df4..cdf71dd 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt @@ -4,9 +4,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.Observer import com.alexdeww.reactiveviewmodel.core.RvmAutoDisposableSupport.StoreKey -import com.alexdeww.reactiveviewmodel.core.property.RvmConfirmationEvent -import com.alexdeww.reactiveviewmodel.core.property.RvmEvent -import com.alexdeww.reactiveviewmodel.core.property.RvmState +import com.alexdeww.reactiveviewmodel.core.property.RvmObservableProperty import com.alexdeww.reactiveviewmodel.widget.BaseControl import io.reactivex.rxjava3.disposables.Disposable @@ -23,25 +21,11 @@ interface RvmViewComponent : RvmAutoDisposableSupport { fun Disposable.disposeOnDestroyView(tag: String) = autoDispose(tag, onDestroyViewStoreKey) fun Disposable.disposeOnDestroy(tag: String) = autoDispose(tag) - fun LiveData.observe( - action: OnLiveDataAction - ): Observer = observe(owner = componentLifecycleOwner, action = action) + fun LiveData.observe(action: OnLiveDataAction): Observer = + observe(owner = componentLifecycleOwner, action = action) - fun RvmState.observe( - action: OnLiveDataAction - ): Observer = observe(componentLifecycleOwner, action) - - fun RvmState<*>.Projection.observe( - action: OnLiveDataAction - ): Observer = observe(componentLifecycleOwner, action) - - fun RvmEvent.observe( - action: OnLiveDataAction - ): Observer = observe(componentLifecycleOwner, action) - - fun RvmConfirmationEvent.observe( - action: OnLiveDataAction - ): Observer = observe(componentLifecycleOwner, action) + fun RvmObservableProperty.observe(action: OnLiveDataAction): Observer = + observe(componentLifecycleOwner, action) val > C.binder: B get() = getBinder(this@RvmViewComponent) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmAction.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmAction.kt index 157604a..721efa5 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmAction.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmAction.kt @@ -4,13 +4,14 @@ import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.functions.Consumer import io.reactivex.rxjava3.subjects.PublishSubject -class RvmAction internal constructor(debounceInterval: Long? = null) { +class RvmAction internal constructor( + debounceInterval: Long? = null +) : RvmProperty() { private val subject = PublishSubject.create().toSerialized() - internal val observable: Observable = subject.letDebounce(debounceInterval) - - val consumer: Consumer = Consumer { subject.onNext(it) } + override val observable: Observable = subject.letDebounce(debounceInterval) + public override val consumer: Consumer = Consumer { subject.onNext(it) } fun call(value: T) = consumer.accept(value) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmConfirmationEvent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmConfirmationEvent.kt index 0170f14..ada9098 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmConfirmationEvent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmConfirmationEvent.kt @@ -11,7 +11,9 @@ import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.functions.Consumer -class RvmConfirmationEvent internal constructor(debounceInterval: Long? = null) { +class RvmConfirmationEvent internal constructor( + debounceInterval: Long? = null +) : RvmProperty(), RvmCallableProperty { private sealed class EventType { data class Pending(val data: Any) : EventType() @@ -23,17 +25,17 @@ class RvmConfirmationEvent internal constructor(debounceInterval: Long? private val eventState = RvmState(EventType.Confirmed, debounceInterval) - internal val consumer: Consumer = Consumer { + override val consumer: Consumer = Consumer { eventState.consumer.accept(EventType.Pending(it)) } @Suppress("UNCHECKED_CAST") - internal val observable: Observable = eventState.observable + override val observable: Observable = eventState.observable .ofType(EventType.Pending::class.java) .map { it.data as T } - val liveData: LiveData by lazy { ConfirmationEventLiveData() } - val viewFlowable: Flowable by lazy { observable.toViewFlowable() } + override val liveData: LiveData by lazy { ConfirmationEventLiveData() } + override val viewFlowable: Flowable by lazy { observable.toViewFlowable() } val isConfirmed: Boolean get() = eventState.value === EventType.Confirmed fun confirm() { diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmEvent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmEvent.kt index 502cef5..cf24eb2 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmEvent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmEvent.kt @@ -1,7 +1,6 @@ package com.alexdeww.reactiveviewmodel.core.property import androidx.lifecycle.LiveData -import com.alexdeww.reactiveviewmodel.core.livedata.LiveEvent import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.Disposable @@ -9,18 +8,20 @@ import io.reactivex.rxjava3.functions.Consumer import io.reactivex.rxjava3.subjects.BehaviorSubject import java.util.concurrent.atomic.AtomicBoolean -class RvmEvent internal constructor(debounceInterval: Long? = null) { +class RvmEvent internal constructor( + debounceInterval: Long? = null +) : RvmProperty(), RvmCallableProperty { private val subject = BehaviorSubject.create() private val serializedSubject = subject.toSerialized() private val isPending = AtomicBoolean(false) - internal val consumer: Consumer = Consumer { + override val consumer: Consumer = Consumer { isPending.set(true) serializedSubject.onNext(it) } - internal val observable: Observable = Observable + override val observable: Observable = Observable .create { emitter -> val skipCount = if (!isPending.get() && subject.hasValue()) 1L else 0L val d = serializedSubject.skip(skipCount).subscribe { @@ -32,10 +33,10 @@ class RvmEvent internal constructor(debounceInterval: Long? = null) { .letDebounce(debounceInterval) .share() - val liveData: LiveData by lazy { EventLiveData() } - val viewFlowable: Flowable by lazy { observable.toViewFlowable() } + override val liveData: LiveData by lazy { EventLiveData() } + override val viewFlowable: Flowable by lazy { observable.toViewFlowable() } - private inner class EventLiveData : LiveEvent() { + private inner class EventLiveData : LiveData() { private var disposable: Disposable? = null diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmProperty.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmProperty.kt new file mode 100644 index 0000000..497efa4 --- /dev/null +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmProperty.kt @@ -0,0 +1,32 @@ +package com.alexdeww.reactiveviewmodel.core.property + +import androidx.lifecycle.LiveData +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.functions.Consumer + +abstract class RvmPropertyInternal { + internal abstract val consumer: Consumer + internal abstract val observable: Observable +} + +@Suppress("UnnecessaryAbstractClass") +abstract class RvmProperty : RvmPropertyInternal() + +interface RvmObservableProperty { + val viewFlowable: Flowable + val liveData: LiveData +} + +interface RvmValueProperty : RvmObservableProperty { + val value: T? + val valueNonNull: T get() = value!! + val hasValue: Boolean get() = value != null + + fun getValueOrDef(actionDefValue: () -> T): T = value ?: actionDefValue() + fun getValueOrDef(defValue: T): T = getValueOrDef { defValue } +} + +interface RvmCallableProperty : RvmObservableProperty + +interface RvmMutableValueProperty : RvmValueProperty diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt index 60e619a..7d80c0c 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt @@ -10,7 +10,7 @@ import io.reactivex.rxjava3.subjects.BehaviorSubject class RvmState internal constructor( initValue: T? = null, debounceInterval: Long? = null -) { +) : RvmProperty(), RvmMutableValueProperty { private val subject = when (initValue) { null -> BehaviorSubject.create() @@ -19,43 +19,31 @@ class RvmState internal constructor( private val serializedSubject = subject.toSerialized() internal var valueChangesHook: ((value: T) -> T)? = null - internal val consumer: Consumer = Consumer { newValue -> + override val consumer: Consumer = Consumer { newValue -> serializedSubject.onNext(valueChangesHook?.invoke(newValue) ?: newValue) } - internal val observable: Observable = serializedSubject.letDebounce(debounceInterval) + override val observable: Observable = serializedSubject.letDebounce(debounceInterval) - val value: T? get() = subject.value - val valueNonNull: T get() = value!! - val hasValue: Boolean get() = value != null - - val viewFlowable: Flowable by lazy { observable.toViewFlowable() } - val liveData: RvmLiveData by lazy { StateLiveData(viewFlowable) } - - fun getValueOrDef(actionDefValue: () -> T): T = value ?: actionDefValue() - fun getValueOrDef(defValue: T): T = getValueOrDef { defValue } + override val value: T? get() = subject.value + override val viewFlowable: Flowable by lazy { observable.toViewFlowable() } + override val liveData: RvmLiveData by lazy { StateLiveData(viewFlowable) } inner class Projection internal constructor( distinctUntilChanged: Boolean, projectionBlock: (value: T, consumer: Consumer) -> Unit - ) { - private val projectionSubject = BehaviorSubject.create() - private val projectionSource = projectionSubject.run { - if (distinctUntilChanged) distinctUntilChanged() - else this + ) : RvmPropertyInternal(), RvmObservableProperty, RvmValueProperty { + private val subject = BehaviorSubject.create() + override val consumer: Consumer = Consumer(subject::onNext) + override val observable: Observable = subject.run { + if (distinctUntilChanged) distinctUntilChanged() else this } - val value: R? get() = projectionSubject.value - val valueNonNull: R get() = value!! - val hasValue: Boolean get() = value != null - - val viewFlowable: Flowable by lazy { projectionSource.toViewFlowable() } - val liveData: RvmLiveData by lazy { StateLiveData(viewFlowable) } - - fun getValueOrDef(actionDefValue: () -> R): R = value ?: actionDefValue() - fun getValueOrDef(defValue: R): R = getValueOrDef { defValue } + override val value: R? get() = subject.value + override val viewFlowable: Flowable by lazy { observable.toViewFlowable() } + override val liveData: RvmLiveData by lazy { StateLiveData(viewFlowable) } init { - observable.subscribe { projectionBlock(it, projectionSubject::onNext) } + this@RvmState.observable.subscribe { projectionBlock(it, subject::onNext) } } } From de58ba38b3a2c582116b53f8b28d45dd6670a088 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 6 Jan 2023 08:40:00 +0700 Subject: [PATCH 12/31] hide consumer access for state projection --- .../com/alexdeww/reactiveviewmodel/core/RvmExtensions.kt | 8 ++++---- .../reactiveviewmodel/core/RvmPropertiesSupport.kt | 2 +- .../reactiveviewmodel/core/property/RvmProperty.kt | 4 ++-- .../alexdeww/reactiveviewmodel/core/property/RvmState.kt | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmExtensions.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmExtensions.kt index c10a28f..f0c524c 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmExtensions.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmExtensions.kt @@ -6,7 +6,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.Observer import com.alexdeww.reactiveviewmodel.core.property.RvmAction import com.alexdeww.reactiveviewmodel.core.property.RvmObservableProperty -import com.alexdeww.reactiveviewmodel.core.property.RvmPropertyInternal +import com.alexdeww.reactiveviewmodel.core.property.RvmPropertyBase import io.reactivex.rxjava3.core.* import io.reactivex.rxjava3.functions.Consumer import io.reactivex.rxjava3.functions.Function @@ -121,7 +121,7 @@ fun Completable.bindProgressAny(progressConsumer: Consumer): Completabl } fun Observable.untilOn( - vararg rvmProperty: RvmPropertyInternal<*> + vararg rvmProperty: RvmPropertyBase<*> ): Observable = takeUntil(Observable.merge(rvmProperty.map { it.observable })) fun Observable.untilOn( @@ -129,7 +129,7 @@ fun Observable.untilOn( ): Observable = takeUntil(Observable.merge(observable.toList())) fun Maybe.untilOn( - vararg rvmProperty: RvmPropertyInternal<*> + vararg rvmProperty: RvmPropertyBase<*> ): Maybe = takeUntil(Maybe.merge(rvmProperty.map { it.observable.firstElement() })) fun Maybe.untilOn( @@ -137,7 +137,7 @@ fun Maybe.untilOn( ): Maybe = takeUntil(Maybe.merge(maybe.toList())) fun Completable.untilOn( - vararg rvmProperty: RvmPropertyInternal<*> + vararg rvmProperty: RvmPropertyBase<*> ): Completable = takeUntil(Completable.merge(rvmProperty.map { it.observable.firstElement().ignoreElement() })) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt index b8c6b76..788a0af 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt @@ -12,7 +12,7 @@ interface RvmPropertiesSupport { // common val RvmProperty.consumer: Consumer get() = this.consumer - val RvmProperty.observable: Observable get() = this.observable + val RvmPropertyBase.observable: Observable get() = this.observable // callable property diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmProperty.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmProperty.kt index 497efa4..64ed7b7 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmProperty.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmProperty.kt @@ -5,13 +5,13 @@ import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.functions.Consumer -abstract class RvmPropertyInternal { +abstract class RvmPropertyBase { internal abstract val consumer: Consumer internal abstract val observable: Observable } @Suppress("UnnecessaryAbstractClass") -abstract class RvmProperty : RvmPropertyInternal() +abstract class RvmProperty : RvmPropertyBase() interface RvmObservableProperty { val viewFlowable: Flowable diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt index 7d80c0c..90b8636 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt @@ -31,7 +31,7 @@ class RvmState internal constructor( inner class Projection internal constructor( distinctUntilChanged: Boolean, projectionBlock: (value: T, consumer: Consumer) -> Unit - ) : RvmPropertyInternal(), RvmObservableProperty, RvmValueProperty { + ) : RvmPropertyBase(), RvmObservableProperty, RvmValueProperty { private val subject = BehaviorSubject.create() override val consumer: Consumer = Consumer(subject::onNext) override val observable: Observable = subject.run { From 4c6713c8c666fcede8edae580d5bdb342489ba0b Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 6 Jan 2023 08:49:24 +0700 Subject: [PATCH 13/31] remove unused --- .../com/alexdeww/reactiveviewmodel/core/property/RvmState.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt index 90b8636..044d9e7 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt @@ -31,7 +31,7 @@ class RvmState internal constructor( inner class Projection internal constructor( distinctUntilChanged: Boolean, projectionBlock: (value: T, consumer: Consumer) -> Unit - ) : RvmPropertyBase(), RvmObservableProperty, RvmValueProperty { + ) : RvmPropertyBase(), RvmValueProperty { private val subject = BehaviorSubject.create() override val consumer: Consumer = Consumer(subject::onNext) override val observable: Observable = subject.run { From 746bde00118b6023896361e95c0f6c81210542f1 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 6 Jan 2023 09:13:24 +0700 Subject: [PATCH 14/31] move state projection delegate to RVM scope --- .../core/RvmPropertiesSupport.kt | 35 ++++++++++--------- .../reactiveviewmodel/widget/BaseControl.kt | 31 ++++++++++++++++ 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt index 788a0af..655cfcc 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt @@ -32,22 +32,6 @@ interface RvmPropertiesSupport { if (this.value != value) setValue(value) } - - // state - @RvmDslMarker - fun RvmState.projectionEx( - distinctUntilChanged: Boolean = true, - projectionBlock: (value: T, consumer: Consumer) -> Unit - ): ReadOnlyProperty.Projection> = - RvmPropertyReadOnlyDelegate(property = Projection(distinctUntilChanged, projectionBlock)) - - @RvmDslMarker - fun RvmState.projection( - distinctUntilChanged: Boolean = true, - mapBlock: (value: T) -> R - ): ReadOnlyProperty.Projection> = - projectionEx(distinctUntilChanged) { value, consumer -> consumer.accept(mapBlock(value)) } - } @Suppress("unused") @@ -59,6 +43,25 @@ fun RVM.state( property = RvmState(initValue, debounceInterval) ) +@Suppress("unused") +@RvmDslMarker +fun RVM.stateProjectionEx( + state: RvmState, + distinctUntilChanged: Boolean = true, + projectionBlock: (value: T, consumer: Consumer) -> Unit +): ReadOnlyProperty.Projection> = RvmPropertyReadOnlyDelegate( + property = state.Projection(distinctUntilChanged, projectionBlock) +) + +@Suppress("unused") +@RvmDslMarker +fun RVM.stateProjection( + state: RvmState, + distinctUntilChanged: Boolean = true, + mapBlock: (value: T) -> R +): ReadOnlyProperty.Projection> = + stateProjectionEx(state, distinctUntilChanged) { v, c -> c.accept(mapBlock(v)) } + @Suppress("unused") @RvmDslMarker fun RVM.progressState( diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt index 111a9b1..c6ceaf3 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt @@ -2,6 +2,12 @@ package com.alexdeww.reactiveviewmodel.widget import com.alexdeww.reactiveviewmodel.core.RvmPropertiesSupport import com.alexdeww.reactiveviewmodel.core.RvmViewComponent +import com.alexdeww.reactiveviewmodel.core.property.RvmCallableProperty +import com.alexdeww.reactiveviewmodel.core.property.RvmMutableValueProperty +import com.alexdeww.reactiveviewmodel.core.property.RvmProperty +import com.alexdeww.reactiveviewmodel.core.property.RvmPropertyBase +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.functions.Consumer import java.lang.ref.WeakReference @Suppress("UnnecessaryAbstractClass") @@ -14,4 +20,29 @@ abstract class BaseControl : RvmPropertiesSupport { internal abstract fun getBinder(rvmViewComponent: RvmViewComponent): B + final override val RvmProperty.consumer: Consumer + get() = (this@BaseControl as RvmPropertiesSupport).run { consumer } + final override val RvmPropertyBase.observable: Observable + get() = (this@BaseControl as RvmPropertiesSupport).run { observable } + + final override fun R.call(value: T) where R : RvmCallableProperty, + R : RvmProperty { + (this@BaseControl as RvmPropertiesSupport).run { call(value) } + } + + final override fun R.call() where R : RvmCallableProperty, + R : RvmProperty { + (this@BaseControl as RvmPropertiesSupport).run { call() } + } + + final override fun R.setValue(value: T) where R : RvmMutableValueProperty, + R : RvmProperty { + (this@BaseControl as RvmPropertiesSupport).run { setValue(value) } + } + + final override fun R.setValueIfChanged(value: T) where R : RvmMutableValueProperty, + R : RvmProperty { + (this@BaseControl as RvmPropertiesSupport).run { setValueIfChanged(value) } + } + } From b8e8f1e51b280c0def33454033531f477a974a42 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 6 Jan 2023 09:40:19 +0700 Subject: [PATCH 15/31] refactoring projection --- .../core/property/RvmState.kt | 2 +- .../widget/BaseVisualControl.kt | 18 +++++++++--------- .../reactiveviewmodel/widget/DialogControl.kt | 15 ++++++++------- .../reactiveviewmodel/widget/InputControl.kt | 2 +- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt index 044d9e7..ff497b0 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt @@ -43,7 +43,7 @@ class RvmState internal constructor( override val liveData: RvmLiveData by lazy { StateLiveData(viewFlowable) } init { - this@RvmState.observable.subscribe { projectionBlock(it, subject::onNext) } + this@RvmState.observable.subscribe { projectionBlock(it, consumer) } } } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt index 10fa117..14fdeef 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt @@ -3,10 +3,7 @@ package com.alexdeww.reactiveviewmodel.widget import android.view.View import androidx.annotation.CallSuper import androidx.lifecycle.MediatorLiveData -import com.alexdeww.reactiveviewmodel.core.RVM -import com.alexdeww.reactiveviewmodel.core.RvmViewComponent -import com.alexdeww.reactiveviewmodel.core.action -import com.alexdeww.reactiveviewmodel.core.state +import com.alexdeww.reactiveviewmodel.core.* import io.reactivex.rxjava3.functions.Consumer import java.lang.ref.WeakReference @@ -55,7 +52,10 @@ abstract class BaseVisualControl GONE(View.GONE) } - val data by RVM.state(initialValue) + protected val dataInternal by RVM.state(initialValue) + internal val dataInternalAccess = dataInternal + + val data by RVM.stateProjection(dataInternal, false) { it } val enabled by RVM.state(initialEnabled) val visibility by RVM.state(initialVisibility) @@ -63,13 +63,13 @@ abstract class BaseVisualControl init { actionChangeDataValue.observable - .filter { it != data.value } + .filter { it != dataInternal.value } .subscribe(::onDataValueChanged) } @CallSuper protected open fun onDataValueChanged(newValue: T) { - data.consumer.accept(newValue) + dataInternal.consumer.accept(newValue) } } @@ -100,7 +100,7 @@ class VisualControlLiveDataMediator internal constructor( control?.apply { if (bindEnable) addSource(enabled.liveData) { view?.isEnabled = it } if (bindVisible) addSource(visibility.liveData) { view?.visibility = it.value } - addSource(data.liveData) { newValue -> + addSource(dataInternalAccess.liveData) { newValue -> isEditing = true onValueChanged(newValue) isEditing = false @@ -113,7 +113,7 @@ class VisualControlLiveDataMediator internal constructor( control?.apply { if (bindEnable) removeSource(enabled.liveData) if (bindVisible) removeSource(visibility.liveData) - removeSource(data.liveData) + removeSource(dataInternalAccess.liveData) } onInactiveAction.invoke(this) super.onInactive() diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt index 71078dd..4d70f44 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt @@ -23,23 +23,24 @@ class DialogControl internal constructor() : } internal val result by RVM.action() + internal val displayedInternal by RVM.state>(Display.Absent) - val displayed by RVM.state>(Display.Absent) + val displayed by RVM.stateProjection(displayedInternal, false) { it } val isShowing get() = displayed.value is Display.Displayed fun show(data: T) { dismiss() - displayed.consumer.accept(Display.Displayed(data)) + displayedInternal.consumer.accept(Display.Displayed(data)) } fun showForResult(data: T, dismissOnDispose: Boolean = false): Maybe { dismiss() return result .observable - .doOnSubscribe { displayed.consumer.accept(Display.Displayed(data)) } + .doOnSubscribe { displayedInternal.consumer.accept(Display.Displayed(data)) } .doOnDispose { if (dismissOnDispose) dismiss() } .takeUntil( - displayed.observable + displayedInternal.observable .skip(1) .filter { it == Display.Absent } ) @@ -47,7 +48,7 @@ class DialogControl internal constructor() : } fun dismiss() { - if (isShowing) displayed.consumer.accept(Display.Absent) + if (isShowing) displayedInternal.consumer.accept(Display.Absent) } override fun getBinder(rvmViewComponent: RvmViewComponent): Binder = Binder(rvmViewComponent) @@ -135,7 +136,7 @@ private class DialogLiveDataMediator( override fun onActive() { super.onActive() - addSource(control.displayed.liveData) { displayData -> + addSource(control.displayedInternal.liveData) { displayData -> value = displayData when (displayData) { is DialogControl.Display.Displayed -> { @@ -154,7 +155,7 @@ private class DialogLiveDataMediator( override fun onInactive() { super.onInactive() - removeSource(control.displayed.liveData) + removeSource(control.displayedInternal.liveData) dialog?.let { onDialogUnbind(it) } releaseDialog() } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt index 66df338..cff382c 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt @@ -26,7 +26,7 @@ class InputControl internal constructor( ) { init { - data.valueChangesHook = formatter + dataInternal.valueChangesHook = formatter } val error by RVM.state() From d555f0824519e8a5b18a067c38e4d7ec17520d8c Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 6 Jan 2023 10:24:49 +0700 Subject: [PATCH 16/31] refactoring visualControl savedState delegate --- .../widget/BaseVisualControl.kt | 50 +++++++++++++++++++ .../reactiveviewmodel/widget/CheckControl.kt | 28 +++-------- .../reactiveviewmodel/widget/InputControl.kt | 36 +++++-------- .../reactiveviewmodel/widget/RatingControl.kt | 28 +++-------- 4 files changed, 75 insertions(+), 67 deletions(-) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt index 14fdeef..6e3e780 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt @@ -3,9 +3,12 @@ package com.alexdeww.reactiveviewmodel.widget import android.view.View import androidx.annotation.CallSuper import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.SavedStateHandle import com.alexdeww.reactiveviewmodel.core.* +import com.alexdeww.reactiveviewmodel.widget.BaseVisualControl.Visibility import io.reactivex.rxjava3.functions.Consumer import java.lang.ref.WeakReference +import kotlin.properties.ReadOnlyProperty typealias ActionOnValueChanged = (newValue: T) -> Unit typealias ActionOnActive = VisualControlLiveDataMediator.() -> Unit @@ -120,3 +123,50 @@ class VisualControlLiveDataMediator internal constructor( } } + +typealias InitControl = ( + value: T, + isEnabled: Boolean, + visibility: Visibility, + stateHandle: SavedStateHandle, + key: String, +) -> C + +fun > SavedStateHandle.visualControlDelegate( + initialValue: T, + initialEnabled: Boolean, + initialVisibility: Visibility, + initControl: InitControl, + watcher: RvmViewModelComponent.(stateHandle: SavedStateHandle, key: String) -> Unit = { _, _ -> } +): ReadOnlyProperty = delegate { thisRef, stateHandle, key -> + val dataKey = "$key.data" + val enabledKey = "$key.enabled" + val visibilityKey = "$key.visibility" + val control = initControl( + stateHandle[dataKey] ?: initialValue, + stateHandle[enabledKey] ?: initialEnabled, + stateHandle[visibilityKey] ?: initialVisibility, + stateHandle, key + ) + thisRef.run { + control.data.viewFlowable + .subscribe { stateHandle[dataKey] = it } + .autoDispose() + control.enabled.viewFlowable + .subscribe { stateHandle[enabledKey] = it } + .autoDispose() + control.visibility.viewFlowable + .subscribe { stateHandle[visibilityKey] = it } + .autoDispose() + watcher(stateHandle, key) + } + control +} + +typealias ControlDefaultConstrictor = (value: T, isEnabled: Boolean, visibility: Visibility) -> C + +inline fun > controlDefaultConstrictor( + crossinline defaultConstructor: ControlDefaultConstrictor +): InitControl = { value: T, isEnabled: Boolean, visibility: Visibility, _, _ -> + defaultConstructor(value, isEnabled, visibility) +} diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt index aa7de61..57f28f5 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt @@ -69,25 +69,9 @@ fun SavedStateHandle.checkControl( initialChecked: Boolean = false, initialEnabled: Boolean = true, initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = delegate { thisRef, stateHandle, key -> - val checkedKey = "$key.checked" - val enabledKey = "$key.enabled" - val visibilityKey = "$key.visibility" - val control = CheckControl( - initialChecked = stateHandle[checkedKey] ?: initialChecked, - initialEnabled = stateHandle[enabledKey] ?: initialEnabled, - initialVisibility = stateHandle[visibilityKey] ?: initialVisibility - ) - thisRef.run { - control.data.viewFlowable - .subscribe { stateHandle[checkedKey] = it } - .autoDispose() - control.enabled.viewFlowable - .subscribe { stateHandle[enabledKey] = it } - .autoDispose() - control.visibility.viewFlowable - .subscribe { stateHandle[visibilityKey] = it } - .autoDispose() - } - control -} +): ReadOnlyProperty = visualControlDelegate( + initialValue = initialChecked, + initialEnabled = initialEnabled, + initialVisibility = initialVisibility, + initControl = controlDefaultConstrictor(::CheckControl) +) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt index cff382c..8c77e32 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt @@ -138,30 +138,20 @@ fun SavedStateHandle.inputControl( formatter: FormatterAction? = null, initialEnabled: Boolean = true, initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = delegate { thisRef, stateHandle, key -> - val textKey = "$key.text" - val enabledKey = "$key.enabled" - val visibilityKey = "$key.visibility" - val control = InputControl( - initialText = stateHandle[textKey] ?: initialText, - hideErrorOnUserInput = hideErrorOnUserInput, - formatter = formatter, - initialEnabled = stateHandle[enabledKey] ?: initialEnabled, - initialVisibility = stateHandle[visibilityKey] ?: initialVisibility - ) - thisRef.run { - control.data.viewFlowable - .subscribe { stateHandle[textKey] = it } - .autoDispose() - control.enabled.viewFlowable - .subscribe { stateHandle[enabledKey] = it } - .autoDispose() - control.visibility.viewFlowable - .subscribe { stateHandle[visibilityKey] = it } - .autoDispose() +): ReadOnlyProperty = visualControlDelegate( + initialValue = initialText, + initialEnabled = initialEnabled, + initialVisibility = initialVisibility, + initControl = { value, isEnabled, visibility, _, _ -> + InputControl( + initialText = value, + hideErrorOnUserInput = hideErrorOnUserInput, + formatter = formatter, + initialEnabled = isEnabled, + initialVisibility = visibility + ) } - control -} +) private fun onTextChangedWatcher( action: (CharSequence) -> Unit diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt index bd6ce3a..6724d7b 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt @@ -69,25 +69,9 @@ fun SavedStateHandle.ratingControl( initialValue: Float = 0f, initialEnabled: Boolean = true, initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = delegate { thisRef, stateHandle, key -> - val ratingKey = "$key.rating" - val enabledKey = "$key.enabled" - val visibilityKey = "$key.visibility" - val control = RatingControl( - initialValue = stateHandle[ratingKey] ?: initialValue, - initialEnabled = stateHandle[enabledKey] ?: initialEnabled, - initialVisibility = stateHandle[visibilityKey] ?: initialVisibility - ) - thisRef.run { - control.data.viewFlowable - .subscribe { stateHandle[ratingKey] = it } - .autoDispose() - control.enabled.viewFlowable - .subscribe { stateHandle[enabledKey] = it } - .autoDispose() - control.visibility.viewFlowable - .subscribe { stateHandle[visibilityKey] = it } - .autoDispose() - } - control -} +): ReadOnlyProperty = visualControlDelegate( + initialValue = initialValue, + initialEnabled = initialEnabled, + initialVisibility = initialVisibility, + initControl = controlDefaultConstrictor(::RatingControl) +) From 1361050a7542f95ed3b3fa220f5682f41b326560 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 7 Jan 2023 05:31:40 +0700 Subject: [PATCH 17/31] make final for some extension --- .../component/ReactiveActivity.kt | 2 +- .../component/ReactiveFragment.kt | 2 +- .../component/ReactiveViewModel.kt | 32 +++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveActivity.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveActivity.kt index ef39b61..5751dca 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveActivity.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveActivity.kt @@ -12,7 +12,7 @@ import io.reactivex.rxjava3.disposables.Disposable abstract class ReactiveActivity : AppCompatActivity(), RvmViewComponent, RvmWidgetBindShortcut { private val rvmAutoDisposableStore by lazy { DefaultRvmDisposableStore() } - override val componentLifecycleOwner: LifecycleOwner get() = this + final override val componentLifecycleOwner: LifecycleOwner get() = this final override fun Disposable.autoDispose( tagKey: String, diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveFragment.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveFragment.kt index 9fad061..b7cefd6 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveFragment.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveFragment.kt @@ -15,7 +15,7 @@ abstract class ReactiveFragment( ) : Fragment(layoutId), RvmViewComponent, RvmWidgetBindShortcut { private val rvmAutoDisposableStore by lazy { DefaultRvmDisposableStore() } - override val componentLifecycleOwner: LifecycleOwner get() = viewLifecycleOwner + final override val componentLifecycleOwner: LifecycleOwner get() = viewLifecycleOwner final override fun Disposable.autoDispose( tagKey: String, diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt index 63055aa..b0dec2d 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt @@ -4,8 +4,15 @@ import androidx.annotation.CallSuper import androidx.lifecycle.ViewModel import com.alexdeww.reactiveviewmodel.core.DefaultRvmDisposableStore import com.alexdeww.reactiveviewmodel.core.RvmAutoDisposableSupport +import com.alexdeww.reactiveviewmodel.core.RvmPropertiesSupport import com.alexdeww.reactiveviewmodel.core.RvmViewModelComponent +import com.alexdeww.reactiveviewmodel.core.property.RvmCallableProperty +import com.alexdeww.reactiveviewmodel.core.property.RvmMutableValueProperty +import com.alexdeww.reactiveviewmodel.core.property.RvmProperty +import com.alexdeww.reactiveviewmodel.core.property.RvmPropertyBase +import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.Disposable +import io.reactivex.rxjava3.functions.Consumer /** * Based on RxPM @@ -21,6 +28,31 @@ abstract class ReactiveViewModel : ViewModel(), RvmViewModelComponent { storeKey: RvmAutoDisposableSupport.StoreKey? ) = rvmAutoDisposableStore.run { this@autoDispose.autoDispose(tagKey, storeKey) } + final override val RvmProperty.consumer: Consumer + get() = (this@ReactiveViewModel as RvmPropertiesSupport).run { consumer } + final override val RvmPropertyBase.observable: Observable + get() = (this@ReactiveViewModel as RvmPropertiesSupport).run { observable } + + final override fun R.call(value: T) where R : RvmCallableProperty, + R : RvmProperty { + (this@ReactiveViewModel as RvmPropertiesSupport).run { call(value) } + } + + final override fun R.call() where R : RvmCallableProperty, + R : RvmProperty { + (this@ReactiveViewModel as RvmPropertiesSupport).run { call() } + } + + final override fun R.setValue(value: T) where R : RvmMutableValueProperty, + R : RvmProperty { + (this@ReactiveViewModel as RvmPropertiesSupport).run { setValue(value) } + } + + final override fun R.setValueIfChanged(value: T) where R : RvmMutableValueProperty, + R : RvmProperty { + (this@ReactiveViewModel as RvmPropertiesSupport).run { setValueIfChanged(value) } + } + @CallSuper override fun onCleared() { rvmAutoDisposableStore.dispose() From 9af89d248c38a4eccc62d3a446c894f3bf714aa7 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 7 Jan 2023 07:04:30 +0700 Subject: [PATCH 18/31] refactoring bind logic --- .../component/ReactiveViewModel.kt | 25 +++++++---- .../core/RvmViewModelComponent.kt | 43 ++++++++++--------- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt index b0dec2d..c0fd6f9 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt @@ -6,10 +6,7 @@ import com.alexdeww.reactiveviewmodel.core.DefaultRvmDisposableStore import com.alexdeww.reactiveviewmodel.core.RvmAutoDisposableSupport import com.alexdeww.reactiveviewmodel.core.RvmPropertiesSupport import com.alexdeww.reactiveviewmodel.core.RvmViewModelComponent -import com.alexdeww.reactiveviewmodel.core.property.RvmCallableProperty -import com.alexdeww.reactiveviewmodel.core.property.RvmMutableValueProperty -import com.alexdeww.reactiveviewmodel.core.property.RvmProperty -import com.alexdeww.reactiveviewmodel.core.property.RvmPropertyBase +import com.alexdeww.reactiveviewmodel.core.property.* import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.functions.Consumer @@ -23,6 +20,12 @@ abstract class ReactiveViewModel : ViewModel(), RvmViewModelComponent { private val rvmAutoDisposableStore by lazy { DefaultRvmDisposableStore() } + @CallSuper + override fun onCleared() { + rvmAutoDisposableStore.dispose() + super.onCleared() + } + final override fun Disposable.autoDispose( tagKey: String, storeKey: RvmAutoDisposableSupport.StoreKey? @@ -53,10 +56,16 @@ abstract class ReactiveViewModel : ViewModel(), RvmViewModelComponent { (this@ReactiveViewModel as RvmPropertiesSupport).run { setValueIfChanged(value) } } - @CallSuper - override fun onCleared() { - rvmAutoDisposableStore.dispose() - super.onCleared() + final override fun RvmAction.bind( + transformChainBlock: Observable.() -> Observable + ) { + (this@ReactiveViewModel as RvmViewModelComponent).run { bind(transformChainBlock) } + } + + final override fun RvmState.bind( + transformChainBlock: Observable.() -> Observable + ) { + (this@ReactiveViewModel as RvmViewModelComponent).run { bind(transformChainBlock) } } } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt index 893c0c1..c3dca18 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt @@ -3,10 +3,13 @@ package com.alexdeww.reactiveviewmodel.core import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker import com.alexdeww.reactiveviewmodel.core.property.RvmAction +import com.alexdeww.reactiveviewmodel.core.property.RvmProperty import com.alexdeww.reactiveviewmodel.core.property.RvmState +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.subjects.BehaviorSubject +import java.util.concurrent.atomic.AtomicInteger import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty @@ -23,14 +26,14 @@ interface RvmViewModelComponent : RvmPropertiesSupport, RvmAutoDisposableSupport fun Observable.applyDefaultErrorHandler(): Observable = this @RvmBinderDslMarker - infix fun RvmAction.bind( - transformChainBlock: Observable.() -> Observable - ) = bindAction(this, transformChainBlock) + infix fun RvmAction.bind( + transformChainBlock: Observable.() -> Observable + ) = bindProperty(this, transformChainBlock) @RvmBinderDslMarker infix fun RvmState.bind( transformChainBlock: Observable.() -> Observable - ) = bindState(this, transformChainBlock) + ) = bindProperty(this, transformChainBlock) } @@ -41,26 +44,24 @@ fun RVM.invocable( ): ReadOnlyProperty> = InvocableDelegate(block) -internal fun RvmViewModelComponent.bindAction( - action: RvmAction, - transformChainBlock: Observable.() -> Observable -) { - action.observable - .transformChainBlock() - .applyDefaultErrorHandler() - .retry() - .subscribe() - .autoDispose() -} - -internal fun RvmViewModelComponent.bindState( - state: RvmState, +internal fun RvmViewModelComponent.bindProperty( + rvmProperty: RvmProperty, transformChainBlock: Observable.() -> Observable ) { - state.observable + // 1 - need skip, 2 - has value (skip only if source has value) + val skipState = AtomicInteger(0) + val source = rvmProperty.observable + .replay(1) + .apply { connect().autoDispose() } + .doOnNext { skipState.compareAndSet(0, 2) } + .skipWhile { skipState.compareAndSet(1, 2) } .transformChainBlock() - .applyDefaultErrorHandler() - .retry() + .doOnError { skipState.compareAndSet(2, 1) } + + Observable + .fromCallable { 0 } + .observeOn(AndroidSchedulers.mainThread()) + .switchMap { source.applyDefaultErrorHandler().retry() } .subscribe() .autoDispose() } From f8d6f625e822826ff8e36dc8402194c23ab6a3b3 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 7 Jan 2023 07:21:59 +0700 Subject: [PATCH 19/31] refactoring RvmPropertyDelegate --- .../core/RvmPropertiesSupport.kt | 32 +++++++++---------- .../core/utils/RvmPropertyDelegate.kt | 24 ++++++++++++++ .../core/utils/RvmPropertyReadOnlyDelegate.kt | 10 ------ .../reactiveviewmodel/widget/CheckControl.kt | 8 ++--- .../reactiveviewmodel/widget/DialogControl.kt | 4 +-- .../widget/DisplayableControl.kt | 8 ++--- .../reactiveviewmodel/widget/InputControl.kt | 8 ++--- .../reactiveviewmodel/widget/RatingControl.kt | 8 ++--- 8 files changed, 58 insertions(+), 44 deletions(-) create mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyDelegate.kt delete mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyReadOnlyDelegate.kt diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt index 655cfcc..86d64fe 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt @@ -2,7 +2,7 @@ package com.alexdeww.reactiveviewmodel.core import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker import com.alexdeww.reactiveviewmodel.core.property.* -import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyReadOnlyDelegate +import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyDelegate import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.functions.Consumer import kotlin.properties.ReadOnlyProperty @@ -39,9 +39,9 @@ interface RvmPropertiesSupport { fun RVM.state( initValue: T? = null, debounceInterval: Long? = null -): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( - property = RvmState(initValue, debounceInterval) -) +): ReadOnlyProperty> = RvmPropertyDelegate.def { + RvmState(initValue, debounceInterval) +} @Suppress("unused") @RvmDslMarker @@ -49,9 +49,9 @@ fun RVM.stateProjectionEx( state: RvmState, distinctUntilChanged: Boolean = true, projectionBlock: (value: T, consumer: Consumer) -> Unit -): ReadOnlyProperty.Projection> = RvmPropertyReadOnlyDelegate( - property = state.Projection(distinctUntilChanged, projectionBlock) -) +): ReadOnlyProperty.Projection> = RvmPropertyDelegate.def { + state.Projection(distinctUntilChanged, projectionBlock) +} @Suppress("unused") @RvmDslMarker @@ -76,9 +76,9 @@ fun RVM.progressState( @RvmDslMarker fun RVM.event( debounceInterval: Long? = null -): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( - property = RvmEvent(debounceInterval) -) +): ReadOnlyProperty> = RvmPropertyDelegate.def { + RvmEvent(debounceInterval) +} @Suppress("unused") @RvmDslMarker @@ -92,9 +92,9 @@ fun RVM.eventNone( @RvmDslMarker fun RVM.confirmationEvent( debounceInterval: Long? = null -): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( - property = RvmConfirmationEvent(debounceInterval) -) +): ReadOnlyProperty> = RvmPropertyDelegate.def { + RvmConfirmationEvent(debounceInterval) +} @Suppress("unused") @RvmDslMarker @@ -108,9 +108,9 @@ fun RVM.confirmationEventNone( @RvmDslMarker fun RVM.action( debounceInterval: Long? = null -): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( - property = RvmAction(debounceInterval) -) +): ReadOnlyProperty> = RvmPropertyDelegate.def { + RvmAction(debounceInterval) +} @Suppress("unused") @RvmDslMarker diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyDelegate.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyDelegate.kt new file mode 100644 index 0000000..1390848 --- /dev/null +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyDelegate.kt @@ -0,0 +1,24 @@ +package com.alexdeww.reactiveviewmodel.core.utils + +import com.alexdeww.reactiveviewmodel.core.RvmPropertiesSupport +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +class RvmPropertyDelegate private constructor( + private val initializer: R.(property: KProperty<*>) -> P +) : ReadOnlyProperty { + + companion object { + fun def( + initializer: R.(property: KProperty<*>) -> P + ): ReadOnlyProperty = RvmPropertyDelegate(initializer) + } + + private var value: P? = null + + override fun getValue(thisRef: R, property: KProperty<*>): P { + if (value == null) value = initializer(thisRef, property) + return value!! + } + +} diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyReadOnlyDelegate.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyReadOnlyDelegate.kt deleted file mode 100644 index 5731db5..0000000 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/utils/RvmPropertyReadOnlyDelegate.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.alexdeww.reactiveviewmodel.core.utils - -import kotlin.properties.ReadOnlyProperty -import kotlin.reflect.KProperty - -class RvmPropertyReadOnlyDelegate

( - private val property: P -) : ReadOnlyProperty { - override fun getValue(thisRef: Any, property: KProperty<*>): P = this.property -} diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt index 57f28f5..261081a 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt @@ -5,7 +5,7 @@ import androidx.lifecycle.SavedStateHandle import com.alexdeww.reactiveviewmodel.core.* import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker -import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyReadOnlyDelegate +import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyDelegate import kotlin.properties.ReadOnlyProperty class CheckControl internal constructor( @@ -56,13 +56,13 @@ fun RVM.checkControl( initialChecked: Boolean = false, initialEnabled: Boolean = true, initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = RvmPropertyReadOnlyDelegate( - property = CheckControl( +): ReadOnlyProperty = RvmPropertyDelegate.def { + CheckControl( initialChecked = initialChecked, initialEnabled = initialEnabled, initialVisibility = initialVisibility ) -) +} @RvmDslMarker fun SavedStateHandle.checkControl( diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt index 4d70f44..2939b82 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt @@ -5,7 +5,7 @@ import androidx.lifecycle.MediatorLiveData import com.alexdeww.reactiveviewmodel.core.* import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker -import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyReadOnlyDelegate +import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyDelegate import io.reactivex.rxjava3.core.Maybe import kotlin.properties.ReadOnlyProperty @@ -102,7 +102,7 @@ class DialogControlResult internal constructor( @Suppress("unused") @RvmDslMarker fun RVM.dialogControl(): ReadOnlyProperty> = - RvmPropertyReadOnlyDelegate(property = DialogControl()) + RvmPropertyDelegate.def { DialogControl() } @Suppress("unused") @RvmDslMarker diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt index 7e1d207..074835c 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt @@ -5,7 +5,7 @@ import androidx.lifecycle.SavedStateHandle import com.alexdeww.reactiveviewmodel.core.* import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker -import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyReadOnlyDelegate +import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyDelegate import kotlinx.parcelize.Parcelize import kotlinx.parcelize.RawValue import kotlin.properties.ReadOnlyProperty @@ -79,9 +79,9 @@ class DisplayableControl internal constructor( @RvmDslMarker fun RVM.displayableControl( debounceInterval: Long? = null -): ReadOnlyProperty> = RvmPropertyReadOnlyDelegate( - property = DisplayableControl(debounceInterval) -) +): ReadOnlyProperty> = RvmPropertyDelegate.def { + DisplayableControl(debounceInterval) +} @RvmDslMarker fun SavedStateHandle.displayableControl( diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt index 8c77e32..43852af 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt @@ -7,7 +7,7 @@ import androidx.lifecycle.SavedStateHandle import com.alexdeww.reactiveviewmodel.core.* import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker -import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyReadOnlyDelegate +import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyDelegate import com.google.android.material.textfield.TextInputLayout import kotlin.properties.ReadOnlyProperty @@ -121,15 +121,15 @@ fun RVM.inputControl( formatter: FormatterAction? = null, initialEnabled: Boolean = true, initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = RvmPropertyReadOnlyDelegate( - property = InputControl( +): ReadOnlyProperty = RvmPropertyDelegate.def { + InputControl( initialText = initialText, hideErrorOnUserInput = hideErrorOnUserInput, formatter = formatter, initialEnabled = initialEnabled, initialVisibility = initialVisibility ) -) +} @RvmDslMarker fun SavedStateHandle.inputControl( diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt index 6724d7b..c7178b8 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt @@ -6,7 +6,7 @@ import androidx.lifecycle.SavedStateHandle import com.alexdeww.reactiveviewmodel.core.* import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker -import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyReadOnlyDelegate +import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyDelegate import kotlin.properties.ReadOnlyProperty @SuppressLint("CheckResult") @@ -56,13 +56,13 @@ fun RVM.ratingControl( initialValue: Float = 0f, initialEnabled: Boolean = true, initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = RvmPropertyReadOnlyDelegate( - property = RatingControl( +): ReadOnlyProperty = RvmPropertyDelegate.def { + RatingControl( initialValue = initialValue, initialEnabled = initialEnabled, initialVisibility = initialVisibility ) -) +} @RvmDslMarker fun SavedStateHandle.ratingControl( From 99fd265cb6779a246be6c529511747fc2fa41a21 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 7 Jan 2023 07:59:51 +0700 Subject: [PATCH 20/31] add stateProjection from source --- .../core/RvmViewModelComponent.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt index c3dca18..5dc7ec9 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt @@ -5,6 +5,7 @@ import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker import com.alexdeww.reactiveviewmodel.core.property.RvmAction import com.alexdeww.reactiveviewmodel.core.property.RvmProperty import com.alexdeww.reactiveviewmodel.core.property.RvmState +import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyDelegate import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Observable @@ -44,6 +45,22 @@ fun RVM.invocable( ): ReadOnlyProperty> = InvocableDelegate(block) +@Suppress("unused") +@RvmDslMarker +fun RVM.stateProjection( + initialValue: T? = null, + distinctUntilChanged: Boolean = true, + sourceBlock: () -> Observable +): ReadOnlyProperty.Projection> = RvmPropertyDelegate.def { + val state = RvmState(initialValue) + sourceBlock() + .applyDefaultErrorHandler() + .retry() + .subscribe(state.consumer) + .autoDispose() + state.Projection(distinctUntilChanged) { v, c -> c.accept(v) } +} + internal fun RvmViewModelComponent.bindProperty( rvmProperty: RvmProperty, transformChainBlock: Observable.() -> Observable From 34cd3eb9267510fecc8947b0b3af251c2d4bbb12 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 7 Jan 2023 08:05:30 +0700 Subject: [PATCH 21/31] rename stateProjection from source --- .../alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt index 5dc7ec9..6a5d6d9 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt @@ -47,7 +47,7 @@ fun RVM.invocable( @Suppress("unused") @RvmDslMarker -fun RVM.stateProjection( +fun RVM.stateProjectionFromSource( initialValue: T? = null, distinctUntilChanged: Boolean = true, sourceBlock: () -> Observable From e052a2f61308da17fb072df6d5cd50d71cd9bb11 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 7 Jan 2023 10:30:33 +0700 Subject: [PATCH 22/31] fix stack overflow on extension implementation --- .../component/ReactiveViewModel.kt | 30 ++++++++++++------- .../reactiveviewmodel/widget/BaseControl.kt | 14 +++++---- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt index c0fd6f9..a6eec0e 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt @@ -4,8 +4,8 @@ import androidx.annotation.CallSuper import androidx.lifecycle.ViewModel import com.alexdeww.reactiveviewmodel.core.DefaultRvmDisposableStore import com.alexdeww.reactiveviewmodel.core.RvmAutoDisposableSupport -import com.alexdeww.reactiveviewmodel.core.RvmPropertiesSupport import com.alexdeww.reactiveviewmodel.core.RvmViewModelComponent +import com.alexdeww.reactiveviewmodel.core.annotation.RvmBinderDslMarker import com.alexdeww.reactiveviewmodel.core.property.* import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.Disposable @@ -19,6 +19,14 @@ import io.reactivex.rxjava3.functions.Consumer abstract class ReactiveViewModel : ViewModel(), RvmViewModelComponent { private val rvmAutoDisposableStore by lazy { DefaultRvmDisposableStore() } + private val defaultViewModelComponent = object : RvmViewModelComponent { + override fun Disposable.autoDispose( + tagKey: String, + storeKey: RvmAutoDisposableSupport.StoreKey? + ) = this@ReactiveViewModel.rvmAutoDisposableStore.run { + this@autoDispose.autoDispose(tagKey, storeKey) + } + } @CallSuper override fun onCleared() { @@ -29,43 +37,45 @@ abstract class ReactiveViewModel : ViewModel(), RvmViewModelComponent { final override fun Disposable.autoDispose( tagKey: String, storeKey: RvmAutoDisposableSupport.StoreKey? - ) = rvmAutoDisposableStore.run { this@autoDispose.autoDispose(tagKey, storeKey) } + ) = defaultViewModelComponent.run { autoDispose(tagKey, storeKey) } final override val RvmProperty.consumer: Consumer - get() = (this@ReactiveViewModel as RvmPropertiesSupport).run { consumer } + get() = defaultViewModelComponent.run { consumer } final override val RvmPropertyBase.observable: Observable - get() = (this@ReactiveViewModel as RvmPropertiesSupport).run { observable } + get() = defaultViewModelComponent.run { observable } final override fun R.call(value: T) where R : RvmCallableProperty, R : RvmProperty { - (this@ReactiveViewModel as RvmPropertiesSupport).run { call(value) } + defaultViewModelComponent.run { call(value) } } final override fun R.call() where R : RvmCallableProperty, R : RvmProperty { - (this@ReactiveViewModel as RvmPropertiesSupport).run { call() } + defaultViewModelComponent.run { call() } } final override fun R.setValue(value: T) where R : RvmMutableValueProperty, R : RvmProperty { - (this@ReactiveViewModel as RvmPropertiesSupport).run { setValue(value) } + defaultViewModelComponent.run { setValue(value) } } final override fun R.setValueIfChanged(value: T) where R : RvmMutableValueProperty, R : RvmProperty { - (this@ReactiveViewModel as RvmPropertiesSupport).run { setValueIfChanged(value) } + defaultViewModelComponent.run { setValueIfChanged(value) } } + @RvmBinderDslMarker final override fun RvmAction.bind( transformChainBlock: Observable.() -> Observable ) { - (this@ReactiveViewModel as RvmViewModelComponent).run { bind(transformChainBlock) } + defaultViewModelComponent.run { bind(transformChainBlock) } } + @RvmBinderDslMarker final override fun RvmState.bind( transformChainBlock: Observable.() -> Observable ) { - (this@ReactiveViewModel as RvmViewModelComponent).run { bind(transformChainBlock) } + defaultViewModelComponent.run { bind(transformChainBlock) } } } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt index c6ceaf3..01fff54 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt @@ -13,6 +13,8 @@ import java.lang.ref.WeakReference @Suppress("UnnecessaryAbstractClass") abstract class BaseControl : RvmPropertiesSupport { + private val defaultPropertiesSupport = object : RvmPropertiesSupport {} + abstract class ViewBinder(rvmViewComponent: RvmViewComponent) { protected val rvmViewComponentRef: WeakReference = WeakReference(rvmViewComponent) @@ -21,28 +23,28 @@ abstract class BaseControl : RvmPropertiesSupport { internal abstract fun getBinder(rvmViewComponent: RvmViewComponent): B final override val RvmProperty.consumer: Consumer - get() = (this@BaseControl as RvmPropertiesSupport).run { consumer } + get() = defaultPropertiesSupport.run { consumer } final override val RvmPropertyBase.observable: Observable - get() = (this@BaseControl as RvmPropertiesSupport).run { observable } + get() = defaultPropertiesSupport.run { observable } final override fun R.call(value: T) where R : RvmCallableProperty, R : RvmProperty { - (this@BaseControl as RvmPropertiesSupport).run { call(value) } + defaultPropertiesSupport.run { call(value) } } final override fun R.call() where R : RvmCallableProperty, R : RvmProperty { - (this@BaseControl as RvmPropertiesSupport).run { call() } + defaultPropertiesSupport.run { call() } } final override fun R.setValue(value: T) where R : RvmMutableValueProperty, R : RvmProperty { - (this@BaseControl as RvmPropertiesSupport).run { setValue(value) } + defaultPropertiesSupport.run { setValue(value) } } final override fun R.setValueIfChanged(value: T) where R : RvmMutableValueProperty, R : RvmProperty { - (this@BaseControl as RvmPropertiesSupport).run { setValueIfChanged(value) } + defaultPropertiesSupport.run { setValueIfChanged(value) } } } From 9e0ed690f899701368d1e8d1455bf33e3f836025 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 8 Jan 2023 05:52:07 +0700 Subject: [PATCH 23/31] refactoring stateProjection --- .../core/RvmPropertiesSupport.kt | 28 +++++++-------- .../core/RvmViewModelComponent.kt | 10 +++--- .../core/property/RvmState.kt | 25 ++------------ .../core/property/RvmStateProjection.kt | 34 +++++++++++++++++++ 4 files changed, 56 insertions(+), 41 deletions(-) create mode 100644 reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmStateProjection.kt diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt index 86d64fe..37d1a40 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt @@ -45,22 +45,22 @@ fun RVM.state( @Suppress("unused") @RvmDslMarker -fun RVM.stateProjectionEx( - state: RvmState, - distinctUntilChanged: Boolean = true, - projectionBlock: (value: T, consumer: Consumer) -> Unit -): ReadOnlyProperty.Projection> = RvmPropertyDelegate.def { - state.Projection(distinctUntilChanged, projectionBlock) -} - -@Suppress("unused") -@RvmDslMarker -fun RVM.stateProjection( - state: RvmState, +fun RVM.stateProjection( + stateSource: P, distinctUntilChanged: Boolean = true, mapBlock: (value: T) -> R -): ReadOnlyProperty.Projection> = - stateProjectionEx(state, distinctUntilChanged) { v, c -> c.accept(mapBlock(v)) } +): ReadOnlyProperty> where P : RvmPropertyBase, + P : RvmValueProperty { + return RvmPropertyDelegate.def { + val projection = RvmStateProjection() + val d = stateSource.observable + .map(mapBlock) + .run { if (distinctUntilChanged) distinctUntilChanged() else this } + .subscribe(projection.consumer) + if (this is RvmAutoDisposableSupport) d.autoDispose() + projection + } +} @Suppress("unused") @RvmDslMarker diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt index 6a5d6d9..8995fa9 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt @@ -5,6 +5,7 @@ import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker import com.alexdeww.reactiveviewmodel.core.property.RvmAction import com.alexdeww.reactiveviewmodel.core.property.RvmProperty import com.alexdeww.reactiveviewmodel.core.property.RvmState +import com.alexdeww.reactiveviewmodel.core.property.RvmStateProjection import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyDelegate import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Completable @@ -49,16 +50,15 @@ fun RVM.invocable( @RvmDslMarker fun RVM.stateProjectionFromSource( initialValue: T? = null, - distinctUntilChanged: Boolean = true, sourceBlock: () -> Observable -): ReadOnlyProperty.Projection> = RvmPropertyDelegate.def { - val state = RvmState(initialValue) +): ReadOnlyProperty> = RvmPropertyDelegate.def { + val projection = RvmStateProjection(initialValue) sourceBlock() .applyDefaultErrorHandler() .retry() - .subscribe(state.consumer) + .subscribe(projection.consumer) .autoDispose() - state.Projection(distinctUntilChanged) { v, c -> c.accept(v) } + projection } internal fun RvmViewModelComponent.bindProperty( diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt index ff497b0..f7c7ccd 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt @@ -26,31 +26,12 @@ class RvmState internal constructor( override val value: T? get() = subject.value override val viewFlowable: Flowable by lazy { observable.toViewFlowable() } - override val liveData: RvmLiveData by lazy { StateLiveData(viewFlowable) } - - inner class Projection internal constructor( - distinctUntilChanged: Boolean, - projectionBlock: (value: T, consumer: Consumer) -> Unit - ) : RvmPropertyBase(), RvmValueProperty { - private val subject = BehaviorSubject.create() - override val consumer: Consumer = Consumer(subject::onNext) - override val observable: Observable = subject.run { - if (distinctUntilChanged) distinctUntilChanged() else this - } - - override val value: R? get() = subject.value - override val viewFlowable: Flowable by lazy { observable.toViewFlowable() } - override val liveData: RvmLiveData by lazy { StateLiveData(viewFlowable) } - - init { - this@RvmState.observable.subscribe { projectionBlock(it, consumer) } - } - } + override val liveData: RvmLiveData by lazy { StateLiveData() } @SuppressLint("CheckResult") - private class StateLiveData(source: Flowable) : RvmLiveData() { + private inner class StateLiveData : RvmLiveData() { init { - source.subscribe { value = it } + viewFlowable.subscribe { value = it } } } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmStateProjection.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmStateProjection.kt new file mode 100644 index 0000000..138791b --- /dev/null +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmStateProjection.kt @@ -0,0 +1,34 @@ +package com.alexdeww.reactiveviewmodel.core.property + +import android.annotation.SuppressLint +import com.alexdeww.reactiveviewmodel.core.livedata.RvmLiveData +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.functions.Consumer +import io.reactivex.rxjava3.subjects.BehaviorSubject + +class RvmStateProjection internal constructor( + initValue: T? = null, +) : RvmPropertyBase(), RvmValueProperty { + + private val subject = when (initValue) { + null -> BehaviorSubject.create() + else -> BehaviorSubject.createDefault(initValue) + } + private val serializedSubject = subject.toSerialized() + + override val consumer: Consumer = Consumer(serializedSubject::onNext) + override val observable: Observable = serializedSubject + + override val value: T? get() = subject.value + override val viewFlowable: Flowable by lazy { observable.toViewFlowable() } + override val liveData: RvmLiveData by lazy { StateLiveData() } + + @SuppressLint("CheckResult") + private inner class StateLiveData : RvmLiveData() { + init { + viewFlowable.subscribe { value = it } + } + } + +} From 3fe0ff3115a542d4dc4610b8a5ab35f211f5120e Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 8 Jan 2023 06:21:59 +0700 Subject: [PATCH 24/31] mark savedStateHandle.state @RvmDslMarker --- .../alexdeww/reactiveviewmodel/core/RvmSavedStateDelegates.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmSavedStateDelegates.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmSavedStateDelegates.kt index 4f3ecf5..a45d43f 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmSavedStateDelegates.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmSavedStateDelegates.kt @@ -1,6 +1,7 @@ package com.alexdeww.reactiveviewmodel.core import androidx.lifecycle.SavedStateHandle +import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker import com.alexdeww.reactiveviewmodel.core.property.RvmState import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadWriteProperty @@ -23,6 +24,7 @@ fun SavedStateHandle.valueNonNull( stateHandle[key] ?: defaultValue } +@RvmDslMarker fun SavedStateHandle.state( initialValue: T? = null, debounceInterval: Long? = null From 2deddb764854a8d186aa571599f5d3b2b7f4f4d9 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 9 Jan 2023 07:54:50 +0700 Subject: [PATCH 25/31] update doc and readme some small fix --- README.md | 202 ++++++++---------- .../core/property/RvmAction.kt | 5 + .../core/property/RvmConfirmationEvent.kt | 14 ++ .../core/property/RvmEvent.kt | 15 +- .../core/property/RvmState.kt | 10 + .../core/property/RvmStateProjection.kt | 9 + .../widget/BaseVisualControl.kt | 6 +- .../reactiveviewmodel/widget/CheckControl.kt | 2 +- .../reactiveviewmodel/widget/RatingControl.kt | 2 +- 9 files changed, 151 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index 42ebea1..7f0c0f1 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ # ReactiveViewModel -This is Android reactive MVVM library, fork https://github.com/dmdevgo/RxPM +Очередная Android Reactive MVVM, за основу взята https://github.com/dmdevgo/RxPM -The Status of the lib: [![](https://jitpack.io/v/AlexDeww/ReactiveViewModel.svg)](https://jitpack.io/#AlexDeww/ReactiveViewModel) -How to use this lib in your project: +Подключение: ```gradle allprojects { repositories { @@ -15,7 +14,6 @@ allprojects { } ``` -Add to your app module build.gradle ```gradle dependencies { implementation "com.github.AlexDeww:ReactiveViewModel:$last_version" @@ -24,147 +22,135 @@ dependencies { ### Пример ViewModel ```kotlin -class EnterSmsCodeViewModel( - val fullPhoneNumber: String, - private val requestSmsCode: RequestSmsCode, - private val registerOrSignIn: RegisterOrSignIn -): ReactiveViewModel() { +class SomeViewModel( + savedState: SavedStateHandle +) : ReactiveViewModel() { - private val eventCancelTimer = eventNone() + val progressVisible by RVM.progressState(initValue = false) + val tryCount by savedState.state(initialValue = 5) + + val tryCountLabelVisible by RVM.stateProjection(tryCount) { it > 0 } + val sendButtonEnable by RVM.stateProjectionFromSource(initialValue = false) { + combineLatest( + progressVisible.observable, + tryCountLabelVisible.observable, + inputCode.data.observable.map { it.length >= 4 } + ) { isProgress, hasTryCount, codeReached -> !isProgress && hasTryCount && codeReached } + } - val progressVisibility = state(initValue, PROGRESS_DEBOUNCE_INTERVAL) - val timerVisibility = state(false) - val timerValue = state() - val blocked = state(false) + val inputCode by savedState.inputControl() - val inputCode = inputControl() + val eventDone by RVM.eventNone() + val eventError by RVM.event() - val eventWrongSmsCode = eventNone() - val eventGoToTripStart = eventNone() - val eventGoToAcceptPolicy = eventNone() - val eventCodeExpired = eventNone() + val actionOnSendCodeClick by RVM.debouncedActionNone() - val actionSendCodeAgainClick = debouncedActionNone() - - init { - inputCode.value.observable - .filter { blocked.value == false && progressVisibility.value == false } - .debounce() - .filter { it.length == SMS_CODE_LENGTH } - .doOnNext { analyticsManager.trackEvent(AppAnalyticEvents.ConfirmPhone) } - .switchMapSingle { register(it) } - .doOnError { inputCode.actionChangeValue.call("") } - .retry() - .subscribe { - when { - it -> eventGoToTripStart.call() - else -> eventGoToAcceptPolicy.call() - } + private val sendCode by RVM.invocable { code -> + Completable + .fromAction { + tryCount.valueNonNull.takeIf { it > 0 }?.let { tryCount.setValue(it - 1) } + Log.d("VM", "Code sent: $code") } - .disposeOnCleared() - - actionSendCodeAgainClick.observable - .filter { blocked.value == false && timerVisibility.value == false && progressVisibility.value == false } - .switchMapSingle { requestCode() } - .switchMap { getTimerObservable(it) } - .retry() - .subscribe() - .disposeOnCleared() - - getRequestSmsTimerValue.execute(Unit) - .takeIf { it > 0 } - ?.let { getTimerObservable(it) } - ?.subscribe() - ?.disposeOnCleared() + .delaySubscription(5, TimeUnit.SECONDS) + .bindProgress(progressVisible.consumer) + .doOnComplete { eventDone.call() } + .doOnError { eventError.call(it) } } + + init { + actionOnSendCodeClick.bind { + this.filter { sendButtonEnable.value == true } + .doOnNext { sendCode(inputCode.data.valueNonNull) } + } + } + } ``` ### Связь с View ```kotlin -class EnterSmsCodeFragment : ReactiveFragment() { +class SomeFragment : ReactiveFragment() { + + private val viewModel: SomeViewModel by Delegates.notNull() - companion object { - private const val ARG_FULL_PHONE_NUMBER = "EnterSmsCodeFragment.ARG_FULL_PHONE_NUMBER" - - fun create(fullPhoneNumber: String): EnterSmsCodeFragment = EnterSmsCodeFragment() - .args { putString(ARG_FULL_PHONE_NUMBER, fullPhoneNumber) } - } - - private val viewModel by viewModel { // koin!!! - parametersOf(arguments?.getString(ARG_FULL_PHONE_NUMBER)!!) - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - - viewModel.progressVisibility.observe { if (it) showProgress() else hideProgress() } - viewModel.timerValue.observe { tvTimer.text = formatTimer(it) } - viewModel.timerVisibility.observe { tvTimer.isVisible = it } - - viewModel.eventError.observe { showError(it) } - viewModel.eventGoToTripStart.observe { router.newRootScreen(Screens.Main.TripSetupFlowScreen()) } - - viewModel.inputCode.bindTo(etSmsCode) - viewModel.actionSendCodeAgainClick.bindOnClick(btnSendAgain) + + viewModel.progressVisible.observe { progressView.isVisible = it } + viewModel.tryCount.observe { tryCountLabel.text = it.toString() } + viewModel.tryCountLabelVisible.observe { tryCountLabel.isVisible = it } + viewModel.sendButtonEnable.observe { sendButton.isEnabled = it } + + viewModel.inputCode.bindTo(codeEditText) + + viewModel.eventDone.observe { /* close fragment */ } + viewModel.eventError.observe { /* show error */ } + + viewModel.actionOnSendCodeClick.bindOnClick(sendButton) } - + } ``` -### State -**State** хранит послдение значение и излучает его при подписке. Используется для передачи значения из ViewModel в View +## Есть 5 базовых объектов для взаимодействия View и ViewModel -Создание -```kotlin -val isProgress = state(false) -``` -Из ViewModel +### RvmState +В основном предназначен для передачи состояния из **ViewModel** во **View**. + - Передавать данные в **RvmState** могут только наследники **RvmPropertiesSupport**. + - Всегда хранит последнее переданное значение. + - Каждый подписчик в первую очередь получит последннее сохраненное значение. + ```kotlin -isProgress.consumer.accept(true) -isProgress.setValue(true) // расширение для isProgress.consumer.accept(true) -isProgress.setValueIfChanged(true) // расширение для isProgress.consumer.accept(true) но с проверкой if (lastValue != newValue) +val state by RVM.state(initialValue = null or data) ``` -В View ```kotlin -isProgress.observe { value -> } +val state by savedStateHandle.state(initialValue = null or data) ``` -### Action -**Action** ипользуется для передачи событий или параметров из View в ViewModel +### RvmStateProjection +Почти тоже самое, что и **RvmState**, но отличается тем, что никто не может передавать данные няпрямую. + - Никто не может передавать данные няпрямую. **RvmStateProjection** может получать данные от источников: **Observable**, **RvmState**, **RvmStateProjection**, либо объекта наследника **RvmPropertyBase** и **RvmValueProperty**. -Создание -```kotlin -val actionSendSmsCodeAgain = action() // or actionEmpty() если тип Unit -``` -Из ViewModel ```kotlin -actionSendSmsCodeAgain.consumer.accept(Unit) -actionSendSmsCodeAgain.call() // расширение для actionSendSmsCodeAgain.consumer.accept(Unit) +val state by RVM.state(initialValue = null or data) +val stateProjection by RVM.stateProjection(state) { /* map block */ } ``` -В View ```kotlin -actionSendSmsCodeAgain.bindOnClick(btnSendSmsCode) -btnSendSmsCode.setOnClickListener { actionSendSmsCodeAgain.call() } +val stateProjection by RVM.stateProjectionFromSource(initialValue = null or data) { ObservableSource } ``` -### Event -**Event** ипользуется для передачи событий или параметров из ViewModel в View. Хранит последнее переданное значение, пока не появится подписчик. +### RvmEvent +В основном предназначен для передачи событий или данных из **ViewModel** во **View**. + - Передавать данные в **RvmEvent** могут только наследники **RvmPropertiesSupport**. + - Хранит последнее переданное значение пока не появится подписчик. Только первый подписчик получит последнее сохраненное значение, все последующие подписки, будут получать только новые значения. + - Пока есть активная подписка, данные не сохраняются. -Создание ```kotlin -val eventDone = event() // or eventEmpty() если тип Unit +val event by RVM.event() ``` -Из ViewModel ```kotlin -eventDone.consumer.accept(Unit) -eventDone.call() // расширение для eventDone.consumer.accept(Unit) +val event by RVM.eventNone() // for Unit Data Type +``` + +### RvmConfirmationEvent +Почти тоже самое, что и **RvmEvent**, но отличается тем, что хранит последнее значение пока не будет вызван метод **confirm**. + - Передавать данные в **RvmConfirmationEvent** могут только наследники **RvmPropertiesSupport**. + - Хранит последнее переданное значение пока не будет вызван метод **confirm**. Каждый новый подписчик будет получать последнее сохраненное значение, пока не вызван метод **confirm**. + +```kotlin +val confirmationEvent by RVM.confirmationEvent() ``` -В View ```kotlin -eventDone.observe { value -> } +val confirmationEvent by RVM.confirmationEventNone() // for Unit Data Type ``` +### RvmAction +В основном предназначен для передачи событий или данных из **View** во **ViewModel**. + - Не хранит данные. -## Инфо -Вся либа, надстройка над LiveData. Cвойства(state, event) имею поле **liveData** для возможности совместного использования с **DataBinding** +```kotlin +val action by RVM.action() +``` +```kotlin +val action by RVM.actionNone() // for Unit Data Type +``` diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmAction.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmAction.kt index 721efa5..f639985 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmAction.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmAction.kt @@ -4,6 +4,11 @@ import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.functions.Consumer import io.reactivex.rxjava3.subjects.PublishSubject +/** + * В основном предназначен для передачи событий или данных из View во ViewModel. + * + * * Не хранит данные. + */ class RvmAction internal constructor( debounceInterval: Long? = null ) : RvmProperty() { diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmConfirmationEvent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmConfirmationEvent.kt index ada9098..038bc33 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmConfirmationEvent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmConfirmationEvent.kt @@ -11,6 +11,17 @@ import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.functions.Consumer +/** + * Почти тоже самое, что и [RvmEvent], но отличается тем, что хранит последнее значение + * пока не будет вызван метод [confirm]. + * + * * Передавать данные в [RvmConfirmationEvent] могут только наследники + * [RvmPropertiesSupport][com.alexdeww.reactiveviewmodel.core.RvmPropertiesSupport]. + * + * * Хранит последнее переданное значение пока не будет вызван метод [confirm]. + * Каждый новый подписчик будет получать последнее сохраненное значение, + * пока не вызван метод [confirm]. + */ class RvmConfirmationEvent internal constructor( debounceInterval: Long? = null ) : RvmProperty(), RvmCallableProperty { @@ -38,6 +49,9 @@ class RvmConfirmationEvent internal constructor( override val viewFlowable: Flowable by lazy { observable.toViewFlowable() } val isConfirmed: Boolean get() = eventState.value === EventType.Confirmed + /** + * Подтверждение, что данные получены + */ fun confirm() { if (!isConfirmed) eventState.consumer.accept(EventType.Confirmed) } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmEvent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmEvent.kt index cf24eb2..e7bc35d 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmEvent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmEvent.kt @@ -1,6 +1,7 @@ package com.alexdeww.reactiveviewmodel.core.property import androidx.lifecycle.LiveData +import com.alexdeww.reactiveviewmodel.core.livedata.LiveEvent import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.Disposable @@ -8,6 +9,18 @@ import io.reactivex.rxjava3.functions.Consumer import io.reactivex.rxjava3.subjects.BehaviorSubject import java.util.concurrent.atomic.AtomicBoolean +/** + * В основном предназначен для передачи событий или данных из ViewModel во View. + * + * * Передавать данные в [RvmEvent] могут только наследники + * [RvmPropertiesSupport][com.alexdeww.reactiveviewmodel.core.RvmPropertiesSupport]. + * + * * Хранит последнее переданное значение пока не появится подписчик. + * Только первый подписчик получит последнее сохраненное значение, + * все последующие подписки, будут получать только новые значения. + * + * * Пока есть активная подписка, данные не сохраняются. + */ class RvmEvent internal constructor( debounceInterval: Long? = null ) : RvmProperty(), RvmCallableProperty { @@ -36,7 +49,7 @@ class RvmEvent internal constructor( override val liveData: LiveData by lazy { EventLiveData() } override val viewFlowable: Flowable by lazy { observable.toViewFlowable() } - private inner class EventLiveData : LiveData() { + private inner class EventLiveData : LiveEvent() { private var disposable: Disposable? = null diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt index f7c7ccd..da326f8 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt @@ -7,6 +7,16 @@ import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.functions.Consumer import io.reactivex.rxjava3.subjects.BehaviorSubject +/** + * В основном предназначен для передачи состояния из ViewModel во View. + * + * * Передавать данные в [RvmState] могут только наследники + * [RvmPropertiesSupport][com.alexdeww.reactiveviewmodel.core.RvmPropertiesSupport]. + * + * * Всегда хранит последнее переданное значение. + * + * * Каждый подписчик в первую очередь получит последннее сохраненное значение. + */ class RvmState internal constructor( initValue: T? = null, debounceInterval: Long? = null diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmStateProjection.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmStateProjection.kt index 138791b..a4e6207 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmStateProjection.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmStateProjection.kt @@ -7,6 +7,15 @@ import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.functions.Consumer import io.reactivex.rxjava3.subjects.BehaviorSubject +/** + * Почти тоже самое, что и [RvmState], но отличается тем, + * что никто не может передавать данные няпрямую. + * + * * Никто не может передавать данные няпрямую. + * [RvmStateProjection] может получать данные от источников: + * [Observable], [RvmState], [RvmStateProjection], + * либо объекта наследника [RvmPropertyBase] и [RvmValueProperty]. + */ class RvmStateProjection internal constructor( initValue: T? = null, ) : RvmPropertyBase(), RvmValueProperty { diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt index 6e3e780..e88992a 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt @@ -163,10 +163,10 @@ fun > SavedStateHandle.visualControlDelegat control } -typealias ControlDefaultConstrictor = (value: T, isEnabled: Boolean, visibility: Visibility) -> C +typealias ControlDefaultConstructor = (value: T, isEnabled: Boolean, visibility: Visibility) -> C -inline fun > controlDefaultConstrictor( - crossinline defaultConstructor: ControlDefaultConstrictor +inline fun > controlDefaultConstructor( + crossinline defaultConstructor: ControlDefaultConstructor ): InitControl = { value: T, isEnabled: Boolean, visibility: Visibility, _, _ -> defaultConstructor(value, isEnabled, visibility) } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt index 261081a..9b36151 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt @@ -73,5 +73,5 @@ fun SavedStateHandle.checkControl( initialValue = initialChecked, initialEnabled = initialEnabled, initialVisibility = initialVisibility, - initControl = controlDefaultConstrictor(::CheckControl) + initControl = controlDefaultConstructor(::CheckControl) ) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt index c7178b8..9b57eab 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt @@ -73,5 +73,5 @@ fun SavedStateHandle.ratingControl( initialValue = initialValue, initialEnabled = initialEnabled, initialVisibility = initialVisibility, - initControl = controlDefaultConstrictor(::RatingControl) + initControl = controlDefaultConstructor(::RatingControl) ) From 457715373c58c9c24d663d04257320a5a9cbc35c Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 10 Jan 2023 06:15:37 +0700 Subject: [PATCH 26/31] refactoring bind args names --- .../reactiveviewmodel/component/ReactiveViewModel.kt | 8 ++++---- .../reactiveviewmodel/core/RvmViewModelComponent.kt | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt index a6eec0e..27b1b02 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/component/ReactiveViewModel.kt @@ -66,16 +66,16 @@ abstract class ReactiveViewModel : ViewModel(), RvmViewModelComponent { @RvmBinderDslMarker final override fun RvmAction.bind( - transformChainBlock: Observable.() -> Observable + chainBlock: Observable.() -> Observable ) { - defaultViewModelComponent.run { bind(transformChainBlock) } + defaultViewModelComponent.run { bind(chainBlock) } } @RvmBinderDslMarker final override fun RvmState.bind( - transformChainBlock: Observable.() -> Observable + chainBlock: Observable.() -> Observable ) { - defaultViewModelComponent.run { bind(transformChainBlock) } + defaultViewModelComponent.run { bind(chainBlock) } } } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt index 8995fa9..fd94cf2 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt @@ -29,13 +29,13 @@ interface RvmViewModelComponent : RvmPropertiesSupport, RvmAutoDisposableSupport @RvmBinderDslMarker infix fun RvmAction.bind( - transformChainBlock: Observable.() -> Observable - ) = bindProperty(this, transformChainBlock) + chainBlock: Observable.() -> Observable + ) = bindProperty(this, chainBlock) @RvmBinderDslMarker infix fun RvmState.bind( - transformChainBlock: Observable.() -> Observable - ) = bindProperty(this, transformChainBlock) + chainBlock: Observable.() -> Observable + ) = bindProperty(this, chainBlock) } @@ -63,7 +63,7 @@ fun RVM.stateProjectionFromSource( internal fun RvmViewModelComponent.bindProperty( rvmProperty: RvmProperty, - transformChainBlock: Observable.() -> Observable + chainBlock: Observable.() -> Observable ) { // 1 - need skip, 2 - has value (skip only if source has value) val skipState = AtomicInteger(0) @@ -72,7 +72,7 @@ internal fun RvmViewModelComponent.bindProperty( .apply { connect().autoDispose() } .doOnNext { skipState.compareAndSet(0, 2) } .skipWhile { skipState.compareAndSet(1, 2) } - .transformChainBlock() + .chainBlock() .doOnError { skipState.compareAndSet(2, 1) } Observable From 5108a2b06ce8b6d5bd2acac40e9b420df74f1129 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 10 Jan 2023 06:58:29 +0700 Subject: [PATCH 27/31] refactoring stateProjectionFromSource make late subs on source --- .../core/RvmViewModelComponent.kt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt index fd94cf2..3b84660 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewModelComponent.kt @@ -53,15 +53,11 @@ fun RVM.stateProjectionFromSource( sourceBlock: () -> Observable ): ReadOnlyProperty> = RvmPropertyDelegate.def { val projection = RvmStateProjection(initialValue) - sourceBlock() - .applyDefaultErrorHandler() - .retry() - .subscribe(projection.consumer) - .autoDispose() + lateSubscription(sourceBlock().doOnNext(projection.consumer)) projection } -internal fun RvmViewModelComponent.bindProperty( +private fun RvmViewModelComponent.bindProperty( rvmProperty: RvmProperty, chainBlock: Observable.() -> Observable ) { @@ -75,10 +71,15 @@ internal fun RvmViewModelComponent.bindProperty( .chainBlock() .doOnError { skipState.compareAndSet(2, 1) } + lateSubscription(source) +} + +private fun RvmViewModelComponent.lateSubscription( + source: Observable<*> +) { Observable - .fromCallable { 0 } - .observeOn(AndroidSchedulers.mainThread()) - .switchMap { source.applyDefaultErrorHandler().retry() } + .defer { source.applyDefaultErrorHandler().retry() } + .subscribeOn(AndroidSchedulers.mainThread()) .subscribe() .autoDispose() } From c590d0b59cb3160daa919d246ed5f878611dd1c5 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 10 Jan 2023 15:29:44 +0700 Subject: [PATCH 28/31] refactoring args naming --- README.md | 2 +- .../reactiveviewmodel/core/RvmPropertiesSupport.kt | 8 ++++---- .../alexdeww/reactiveviewmodel/core/property/RvmState.kt | 6 +++--- .../reactiveviewmodel/core/property/RvmStateProjection.kt | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 7f0c0f1..f4c22fa 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ class SomeViewModel( savedState: SavedStateHandle ) : ReactiveViewModel() { - val progressVisible by RVM.progressState(initValue = false) + val progressVisible by RVM.progressState(initialValue = false) val tryCount by savedState.state(initialValue = 5) val tryCountLabelVisible by RVM.stateProjection(tryCount) { it > 0 } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt index 37d1a40..c035d40 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmPropertiesSupport.kt @@ -37,10 +37,10 @@ interface RvmPropertiesSupport { @Suppress("unused") @RvmDslMarker fun RVM.state( - initValue: T? = null, + initialValue: T? = null, debounceInterval: Long? = null ): ReadOnlyProperty> = RvmPropertyDelegate.def { - RvmState(initValue, debounceInterval) + RvmState(initialValue, debounceInterval) } @Suppress("unused") @@ -65,10 +65,10 @@ fun RVM.stateProjection( @Suppress("unused") @RvmDslMarker fun RVM.progressState( - initValue: Boolean? = null, + initialValue: Boolean? = null, debounceInterval: Long = DEF_PROGRESS_DEBOUNCE_INTERVAL ): ReadOnlyProperty> = state( - initValue = initValue, + initialValue = initialValue, debounceInterval = debounceInterval ) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt index da326f8..c5c48b0 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmState.kt @@ -18,13 +18,13 @@ import io.reactivex.rxjava3.subjects.BehaviorSubject * * Каждый подписчик в первую очередь получит последннее сохраненное значение. */ class RvmState internal constructor( - initValue: T? = null, + initialValue: T? = null, debounceInterval: Long? = null ) : RvmProperty(), RvmMutableValueProperty { - private val subject = when (initValue) { + private val subject = when (initialValue) { null -> BehaviorSubject.create() - else -> BehaviorSubject.createDefault(initValue) + else -> BehaviorSubject.createDefault(initialValue) } private val serializedSubject = subject.toSerialized() diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmStateProjection.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmStateProjection.kt index a4e6207..099347e 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmStateProjection.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/property/RvmStateProjection.kt @@ -17,12 +17,12 @@ import io.reactivex.rxjava3.subjects.BehaviorSubject * либо объекта наследника [RvmPropertyBase] и [RvmValueProperty]. */ class RvmStateProjection internal constructor( - initValue: T? = null, + initialValue: T? = null, ) : RvmPropertyBase(), RvmValueProperty { - private val subject = when (initValue) { + private val subject = when (initialValue) { null -> BehaviorSubject.create() - else -> BehaviorSubject.createDefault(initValue) + else -> BehaviorSubject.createDefault(initialValue) } private val serializedSubject = subject.toSerialized() From 204546fceee3890d512d72f16db7b53284b0f437 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 10 Jan 2023 15:32:58 +0700 Subject: [PATCH 29/31] fix readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f4c22fa..2349ca9 100644 --- a/README.md +++ b/README.md @@ -108,8 +108,8 @@ val state by savedStateHandle.state(initialValue = null or data) ``` ### RvmStateProjection -Почти тоже самое, что и **RvmState**, но отличается тем, что никто не может передавать данные няпрямую. - - Никто не может передавать данные няпрямую. **RvmStateProjection** может получать данные от источников: **Observable**, **RvmState**, **RvmStateProjection**, либо объекта наследника **RvmPropertyBase** и **RvmValueProperty**. +Почти тоже самое, что и **RvmState**, но отличается тем, что никто не может передавать данные напрямую. + - Никто не может передавать данные напрямую. **RvmStateProjection** может получать данные от источников: **Observable**, **RvmState**, **RvmStateProjection**, либо объекта наследника **RvmPropertyBase** и **RvmValueProperty**. ```kotlin val state by RVM.state(initialValue = null or data) From c0a080e9373d773532a4dfd48a0a18bdcbf03822 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 11 Jan 2023 04:35:15 +0700 Subject: [PATCH 30/31] refactoring widget naming update readme --- README.md | 141 +++++++++++++++++- .../core/RvmViewComponent.kt | 4 +- .../{BaseControl.kt => RvmBaseControl.kt} | 2 +- ...sualControl.kt => RvmBaseVisualControl.kt} | 48 +++--- .../{CheckControl.kt => RvmCheckControl.kt} | 18 +-- .../{DialogControl.kt => RvmDialogControl.kt} | 62 ++++---- ...bleControl.kt => RvmDisplayableControl.kt} | 22 +-- .../{InputControl.kt => RvmInputControl.kt} | 20 +-- .../{RatingControl.kt => RvmRatingControl.kt} | 18 +-- .../widget/RvmWidgetBindShortcut.kt | 24 +-- 10 files changed, 250 insertions(+), 109 deletions(-) rename reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/{BaseControl.kt => RvmBaseControl.kt} (96%) rename reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/{BaseVisualControl.kt => RvmBaseVisualControl.kt} (73%) rename reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/{CheckControl.kt => RvmCheckControl.kt} (76%) rename reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/{DialogControl.kt => RvmDialogControl.kt} (67%) rename reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/{DisplayableControl.kt => RvmDisplayableControl.kt} (76%) rename reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/{InputControl.kt => RvmInputControl.kt} (89%) rename reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/{RatingControl.kt => RvmRatingControl.kt} (75%) diff --git a/README.md b/README.md index 2349ca9..acaf673 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,17 @@ # ReactiveViewModel -Очередная Android Reactive MVVM, за основу взята https://github.com/dmdevgo/RxPM +Очередная библиотека Android Reactive MVVM, за основу взята https://github.com/dmdevgo/RxPM + +Основная цель библиотеки, это организовать направление данных **View** <-> **ViewModel** и **сокрытия** методов по изменению состояния напрямую из **View**. + +Пример такого сокрытия вы могли видеть (или применять) при использовании **LiveData** + +```kotlin +prvate val _state = MutableLiveData() +val state: LiveData = _state +``` + +Вместо этого, библиотека предлагает делать это [так](#пример-viewmodel) [![](https://jitpack.io/v/AlexDeww/ReactiveViewModel.svg)](https://jitpack.io/#AlexDeww/ReactiveViewModel) @@ -100,6 +111,8 @@ class SomeFragment : ReactiveFragment() { - Всегда хранит последнее переданное значение. - Каждый подписчик в первую очередь получит последннее сохраненное значение. +Объявление: + ```kotlin val state by RVM.state(initialValue = null or data) ``` @@ -107,10 +120,19 @@ val state by RVM.state(initialValue = null or data) val state by savedStateHandle.state(initialValue = null or data) ``` +Подписка: + +```kotlin +viewModel.state.observe { value -> /* do */ } +``` + + ### RvmStateProjection Почти тоже самое, что и **RvmState**, но отличается тем, что никто не может передавать данные напрямую. - Никто не может передавать данные напрямую. **RvmStateProjection** может получать данные от источников: **Observable**, **RvmState**, **RvmStateProjection**, либо объекта наследника **RvmPropertyBase** и **RvmValueProperty**. +Объявление: + ```kotlin val state by RVM.state(initialValue = null or data) val stateProjection by RVM.stateProjection(state) { /* map block */ } @@ -119,12 +141,20 @@ val stateProjection by RVM.stateProjection(state) { /* map block */ } val stateProjection by RVM.stateProjectionFromSource(initialValue = null or data) { ObservableSource } ``` +Подписка: + +```kotlin +viewModel.stateProjection.observe { value -> /* do */ } +``` + ### RvmEvent В основном предназначен для передачи событий или данных из **ViewModel** во **View**. - Передавать данные в **RvmEvent** могут только наследники **RvmPropertiesSupport**. - Хранит последнее переданное значение пока не появится подписчик. Только первый подписчик получит последнее сохраненное значение, все последующие подписки, будут получать только новые значения. - Пока есть активная подписка, данные не сохраняются. +Объявление: + ```kotlin val event by RVM.event() ``` @@ -132,11 +162,19 @@ val event by RVM.event() val event by RVM.eventNone() // for Unit Data Type ``` +Подписка: + +```kotlin +viewModel.event.observe { value -> /* do */ } +``` + ### RvmConfirmationEvent Почти тоже самое, что и **RvmEvent**, но отличается тем, что хранит последнее значение пока не будет вызван метод **confirm**. - Передавать данные в **RvmConfirmationEvent** могут только наследники **RvmPropertiesSupport**. - Хранит последнее переданное значение пока не будет вызван метод **confirm**. Каждый новый подписчик будет получать последнее сохраненное значение, пока не вызван метод **confirm**. +Объявление: + ```kotlin val confirmationEvent by RVM.confirmationEvent() ``` @@ -144,13 +182,114 @@ val confirmationEvent by RVM.confirmationEvent() val confirmationEvent by RVM.confirmationEventNone() // for Unit Data Type ``` +Подписка: + +```kotlin +viewModel.confirmationEvent.observe { value -> + /* do */ + viewModel.confirmationEvent.confirm() +} +``` + ### RvmAction В основном предназначен для передачи событий или данных из **View** во **ViewModel**. - Не хранит данные. +Объявление: + ```kotlin val action by RVM.action() ``` ```kotlin val action by RVM.actionNone() // for Unit Data Type ``` + +Привязка: + +```kotlin +viewModel.action.bindOnClick(someView) +``` + +## Также имеется 4 вспомогательных объекта для удобной связи View-элементов с ViewModel + +### RvmCheckControl +Для связи **CompoundButton** и **ViewModel**. + +Объявление: + +```kotlin +val checkControl by RVM.checkControl(initialChecked = false) +``` +```kotlin +val checkControl by savedStateHandle.checkControl(initialChecked = false) +``` + +Привязка: + +```kotlin +viewModel.checkControl.bindTo(compoundButton) +``` + +### RvmRatingControl +Для связи **RatingBar** и **ViewModel**. + +Объявление: + +```kotlin +val ratingControl by RVM.ratingControl(initialValue = 3) +``` +```kotlin +val ratingControl by savedStateHandle.ratingControl(initialValue = 3) +``` + +Привязка: + +```kotlin +viewModel.ratingControl.bindTo(ratingBar) +``` + +### RvmInputControl +Для связи **EditText** или **TextInputLayout** и **ViewModel**. + +Объявление: + +```kotlin +val inputControl by RVM.inputControl(initialText = "Some Text") +``` +```kotlin +val inputControl by savedStateHandle.inputControl(initialText = "Some Text") +``` + +Привязка: + +```kotlin +viewModel.inputControl.bindTo(editText) +``` +```kotlin +viewModel.inputControl.bindTo(textInputLayout) +``` + +### RvmDialogControl +Для связи **Dialog** и **ViewModel**. + +Объявление: + +```kotlin +val dialogControl by RVM.dialogControl() +``` +```kotlin +val dialogControl by RVM.dialogControlDefaultResult() +``` + +Привязка: + +```kotlin +viewModel.dialogControl.bindTo { data: DataType, dc: RvmDialogControlResult -> + MaterialAlertDialogBuilder(context) + .title("Title") + .message("Message") + .setPositiveButton("OK") { _, _ -> dc.sendResult(ResultDataType) } + .setNegativeButton("Cancel") { _, _ -> dc.sendResult(ResultDataType) } + .create() +} +``` diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt index cdf71dd..d418821 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/core/RvmViewComponent.kt @@ -5,7 +5,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.Observer import com.alexdeww.reactiveviewmodel.core.RvmAutoDisposableSupport.StoreKey import com.alexdeww.reactiveviewmodel.core.property.RvmObservableProperty -import com.alexdeww.reactiveviewmodel.widget.BaseControl +import com.alexdeww.reactiveviewmodel.widget.RvmBaseControl import io.reactivex.rxjava3.disposables.Disposable interface RvmViewComponent : RvmAutoDisposableSupport { @@ -27,7 +27,7 @@ interface RvmViewComponent : RvmAutoDisposableSupport { fun RvmObservableProperty.observe(action: OnLiveDataAction): Observer = observe(componentLifecycleOwner, action) - val > C.binder: B + val > C.binder: B get() = getBinder(this@RvmViewComponent) } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmBaseControl.kt similarity index 96% rename from reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt rename to reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmBaseControl.kt index 01fff54..ba59f2a 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmBaseControl.kt @@ -11,7 +11,7 @@ import io.reactivex.rxjava3.functions.Consumer import java.lang.ref.WeakReference @Suppress("UnnecessaryAbstractClass") -abstract class BaseControl : RvmPropertiesSupport { +abstract class RvmBaseControl : RvmPropertiesSupport { private val defaultPropertiesSupport = object : RvmPropertiesSupport {} diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmBaseVisualControl.kt similarity index 73% rename from reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt rename to reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmBaseVisualControl.kt index e88992a..0f61b4c 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/BaseVisualControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmBaseVisualControl.kt @@ -5,37 +5,37 @@ import androidx.annotation.CallSuper import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.SavedStateHandle import com.alexdeww.reactiveviewmodel.core.* -import com.alexdeww.reactiveviewmodel.widget.BaseVisualControl.Visibility +import com.alexdeww.reactiveviewmodel.widget.RvmBaseVisualControl.Visibility import io.reactivex.rxjava3.functions.Consumer import java.lang.ref.WeakReference import kotlin.properties.ReadOnlyProperty -typealias ActionOnValueChanged = (newValue: T) -> Unit -typealias ActionOnActive = VisualControlLiveDataMediator.() -> Unit -typealias ActionOnInactive = VisualControlLiveDataMediator.() -> Unit +typealias RvmActionOnValueChanged = (newValue: T) -> Unit +typealias RvmActionOnActive = RvmVisualControlLiveDataMediator.() -> Unit +typealias RvmActionOnInactive = RvmVisualControlLiveDataMediator.() -> Unit -abstract class BaseVisualControl>( +abstract class RvmBaseVisualControl>( initialValue: T, initialEnabled: Boolean, initialVisibility: Visibility -) : BaseControl() { +) : RvmBaseControl() { abstract class BaseBinder( rvmViewComponent: RvmViewComponent ) : ViewBinder(rvmViewComponent) { - protected abstract val control: BaseVisualControl + protected abstract val control: RvmBaseVisualControl @Suppress("LongParameterList") protected fun bindTo( view: V, bindEnable: Boolean, bindVisible: Boolean, - onValueChanged: ActionOnValueChanged, - onActiveAction: ActionOnActive, - onInactiveAction: ActionOnInactive + onValueChanged: RvmActionOnValueChanged, + onActiveAction: RvmActionOnActive, + onInactiveAction: RvmActionOnInactive ) { - val liveData = VisualControlLiveDataMediator( + val liveData = RvmVisualControlLiveDataMediator( control = control, view = view, bindEnable = bindEnable, @@ -78,20 +78,20 @@ abstract class BaseVisualControl } @Suppress("LongParameterList") -class VisualControlLiveDataMediator internal constructor( - control: BaseVisualControl, +class RvmVisualControlLiveDataMediator internal constructor( + control: RvmBaseVisualControl, view: View, private val bindEnable: Boolean, private val bindVisible: Boolean, - private val onValueChanged: ActionOnValueChanged, - private val onActiveAction: ActionOnActive, - private val onInactiveAction: ActionOnInactive + private val onValueChanged: RvmActionOnValueChanged, + private val onActiveAction: RvmActionOnActive, + private val onInactiveAction: RvmActionOnInactive ) : MediatorLiveData() { private val viewRef = WeakReference(view) private val view: View? get() = viewRef.get() private val controlRef = WeakReference(control) - private val control: BaseVisualControl? get() = controlRef.get() + private val control: RvmBaseVisualControl? get() = controlRef.get() private var isEditing: Boolean = false val changeValueConsumer = Consumer { @@ -124,7 +124,7 @@ class VisualControlLiveDataMediator internal constructor( } -typealias InitControl = ( +typealias RvmInitControl = ( value: T, isEnabled: Boolean, visibility: Visibility, @@ -132,11 +132,11 @@ typealias InitControl = ( key: String, ) -> C -fun > SavedStateHandle.visualControlDelegate( +fun > SavedStateHandle.visualControlDelegate( initialValue: T, initialEnabled: Boolean, initialVisibility: Visibility, - initControl: InitControl, + initControl: RvmInitControl, watcher: RvmViewModelComponent.(stateHandle: SavedStateHandle, key: String) -> Unit = { _, _ -> } ): ReadOnlyProperty = delegate { thisRef, stateHandle, key -> val dataKey = "$key.data" @@ -163,10 +163,10 @@ fun > SavedStateHandle.visualControlDelegat control } -typealias ControlDefaultConstructor = (value: T, isEnabled: Boolean, visibility: Visibility) -> C +typealias RvmControlDefaultConstructor = (value: T, isEnabled: Boolean, visibility: Visibility) -> C -inline fun > controlDefaultConstructor( - crossinline defaultConstructor: ControlDefaultConstructor -): InitControl = { value: T, isEnabled: Boolean, visibility: Visibility, _, _ -> +inline fun > rvmControlDefaultConstructor( + crossinline defaultConstructor: RvmControlDefaultConstructor +): RvmInitControl = { value: T, isEnabled: Boolean, visibility: Visibility, _, _ -> defaultConstructor(value, isEnabled, visibility) } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmCheckControl.kt similarity index 76% rename from reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt rename to reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmCheckControl.kt index 9b36151..bd3583c 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/CheckControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmCheckControl.kt @@ -8,11 +8,11 @@ import com.alexdeww.reactiveviewmodel.core.annotation.RvmDslMarker import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyDelegate import kotlin.properties.ReadOnlyProperty -class CheckControl internal constructor( +class RvmCheckControl internal constructor( initialChecked: Boolean, initialEnabled: Boolean, initialVisibility: Visibility -) : BaseVisualControl( +) : RvmBaseVisualControl( initialValue = initialChecked, initialEnabled = initialEnabled, initialVisibility = initialVisibility @@ -24,7 +24,7 @@ class CheckControl internal constructor( rvmViewComponent: RvmViewComponent ) : BaseBinder(rvmViewComponent) { - override val control: BaseVisualControl get() = this@CheckControl + override val control: RvmBaseVisualControl get() = this@RvmCheckControl @RvmBinderDslMarker fun bindTo( @@ -55,9 +55,9 @@ class CheckControl internal constructor( fun RVM.checkControl( initialChecked: Boolean = false, initialEnabled: Boolean = true, - initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = RvmPropertyDelegate.def { - CheckControl( + initialVisibility: RvmBaseVisualControl.Visibility = RvmBaseVisualControl.Visibility.VISIBLE +): ReadOnlyProperty = RvmPropertyDelegate.def { + RvmCheckControl( initialChecked = initialChecked, initialEnabled = initialEnabled, initialVisibility = initialVisibility @@ -68,10 +68,10 @@ fun RVM.checkControl( fun SavedStateHandle.checkControl( initialChecked: Boolean = false, initialEnabled: Boolean = true, - initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = visualControlDelegate( + initialVisibility: RvmBaseVisualControl.Visibility = RvmBaseVisualControl.Visibility.VISIBLE +): ReadOnlyProperty = visualControlDelegate( initialValue = initialChecked, initialEnabled = initialEnabled, initialVisibility = initialVisibility, - initControl = controlDefaultConstructor(::CheckControl) + initControl = rvmControlDefaultConstructor(::RvmCheckControl) ) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmDialogControl.kt similarity index 67% rename from reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt rename to reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmDialogControl.kt index 2939b82..ee0ab1e 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DialogControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmDialogControl.kt @@ -9,13 +9,15 @@ import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyDelegate import io.reactivex.rxjava3.core.Maybe import kotlin.properties.ReadOnlyProperty -sealed class DialogResult { - object Accept : DialogResult() - object Cancel : DialogResult() +typealias RvmDialogCreator = (data: T, dc: RvmDialogControlResult) -> D + +sealed class RvmDefaultDialogResult { + object Accept : RvmDefaultDialogResult() + object Cancel : RvmDefaultDialogResult() } -class DialogControl internal constructor() : - BaseControl.Binder>() { +class RvmDialogControl internal constructor() : + RvmBaseControl.Binder>() { sealed class Display { data class Displayed(val data: T) : Display() @@ -59,11 +61,11 @@ class DialogControl internal constructor() : @RvmBinderDslMarker fun bindTo( - dialogHandlerListener: DialogHandlerListener, - dialogCreator: DialogCreator, + dialogHandlerListener: RvmDialogHandlerListener, + dialogCreator: RvmDialogCreator, ) { - val liveData = DialogLiveDataMediator( - control = this@DialogControl, + val liveData = RvmDialogLiveDataMediator( + control = this@RvmDialogControl, dialogCreator = dialogCreator, dialogHandlerListener = dialogHandlerListener ) @@ -71,8 +73,8 @@ class DialogControl internal constructor() : } @RvmBinderDslMarker - fun bindTo(dialogCreator: DialogCreator) = bindTo( - dialogHandlerListener = OrdinaryDialogHandlerListener(), + fun bindTo(dialogCreator: RvmDialogCreator) = bindTo( + dialogHandlerListener = RvmOrdinaryDialogHandlerListener(), dialogCreator = dialogCreator ) @@ -80,8 +82,8 @@ class DialogControl internal constructor() : } -class DialogControlResult internal constructor( - private val dialogControl: DialogControl<*, R> +class RvmDialogControlResult internal constructor( + private val dialogControl: RvmDialogControl<*, R> ) { fun sendResult(result: R) { @@ -101,17 +103,17 @@ class DialogControlResult internal constructor( @Suppress("unused") @RvmDslMarker -fun RVM.dialogControl(): ReadOnlyProperty> = - RvmPropertyDelegate.def { DialogControl() } +fun RVM.dialogControl(): ReadOnlyProperty< + RvmPropertiesSupport, + RvmDialogControl> = RvmPropertyDelegate.def { RvmDialogControl() } @Suppress("unused") @RvmDslMarker -fun RVM.dialogControlWithResult(): ReadOnlyProperty> = - dialogControl() - -typealias DialogCreator = (data: T, dc: DialogControlResult) -> D +fun RVM.dialogControlDefaultResult(): ReadOnlyProperty< + RvmPropertiesSupport, + RvmDialogControl> = dialogControl() -interface DialogHandlerListener { +interface RvmDialogHandlerListener { fun onSetupOnDismiss(dialog: D, dismissAction: () -> Unit) @@ -125,12 +127,12 @@ interface DialogHandlerListener { } -private class DialogLiveDataMediator( - private val control: DialogControl, - private val dialogCreator: DialogCreator, - private val dialogHandlerListener: DialogHandlerListener -) : MediatorLiveData>(), - DialogHandlerListener by dialogHandlerListener { +private class RvmDialogLiveDataMediator( + private val control: RvmDialogControl, + private val dialogCreator: RvmDialogCreator, + private val dialogHandlerListener: RvmDialogHandlerListener +) : MediatorLiveData>(), + RvmDialogHandlerListener by dialogHandlerListener { private var dialog: D? = null @@ -139,13 +141,13 @@ private class DialogLiveDataMediator( addSource(control.displayedInternal.liveData) { displayData -> value = displayData when (displayData) { - is DialogControl.Display.Displayed -> { - dialog = dialogCreator(displayData.data, DialogControlResult(control)).also { + is RvmDialogControl.Display.Displayed -> { + dialog = dialogCreator(displayData.data, RvmDialogControlResult(control)).also { onSetupOnDismiss(it) { control.dismiss() } onShowDialog(it) } } - DialogControl.Display.Absent -> { + RvmDialogControl.Display.Absent -> { dialog?.let { onCloseDialog(it) } releaseDialog() } @@ -166,7 +168,7 @@ private class DialogLiveDataMediator( } -private class OrdinaryDialogHandlerListener : DialogHandlerListener

{ +private class RvmOrdinaryDialogHandlerListener : RvmDialogHandlerListener { override fun onSetupOnDismiss(dialog: Dialog, dismissAction: () -> Unit) { dialog.setOnDismissListener { dismissAction() } diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmDisplayableControl.kt similarity index 76% rename from reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt rename to reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmDisplayableControl.kt index 074835c..8a7aaa6 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/DisplayableControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmDisplayableControl.kt @@ -10,11 +10,11 @@ import kotlinx.parcelize.Parcelize import kotlinx.parcelize.RawValue import kotlin.properties.ReadOnlyProperty -typealias DisplayableAction = (isVisible: Boolean, data: T?) -> Unit +typealias RvmDisplayableAction = (isVisible: Boolean, data: T?) -> Unit -class DisplayableControl internal constructor( +class RvmDisplayableControl internal constructor( debounceInterval: Long? = null -) : BaseControl.Binder>() { +) : RvmBaseControl.Binder>() { sealed class Action : Parcelable { @@ -48,9 +48,9 @@ class DisplayableControl internal constructor( ) : ViewBinder(rvmViewComponent) { @RvmBinderDslMarker - fun bind(action: DisplayableAction) { + fun bind(action: RvmDisplayableAction) { rvmViewComponentRef.get()?.run { - this@DisplayableControl.action.observe { + this@RvmDisplayableControl.action.observe { action.invoke(it.isShowing, it.getShowingValue()) } } @@ -62,7 +62,7 @@ class DisplayableControl internal constructor( onHide: () -> Unit ) { rvmViewComponentRef.get()?.run { - this@DisplayableControl.action.observe { + this@RvmDisplayableControl.action.observe { when (it) { is Action.Show -> onShow.invoke(it.data) else -> onHide.invoke() @@ -79,18 +79,18 @@ class DisplayableControl internal constructor( @RvmDslMarker fun RVM.displayableControl( debounceInterval: Long? = null -): ReadOnlyProperty> = RvmPropertyDelegate.def { - DisplayableControl(debounceInterval) +): ReadOnlyProperty> = RvmPropertyDelegate.def { + RvmDisplayableControl(debounceInterval) } @RvmDslMarker fun SavedStateHandle.displayableControl( debounceInterval: Long? = null -): ReadOnlyProperty> = delegate { thisRef, sh, key -> +): ReadOnlyProperty> = delegate { thisRef, sh, key -> val actionKey = "$key.action" - val control = DisplayableControl(debounceInterval) + val control = RvmDisplayableControl(debounceInterval) thisRef.run { - control.action.setValue(sh[actionKey] ?: DisplayableControl.Action.Hide) + control.action.setValue(sh[actionKey] ?: RvmDisplayableControl.Action.Hide) control.action.viewFlowable .subscribe { sh[actionKey] = it } .autoDispose() diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmInputControl.kt similarity index 89% rename from reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt rename to reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmInputControl.kt index 43852af..106f95c 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/InputControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmInputControl.kt @@ -13,13 +13,13 @@ import kotlin.properties.ReadOnlyProperty typealias FormatterAction = (text: String) -> String -class InputControl internal constructor( +class RvmInputControl internal constructor( initialText: String, private val hideErrorOnUserInput: Boolean, formatter: FormatterAction?, initialEnabled: Boolean, initialVisibility: Visibility -) : BaseVisualControl( +) : RvmBaseVisualControl( initialValue = initialText, initialEnabled = initialEnabled, initialVisibility = initialVisibility @@ -42,7 +42,7 @@ class InputControl internal constructor( rvmViewComponent: RvmViewComponent ) : BaseBinder(rvmViewComponent) { - override val control: BaseVisualControl get() = this@InputControl + override val control: RvmBaseVisualControl get() = this@RvmInputControl @RvmBinderDslMarker fun bindTo( @@ -75,7 +75,7 @@ class InputControl internal constructor( ) @Suppress("LongParameterList") - private fun InputControl.bindTo( + private fun RvmInputControl.bindTo( view: View, editText: EditText, actionOnError: (String) -> Unit, @@ -120,9 +120,9 @@ fun RVM.inputControl( hideErrorOnUserInput: Boolean = true, formatter: FormatterAction? = null, initialEnabled: Boolean = true, - initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = RvmPropertyDelegate.def { - InputControl( + initialVisibility: RvmBaseVisualControl.Visibility = RvmBaseVisualControl.Visibility.VISIBLE +): ReadOnlyProperty = RvmPropertyDelegate.def { + RvmInputControl( initialText = initialText, hideErrorOnUserInput = hideErrorOnUserInput, formatter = formatter, @@ -137,13 +137,13 @@ fun SavedStateHandle.inputControl( hideErrorOnUserInput: Boolean = true, formatter: FormatterAction? = null, initialEnabled: Boolean = true, - initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = visualControlDelegate( + initialVisibility: RvmBaseVisualControl.Visibility = RvmBaseVisualControl.Visibility.VISIBLE +): ReadOnlyProperty = visualControlDelegate( initialValue = initialText, initialEnabled = initialEnabled, initialVisibility = initialVisibility, initControl = { value, isEnabled, visibility, _, _ -> - InputControl( + RvmInputControl( initialText = value, hideErrorOnUserInput = hideErrorOnUserInput, formatter = formatter, diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmRatingControl.kt similarity index 75% rename from reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt rename to reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmRatingControl.kt index 9b57eab..3b14a42 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RatingControl.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmRatingControl.kt @@ -10,11 +10,11 @@ import com.alexdeww.reactiveviewmodel.core.utils.RvmPropertyDelegate import kotlin.properties.ReadOnlyProperty @SuppressLint("CheckResult") -class RatingControl internal constructor( +class RvmRatingControl internal constructor( initialValue: Float, initialEnabled: Boolean, initialVisibility: Visibility -) : BaseVisualControl( +) : RvmBaseVisualControl( initialValue = initialValue, initialEnabled = initialEnabled, initialVisibility = initialVisibility @@ -26,7 +26,7 @@ class RatingControl internal constructor( rvmViewComponent: RvmViewComponent ) : BaseBinder(rvmViewComponent) { - override val control: BaseVisualControl get() = this@RatingControl + override val control: RvmBaseVisualControl get() = this@RvmRatingControl @RvmBinderDslMarker fun bindTo( @@ -55,9 +55,9 @@ class RatingControl internal constructor( fun RVM.ratingControl( initialValue: Float = 0f, initialEnabled: Boolean = true, - initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = RvmPropertyDelegate.def { - RatingControl( + initialVisibility: RvmBaseVisualControl.Visibility = RvmBaseVisualControl.Visibility.VISIBLE +): ReadOnlyProperty = RvmPropertyDelegate.def { + RvmRatingControl( initialValue = initialValue, initialEnabled = initialEnabled, initialVisibility = initialVisibility @@ -68,10 +68,10 @@ fun RVM.ratingControl( fun SavedStateHandle.ratingControl( initialValue: Float = 0f, initialEnabled: Boolean = true, - initialVisibility: BaseVisualControl.Visibility = BaseVisualControl.Visibility.VISIBLE -): ReadOnlyProperty = visualControlDelegate( + initialVisibility: RvmBaseVisualControl.Visibility = RvmBaseVisualControl.Visibility.VISIBLE +): ReadOnlyProperty = visualControlDelegate( initialValue = initialValue, initialEnabled = initialEnabled, initialVisibility = initialVisibility, - initControl = controlDefaultConstructor(::RatingControl) + initControl = rvmControlDefaultConstructor(::RvmRatingControl) ) diff --git a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmWidgetBindShortcut.kt b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmWidgetBindShortcut.kt index 699669c..97dcfe8 100644 --- a/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmWidgetBindShortcut.kt +++ b/reactiveviewmodel/src/main/java/com/alexdeww/reactiveviewmodel/widget/RvmWidgetBindShortcut.kt @@ -12,36 +12,36 @@ import com.google.android.material.textfield.TextInputLayout interface RvmWidgetBindShortcut : RvmViewComponent { @RvmBinderDslMarker - fun CheckControl.bindTo( + fun RvmCheckControl.bindTo( compoundButton: CompoundButton, bindEnable: Boolean = true, bindVisible: Boolean = true ) = binder.bindTo(compoundButton, bindEnable, bindVisible) @RvmBinderDslMarker - fun DialogControl.bindTo( - dialogHandlerListener: DialogHandlerListener, - dialogCreator: DialogCreator, + fun RvmDialogControl.bindTo( + dialogHandlerListener: RvmDialogHandlerListener, + dialogCreator: RvmDialogCreator, ) = binder.bindTo(dialogHandlerListener, dialogCreator) @RvmBinderDslMarker - fun DialogControl.bindTo( - dialogCreator: DialogCreator + fun RvmDialogControl.bindTo( + dialogCreator: RvmDialogCreator ) = binder.bindTo(dialogCreator) @RvmBinderDslMarker - fun DisplayableControl.bind( - action: DisplayableAction + fun RvmDisplayableControl.bind( + action: RvmDisplayableAction ) = binder.bind(action) @RvmBinderDslMarker - fun DisplayableControl.bind( + fun RvmDisplayableControl.bind( onShow: (T) -> Unit, onHide: () -> Unit ) = binder.bind(onShow, onHide) @RvmBinderDslMarker - fun InputControl.bindTo( + fun RvmInputControl.bindTo( editText: EditText, bindError: Boolean = false, bindEnable: Boolean = true, @@ -49,7 +49,7 @@ interface RvmWidgetBindShortcut : RvmViewComponent { ) = binder.bindTo(editText, bindError, bindEnable, bindVisible) @RvmBinderDslMarker - fun InputControl.bindTo( + fun RvmInputControl.bindTo( textInputLayout: TextInputLayout, bindError: Boolean = false, bindEnable: Boolean = true, @@ -57,7 +57,7 @@ interface RvmWidgetBindShortcut : RvmViewComponent { ) = binder.bindTo(textInputLayout, bindError, bindEnable, bindVisible) @RvmBinderDslMarker - fun RatingControl.bindTo( + fun RvmRatingControl.bindTo( ratingBar: RatingBar, bindEnable: Boolean = true, bindVisible: Boolean = true From 65540085315fbc3586e36c232ad77ad04db44399 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 21 Jan 2023 06:53:44 +0700 Subject: [PATCH 31/31] set lib version 3.0.1 --- build.gradle | 2 +- reactiveviewmodel/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 12a1fa4..80d8b90 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { min_sdk_version = 17 sdk_version = 31 - kotlin_version = '1.7.20' + kotlin_version = '1.7.21' rxjava_version = '3.1.2' rxandroid_version = '3.0.0' archx_version = '2.2.0' diff --git a/reactiveviewmodel/build.gradle b/reactiveviewmodel/build.gradle index 0b195ff..3990958 100644 --- a/reactiveviewmodel/build.gradle +++ b/reactiveviewmodel/build.gradle @@ -45,7 +45,7 @@ afterEvaluate { release(MavenPublication) { from components.release groupId = 'com.alexdeww.reactiveviewmodel' - version = '2.5.1' + version = '3.0.1' } } }