From 672ccae32a33dcddd44ac4581553b3df5b7895a3 Mon Sep 17 00:00:00 2001 From: Niclas Date: Sun, 18 Oct 2020 17:32:21 +0200 Subject: [PATCH] fixes #223 -- replace rxjava with coroutines in radar --- .../presentation/radar/RadarFragment.kt | 264 +++++------------ .../presentation/radar/RadarViewModel.kt | 265 +++++++++--------- .../presentation/radar/di/RadarComponent.kt | 25 +- .../presentation/radar/di/RadarModule.kt | 54 +--- 4 files changed, 204 insertions(+), 404 deletions(-) diff --git a/app/src/main/java/fi/kroon/vadret/presentation/radar/RadarFragment.kt b/app/src/main/java/fi/kroon/vadret/presentation/radar/RadarFragment.kt index 2a6ae92f..ae4ee0d1 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/radar/RadarFragment.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/radar/RadarFragment.kt @@ -5,16 +5,14 @@ import android.graphics.drawable.Drawable import android.os.Bundle import android.view.View import androidx.core.graphics.drawable.toBitmap +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import coil.ImageLoader import coil.request.ImageRequest -import com.jakewharton.rxbinding3.view.clicks -import com.jakewharton.rxbinding3.widget.changes -import com.jakewharton.rxbinding3.widget.userChanges import fi.kroon.vadret.BuildConfig import fi.kroon.vadret.R import fi.kroon.vadret.presentation.radar.di.DaggerRadarComponent import fi.kroon.vadret.presentation.radar.di.RadarComponent -import fi.kroon.vadret.presentation.shared.BaseFragment import fi.kroon.vadret.util.DEFAULT_BOUNDINGBOX_CENTER_LATITUDE import fi.kroon.vadret.util.DEFAULT_BOUNDINGBOX_CENTER_LONGITUDE import fi.kroon.vadret.util.DEFAULT_BOUNDINGBOX_LATITUDE_MAX @@ -25,20 +23,19 @@ import fi.kroon.vadret.util.DEFAULT_RADAR_FILE_EXTENSION import fi.kroon.vadret.util.DEFAULT_RADAR_ZOOM_LEVEL import fi.kroon.vadret.util.MAXIMUM_ZOOM_LEVEL import fi.kroon.vadret.util.MINIMUM_ZOOM_LEVEL -import fi.kroon.vadret.util.RADAR_DEBOUNCE_MILLIS import fi.kroon.vadret.util.WIKIMEDIA_TILE_SOURCE_URL import fi.kroon.vadret.util.extension.coreComponent import fi.kroon.vadret.util.extension.lazyAndroid import fi.kroon.vadret.util.extension.snack -import fi.kroon.vadret.util.extension.toObservable -import io.reactivex.Observable -import io.reactivex.disposables.CompositeDisposable -import io.reactivex.disposables.Disposable -import io.reactivex.rxkotlin.addTo -import io.reactivex.subjects.PublishSubject import kotlinx.android.synthetic.main.radar_fragment.radarMapView import kotlinx.android.synthetic.main.radar_fragment.radarPlayFab import kotlinx.android.synthetic.main.radar_fragment.radarSeekBar +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import org.osmdroid.config.Configuration import org.osmdroid.config.IConfigurationProvider import org.osmdroid.tileprovider.tilesource.XYTileSource @@ -46,13 +43,16 @@ import org.osmdroid.util.BoundingBox import org.osmdroid.util.GeoPoint import org.osmdroid.views.CustomZoomButtonsController import org.osmdroid.views.overlay.GroundOverlay +import ru.ldralighieri.corbind.view.clicks +import ru.ldralighieri.corbind.widget.changes +import ru.ldralighieri.corbind.widget.userChanges import timber.log.Timber import java.io.File -import java.util.concurrent.TimeUnit typealias RadarFile = fi.kroon.vadret.data.radar.model.File -class RadarFragment : BaseFragment() { +@ExperimentalCoroutinesApi +class RadarFragment : Fragment(R.layout.radar_fragment) { private companion object { const val A_NAME = "wikimedia" @@ -62,7 +62,7 @@ class RadarFragment : BaseFragment() { const val STATE_PARCEL_KEY = "STATE_PARCEL_KEY" } - private lateinit var disposable: Disposable + private var isConfigChangeOrProcessDeath: Boolean = false private var bundle: Bundle? = null private var stateParcel: RadarView.StateParcel? = null @@ -72,10 +72,6 @@ class RadarFragment : BaseFragment() { .create(context = requireContext(), coreComponent = coreComponent) } - private val scheduler by lazyAndroid { - component.provideScheduler() - } - private val imageLoader: ImageLoader by lazyAndroid { component.provideImageLoader() } @@ -84,70 +80,6 @@ class RadarFragment : BaseFragment() { component.provideRadarViewModel() } - private val onViewInitialisedSubject: PublishSubject by lazy( - LazyThreadSafetyMode.NONE - ) { - component.provideOnViewInitialised() - } - - private val onFailureHandledSubject: PublishSubject by lazy( - LazyThreadSafetyMode.NONE - ) { - component.provideOnFailureHandled() - } - - private val onRadarImageDisplayedSubject: PublishSubject by lazy( - LazyThreadSafetyMode.NONE - ) { - component.provideOnRadarImageDisplayed() - } - - private val onSeekBarStoppedSubject: PublishSubject by lazy( - LazyThreadSafetyMode.NONE - ) { - component.provideOnSeekBarStopped() - } - - private val onStateParcelUpdatedSubject: PublishSubject by lazy( - LazyThreadSafetyMode.NONE - ) { - component.provideOnStateParcelUpdated() - } - - private val onPlayButtonStartedSubject: PublishSubject by lazy( - LazyThreadSafetyMode.NONE - ) { - component.provideOnPlayButtonStarted() - } - - private val onPlayButtonStoppedSubject: PublishSubject by lazy( - LazyThreadSafetyMode.NONE - ) { - component.provideOnPlayButtonStopped() - } - - private val onSeekBarResetSubject: PublishSubject by lazy( - LazyThreadSafetyMode.NONE - ) { - component.provideOnSeekBarReset() - } - - private val onPositionUpdatedSubject: PublishSubject by lazy( - LazyThreadSafetyMode.NONE - ) { - component.provideOnPositionUpdated() - } - - private val onSeekBarRestoredSubject: PublishSubject by lazy( - LazyThreadSafetyMode.NONE - ) { - component.provideOnSeekBarRestored() - } - - private val subscriptions: CompositeDisposable by lazyAndroid { - component.provideCompositeDisposable() - } - private val defaultTileSource = XYTileSource( A_NAME, A_ZOOM_MIN_LEVEL, @@ -157,16 +89,10 @@ class RadarFragment : BaseFragment() { arrayOf(WIKIMEDIA_TILE_SOURCE_URL) ) - override fun layoutId(): Int = R.layout.radar_fragment - - override fun renderError(errorCode: Int) { + private fun renderError(errorCode: Int) { snack(errorCode) Timber.e("Rendering error code: ${getString(errorCode)}") - onFailureHandledSubject.onNext( - RadarView - .Event - .OnFailureHandled - ) + viewModel.send(RadarView.Event.OnFailureHandled) } override fun onCreate(savedInstanceState: Bundle?) { @@ -208,6 +134,12 @@ class RadarFragment : BaseFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + + lifecycleScope + .launch { + viewModel.viewState + .collect(::render) + } setupRadarMapView() setupEvents() } @@ -229,14 +161,6 @@ class RadarFragment : BaseFragment() { override fun onDestroyView() { super.onDestroyView() radarMapView.onDetach() - disposeSeekBar() - subscriptions.clear() - } - - private fun disposeSeekBar() { - if (::disposable.isInitialized) { - disposable.dispose() - } } private fun setupRadarMapView() { @@ -267,78 +191,49 @@ class RadarFragment : BaseFragment() { } private fun setupEvents() { - if (subscriptions.size() == 0) { - - Observable.mergeArray( - onViewInitialisedSubject - .toObservable(), - onStateParcelUpdatedSubject - .toObservable(), - onSeekBarStoppedSubject - .toObservable(), - onRadarImageDisplayedSubject - .toObservable(), - onSeekBarRestoredSubject - .toObservable(), - onFailureHandledSubject - .toObservable(), - onSeekBarResetSubject - .toObservable(), - onPositionUpdatedSubject - .toObservable(), - onPlayButtonStartedSubject - .toObservable(), - onPlayButtonStoppedSubject - .toObservable(), - radarSeekBar - .userChanges() - .throttleFirst(RADAR_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS) - .map { position: Int -> - RadarView - .Event - .OnPositionUpdated( - position - ) - }, - radarSeekBar - .changes() - .skipInitialValue() - .map { position: Int -> - RadarView - .Event - .OnRadarImageDisplayed(position) - }, - radarPlayFab - .clicks() - .map { - RadarView - .Event - .OnPlayButtonClicked - } - - ).observeOn( - scheduler.io() - ).compose( - viewModel() - ).observeOn( - scheduler.ui() - ).subscribe( - ::render - ).addTo( - subscriptions - ) - - onViewInitialisedSubject - .onNext( + // TODO this would allow ddos like behavior since + // it does not throttle + radarSeekBar + .userChanges() + .map { position: Int -> + viewModel.send( RadarView .Event - .OnViewInitialised( - stateParcel = bundle?.getParcelable( - STATE_PARCEL_KEY - ) + .OnPositionUpdated( + position ) ) - } + }.launchIn(lifecycleScope) + radarSeekBar + .changes() + .drop(1) + .map { position: Int -> + viewModel.send( + RadarView + .Event + .OnRadarImageDisplayed(position) + ) + }.launchIn(lifecycleScope) + + radarPlayFab + .clicks() + .map { + viewModel.send( + RadarView + .Event + .OnPlayButtonClicked + ) + }.launchIn(lifecycleScope) + + viewModel.send( + RadarView + .Event + .OnViewInitialised( + stateParcel = bundle?.getParcelable( + STATE_PARCEL_KEY + ) + ) + ) } private fun updateStateParcel(state: RadarView.State) { @@ -350,8 +245,8 @@ class RadarFragment : BaseFragment() { seekBarMax = state.seekBarMax ) - onStateParcelUpdatedSubject - .onNext( + viewModel + .send( RadarView .Event .OnStateParcelUpdated @@ -364,8 +259,8 @@ class RadarFragment : BaseFragment() { RadarView.RenderEvent.UpdateStateParcel -> updateStateParcel(viewState) RadarView.RenderEvent.SetPlayButtonToPlaying -> setPlayButtonToPlaying() RadarView.RenderEvent.SetPlayButtonToStopped -> setPlayButtonToStopped() - RadarView.RenderEvent.StartSeekBar -> startSeekBar(viewState) - RadarView.RenderEvent.StopSeekBar -> stopSeekBar() + RadarView.RenderEvent.StartSeekBar -> Unit // startSeekBar(viewState) + RadarView.RenderEvent.StopSeekBar -> Unit // stopSeekBar() is RadarView.RenderEvent.DisplayError -> renderError(viewState.renderEvent.errorCode) is RadarView.RenderEvent.DisplayRadarImage -> displayRadarImage(viewState.renderEvent.file) RadarView.RenderEvent.ResetSeekBar -> resetSeekBarPosition(viewState) @@ -375,7 +270,7 @@ class RadarFragment : BaseFragment() { private fun restoreSeekBarPosition(viewState: RadarView.State) { setRadarSeekBarPosition(viewState) - onSeekBarRestoredSubject.onNext( + viewModel.send( RadarView .Event .OnSeekBarRestored @@ -385,7 +280,7 @@ class RadarFragment : BaseFragment() { private fun resetSeekBarPosition(viewState: RadarView.State) { setRadarSeekBarPosition(viewState) Timber.d("resetSeekBarPosition") - onSeekBarResetSubject.onNext( + viewModel.send( RadarView .Event .OnSeekBarReset @@ -402,7 +297,7 @@ class RadarFragment : BaseFragment() { private fun setPlayButtonToPlaying() { radarPlayFab.setImageResource(R.drawable.ic_pause_white_24dp) Timber.d("setPlayButtonToPlaying") - onPlayButtonStartedSubject.onNext( + viewModel.send( RadarView .Event .OnPlayButtonStarted @@ -412,34 +307,13 @@ class RadarFragment : BaseFragment() { private fun setPlayButtonToStopped() { radarPlayFab.setImageResource(R.drawable.ic_play_arrow_white_24dp) Timber.d("setPlayButtonToStopped") - onPlayButtonStoppedSubject.onNext( + viewModel.send( RadarView .Event .OnPlayButtonStopped ) } - private fun startSeekBar(state: RadarView.State) { - disposable = Observable.interval( - RADAR_DEBOUNCE_MILLIS, - TimeUnit.MILLISECONDS - ).map { _ -> - radarSeekBar?.run { - progress += state.seekStep - max = state.seekBarMax - } - }.subscribe() - } - - private fun stopSeekBar() { - disposeSeekBar() - onSeekBarStoppedSubject.onNext( - RadarView - .Event - .OnSeekBarStopped - ) - } - private fun displayRadarImage(file: RadarFile) { ImageRequest .Builder(requireContext()) diff --git a/app/src/main/java/fi/kroon/vadret/presentation/radar/RadarViewModel.kt b/app/src/main/java/fi/kroon/vadret/presentation/radar/RadarViewModel.kt index 0e24c3bc..62fbb10c 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/radar/RadarViewModel.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/radar/RadarViewModel.kt @@ -1,101 +1,86 @@ package fi.kroon.vadret.presentation.radar +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import fi.kroon.vadret.data.failure.Failure import fi.kroon.vadret.domain.radar.GetRadarImageUrlService import fi.kroon.vadret.domain.radar.GetRadarLastCheckedKeyValueTask import fi.kroon.vadret.domain.radar.SetRadarLastCheckedKeyValueTask -import fi.kroon.vadret.presentation.radar.di.RadarScope import fi.kroon.vadret.presentation.shared.IViewModel import fi.kroon.vadret.util.NIL_INT import fi.kroon.vadret.util.extension.asObservable -import io.github.sphrak.either.Either -import io.reactivex.Observable -import io.reactivex.ObservableTransformer +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.rx2.await import timber.log.Timber import javax.inject.Inject -@RadarScope +@ExperimentalCoroutinesApi class RadarViewModel @Inject constructor( - private var state: RadarView.State, + private var stateModel: RadarView.State, + private val state: MutableSharedFlow, private val getRadarImageUrlService: GetRadarImageUrlService, private val getRadarLastCheckedKeyValueTask: GetRadarLastCheckedKeyValueTask, private val setRadarLastCheckedKeyValueTask: SetRadarLastCheckedKeyValueTask -) : IViewModel { +) : ViewModel(), IViewModel { - operator fun invoke(): ObservableTransformer = onEvent + val viewState: SharedFlow get() = state.asSharedFlow() - private val onEvent = ObservableTransformer { upstream: Observable -> - upstream.publish { shared: Observable -> - Observable.mergeArray( - shared.ofType(RadarView.Event.OnViewInitialised::class.java), - shared.ofType(RadarView.Event.OnFailureHandled::class.java), - shared.ofType(RadarView.Event.OnRadarImageDisplayed::class.java), - shared.ofType(RadarView.Event.OnStateParcelUpdated::class.java), - shared.ofType(RadarView.Event.OnSeekBarRestored::class.java), - shared.ofType(RadarView.Event.OnSeekBarStopped::class.java), - shared.ofType(RadarView.Event.OnPlayButtonStarted::class.java), - shared.ofType(RadarView.Event.OnPlayButtonStopped::class.java), - shared.ofType(RadarView.Event.OnSeekBarReset::class.java), - shared.ofType(RadarView.Event.OnPlayButtonClicked::class.java), - shared.ofType(RadarView.Event.OnPositionUpdated::class.java) - ).compose( - eventToViewState - ) - } + fun send(event: RadarView.Event) { + viewModelScope.launch { reduce(event = event) } } - private val eventToViewState = ObservableTransformer { upstream: Observable -> - upstream.flatMap { event: RadarView.Event -> - when (event) { - is RadarView.Event.OnViewInitialised -> onViewInitialisedEvent(event) - is RadarView.Event.OnPositionUpdated -> onPositionSeekedEvent(event.position) - is RadarView.Event.OnRadarImageDisplayed -> onRadarImageDisplayed(event.position) - RadarView.Event.OnStateParcelUpdated -> onStateParcelUpdated() - RadarView.Event.OnFailureHandled -> onFailureHandledEvent() - RadarView.Event.OnPlayButtonClicked -> onPlayButtonClickedEvent() - RadarView.Event.OnPlayButtonStarted -> onPlayButtonStartedEvent() - RadarView.Event.OnPlayButtonStopped -> onPlayButtonStoppedEvent() - RadarView.Event.OnSeekBarStopped -> onSeekBarStopped() - RadarView.Event.OnSeekBarReset -> onSeekBarReset() - RadarView.Event.OnSeekBarRestored -> onSeekBarRestored() - } - } + private suspend fun reduce(event: RadarView.Event) = when (event) { + is RadarView.Event.OnViewInitialised -> onViewInitialisedEvent(event) + is RadarView.Event.OnPositionUpdated -> onPositionSeekedEvent(event.position) + is RadarView.Event.OnRadarImageDisplayed -> onRadarImageDisplayed(event.position) + RadarView.Event.OnStateParcelUpdated -> onStateParcelUpdated() + RadarView.Event.OnFailureHandled -> onFailureHandledEvent() + RadarView.Event.OnPlayButtonClicked -> onPlayButtonClickedEvent() + RadarView.Event.OnPlayButtonStarted -> onPlayButtonStartedEvent() + RadarView.Event.OnPlayButtonStopped -> onPlayButtonStoppedEvent() + RadarView.Event.OnSeekBarStopped -> onSeekBarStopped() + RadarView.Event.OnSeekBarReset -> onSeekBarReset() + RadarView.Event.OnSeekBarRestored -> onSeekBarRestored() } - private fun onViewInitialisedEvent(event: RadarView.Event.OnViewInitialised): Observable { + private suspend fun onViewInitialisedEvent(event: RadarView.Event.OnViewInitialised) { restoreStateFromStateParcel(event.stateParcel) - state = state.copy(isInitialised = true, renderEvent = RadarView.RenderEvent.None) - return restoreSeekBarIfNeeded() + stateModel = stateModel.copy(isInitialised = true, renderEvent = RadarView.RenderEvent.None) + state.emit(stateModel) } - private fun onSeekBarRestored(): Observable { + private suspend fun onSeekBarRestored() { Timber.d("ON SEEKBAR RESTORED") - return loadRadarImageUrl() + loadRadarImageUrl() } - private fun restoreSeekBarIfNeeded(): Observable = - if (state.wasRestoredFromStateParcel) { - state = state.copy( + private suspend fun restoreSeekBarIfNeeded() = + if (stateModel.wasRestoredFromStateParcel) { + stateModel = stateModel.copy( renderEvent = RadarView.RenderEvent.RestoreSeekBarPosition, wasRestoredFromStateParcel = false ) - state.asObservable() + state.emit(stateModel) } else { loadRadarImageUrl() } - private fun onSeekBarStopped(): Observable { + private suspend fun onSeekBarStopped() { Timber.d("ON SEEKBAR STOPPED") - state = state.copy( + stateModel = stateModel.copy( renderEvent = RadarView.RenderEvent.None ) - return state.asObservable() + state.emit(stateModel) } private fun restoreStateFromStateParcel(stateParcel: RadarView.StateParcel?) { Timber.d("RESTORE STATE FROM STATE PARCEL: $stateParcel") stateParcel?.run { - state = state.copy( + stateModel = stateModel.copy( isInitialised = isInitialised, isSeekBarRunning = false, wasRestoredFromStateParcel = true, @@ -105,125 +90,131 @@ class RadarViewModel @Inject constructor( } } - private fun onPlayButtonClickedEvent(): Observable = + private suspend fun onPlayButtonClickedEvent() { handlePlayButtonClicks() + } - private fun handlePlayButtonClicks(): Observable = + private suspend fun handlePlayButtonClicks() { when { - state.isSeekBarRunning -> { - state = state.copy(renderEvent = RadarView.RenderEvent.SetPlayButtonToStopped) - state + stateModel.isSeekBarRunning -> { + stateModel = + stateModel.copy(renderEvent = RadarView.RenderEvent.SetPlayButtonToStopped) + state.emit(stateModel) } - state.isSeekBarRunning.not() -> { - state = state.copy(renderEvent = RadarView.RenderEvent.SetPlayButtonToPlaying) - state + stateModel.isSeekBarRunning.not() -> { + stateModel = + stateModel.copy(renderEvent = RadarView.RenderEvent.SetPlayButtonToPlaying) + state.emit(stateModel) } else -> { - state = state.copy(isSeekBarRunning = false) - state + stateModel = stateModel.copy(isSeekBarRunning = false) + state.emit(stateModel) } - }.asObservable() + } + } - private fun onPlayButtonStartedEvent(): Observable { + private suspend fun onPlayButtonStartedEvent() { Timber.d("ON PLAY BUTTON STARTED EVENT") - state = state.copy( + stateModel = stateModel.copy( isSeekBarRunning = true, renderEvent = RadarView.RenderEvent.StartSeekBar ) - return state.asObservable() + state.emit(stateModel) } - private fun onPlayButtonStoppedEvent(): Observable { + private suspend fun onPlayButtonStoppedEvent() { Timber.d("ON PLAY BUTTON STOPPED EVENT") - state = state.copy( + stateModel = stateModel.copy( isSeekBarRunning = false, renderEvent = RadarView.RenderEvent.StopSeekBar ) - return state.asObservable() + state.emit(stateModel) } - private fun onPositionSeekedEvent(position: Int): Observable { - state = state.copy(currentSeekBarIndex = position) - return radarPlayerHandler() + private suspend fun onPositionSeekedEvent(position: Int) { + stateModel = stateModel.copy(currentSeekBarIndex = position) + radarPlayerHandler() } - private fun onSeekBarReset(): Observable = + private suspend fun onSeekBarReset() { radarPlayerHandler() + } - private fun radarPlayerHandler(): Observable = + private suspend fun radarPlayerHandler() { when { - state.isSeekBarRunning -> { + stateModel.isSeekBarRunning -> { loadRadarImageUrl() } else -> { - state = state.copy( + stateModel = stateModel.copy( renderEvent = RadarView.RenderEvent.None ) - state.asObservable() + state.emit(stateModel) } } + } - private fun loadRadarImageUrl(): Observable = + private suspend fun loadRadarImageUrl() { getRadarLastCheckedKeyValueTask() - .flatMapObservable { result: Either -> - result.either( - { failure: Failure -> - Timber.e("failure: $failure") - state = state.copy(renderEvent = RadarView.RenderEvent.None) - state.asObservable() - }, - { timeStamp: Long -> - getRadarImageUrlService(timeStamp, state.currentSeekBarIndex) - .flatMapObservable { result: Either -> - result.either( - { failure: Failure -> - Timber.e("LOAD RADAR IMAGE URL: $failure") - val errorCode: Int = getErrorCode(failure) - val renderEvent: RadarView.RenderEvent.DisplayError = RadarView.RenderEvent.DisplayError(errorCode) - - state = state.copy(renderEvent = renderEvent) - state.asObservable() - }, - { data: GetRadarImageUrlService.Data -> - val renderEvent: RadarView.RenderEvent.DisplayRadarImage = RadarView.RenderEvent.DisplayRadarImage(data.file!!) - state = state.copy( - renderEvent = renderEvent, - seekBarMax = data.maxIndex!!, - currentSeekBarIndex = state.currentSeekBarIndex - ) - setLastCheckedTimeStamp(timeStamp = timeStamp) - } + .await() + .either( + { failure: Failure -> + Timber.e("failure: $failure") + stateModel = stateModel.copy(renderEvent = RadarView.RenderEvent.None) + stateModel.asObservable() + }, + { timeStamp: Long -> + getRadarImageUrlService(timeStamp, stateModel.currentSeekBarIndex) + .await() + .either( + { failure: Failure -> + Timber.e("LOAD RADAR IMAGE URL: $failure") + val errorCode: Int = getErrorCode(failure) + val renderEvent: RadarView.RenderEvent.DisplayError = + RadarView.RenderEvent.DisplayError(errorCode) + stateModel = stateModel.copy(renderEvent = renderEvent) + state.emit(stateModel) + }, + { data: GetRadarImageUrlService.Data -> + val renderEvent: RadarView.RenderEvent.DisplayRadarImage = + RadarView.RenderEvent.DisplayRadarImage(data.file!!) + stateModel = stateModel.copy( + renderEvent = renderEvent, + seekBarMax = data.maxIndex!!, + currentSeekBarIndex = stateModel.currentSeekBarIndex ) + setLastCheckedTimeStamp(timeStamp = timeStamp) } - } - ) - } + ) + } + ) + } - private fun setLastCheckedTimeStamp(timeStamp: Long): Observable = + private suspend fun setLastCheckedTimeStamp(timeStamp: Long) { setRadarLastCheckedKeyValueTask(value = timeStamp) - .flatMapObservable { result: Either -> - result.either( - { failure: Failure -> - Timber.e("failure: $failure") - state = state.copy(renderEvent = RadarView.RenderEvent.None) - state.asObservable() - }, - { - Timber.d("LAST CHECKED UPDATED: $timeStamp") - state.asObservable() - } - ) - } + .await() + .either( + { failure: Failure -> + Timber.e("failure: $failure") + stateModel = stateModel.copy(renderEvent = RadarView.RenderEvent.None) + state.emit(stateModel) + }, + { + Timber.d("LAST CHECKED UPDATED: $timeStamp") + state.emit(stateModel) + } + ) + } - private fun onStateParcelUpdated(): Observable { + private suspend fun onStateParcelUpdated() { Timber.d("ON STATE PARCEL UPDATED") - return radarPlayerHandler() + radarPlayerHandler() } - private fun onRadarImageDisplayed(newPosition: Int): Observable { + private suspend fun onRadarImageDisplayed(newPosition: Int) { Timber.d("ON RADAR IMAGE DISPLAYED") val currentSeekBarIndex: Int = when { - (newPosition < state.seekBarMax) -> { + (newPosition < stateModel.seekBarMax) -> { newPosition } else -> { @@ -232,8 +223,8 @@ class RadarViewModel @Inject constructor( } val renderEvent: RadarView.RenderEvent = when { - (newPosition == state.seekBarMax && NIL_INT < state.seekBarMax) -> { - state = state.copy(currentSeekBarIndex = NIL_INT) + (newPosition == stateModel.seekBarMax && NIL_INT < stateModel.seekBarMax) -> { + stateModel = stateModel.copy(currentSeekBarIndex = NIL_INT) RadarView.RenderEvent.ResetSeekBar } else -> { @@ -241,21 +232,21 @@ class RadarViewModel @Inject constructor( } } - state = state.copy( + stateModel = stateModel.copy( renderEvent = renderEvent, currentSeekBarIndex = currentSeekBarIndex ) - return state.asObservable() + state.emit(stateModel) } - private fun onFailureHandledEvent(): Observable { - state = state.copy( + private suspend fun onFailureHandledEvent() { + stateModel = stateModel.copy( renderEvent = RadarView.RenderEvent.SetPlayButtonToStopped, currentSeekBarIndex = NIL_INT, seekBarMax = NIL_INT, isSeekBarRunning = false ) - return state.asObservable() + state.emit(stateModel) } } \ No newline at end of file diff --git a/app/src/main/java/fi/kroon/vadret/presentation/radar/di/RadarComponent.kt b/app/src/main/java/fi/kroon/vadret/presentation/radar/di/RadarComponent.kt index d0cb99f2..6df24d6d 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/radar/di/RadarComponent.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/radar/di/RadarComponent.kt @@ -5,12 +5,10 @@ import coil.ImageLoader import dagger.BindsInstance import dagger.Component import fi.kroon.vadret.core.CoreComponent -import fi.kroon.vadret.presentation.radar.RadarView import fi.kroon.vadret.presentation.radar.RadarViewModel -import fi.kroon.vadret.util.Scheduler -import io.reactivex.disposables.CompositeDisposable -import io.reactivex.subjects.PublishSubject +import kotlinx.coroutines.ExperimentalCoroutinesApi +@ExperimentalCoroutinesApi @Component( modules = [ RadarModule::class @@ -22,27 +20,8 @@ import io.reactivex.subjects.PublishSubject @RadarScope interface RadarComponent { - /** - * ViewModel - */ fun provideRadarViewModel(): RadarViewModel - - /** - * PublishSubject - */ - fun provideOnViewInitialised(): PublishSubject - fun provideOnFailureHandled(): PublishSubject - fun provideOnRadarImageDisplayed(): PublishSubject - fun provideOnSeekBarStopped(): PublishSubject - fun provideOnStateParcelUpdated(): PublishSubject - fun provideOnPlayButtonStarted(): PublishSubject - fun provideOnPlayButtonStopped(): PublishSubject - fun provideOnSeekBarReset(): PublishSubject - fun provideOnPositionUpdated(): PublishSubject - fun provideOnSeekBarRestored(): PublishSubject - fun provideCompositeDisposable(): CompositeDisposable fun provideImageLoader(): ImageLoader - fun provideScheduler(): Scheduler @Component.Factory interface Factory { diff --git a/app/src/main/java/fi/kroon/vadret/presentation/radar/di/RadarModule.kt b/app/src/main/java/fi/kroon/vadret/presentation/radar/di/RadarModule.kt index 98fd684a..283e4442 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/radar/di/RadarModule.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/radar/di/RadarModule.kt @@ -18,68 +18,24 @@ import fi.kroon.vadret.util.common.IDateTimeUtil import fi.kroon.vadret.util.extension.assertNoInitMainThread import fi.kroon.vadret.util.extension.delegatingCallFactory import io.reactivex.disposables.CompositeDisposable -import io.reactivex.subjects.PublishSubject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableSharedFlow import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.converter.moshi.MoshiConverterFactory @Module +@ExperimentalCoroutinesApi object RadarModule { @Provides @RadarScope - fun provideOnViewInitialisedSubject(): PublishSubject = - PublishSubject.create() - - @Provides - @RadarScope - fun provideOnPlayButtonStartedSubject(): PublishSubject = - PublishSubject.create() - - @Provides - @RadarScope - fun provideOnPlayButtonStoppedSubject(): PublishSubject = - PublishSubject.create() - - @Provides - @RadarScope - fun provideOnFailureHandledSubject(): PublishSubject = - PublishSubject.create() - - @Provides - @RadarScope - fun provideOnRadarImageDisplayedSubject(): PublishSubject = - PublishSubject.create() - - @Provides - @RadarScope - fun provideOnSeekBarResetSubject(): PublishSubject = - PublishSubject.create() - - @Provides - @RadarScope - fun provideOnSeekBarStoppedSubject(): PublishSubject = - PublishSubject.create() - - @Provides - @RadarScope - fun provideOnStateParcelUpdatedSubject(): PublishSubject = - PublishSubject.create() - - @Provides - @RadarScope - fun provideOnPositionUpdatedSubject(): PublishSubject = - PublishSubject.create() - - @Provides - @RadarScope - fun provideOnSeekBarRestoredSubject(): PublishSubject = - PublishSubject.create() + fun provideViewState(): RadarView.State = RadarView.State() @Provides @RadarScope - fun provideViewState(): RadarView.State = RadarView.State() + fun provideSharedFlowState(): MutableSharedFlow = MutableSharedFlow() @Provides @RadarScope