Last active
April 5, 2017 19:36
-
-
Save Jeevuz/ae0d071b4f44566793d4c5eaeb530a9c to your computer and use it in GitHub Desktop.
Real project's part converted from MVP (with Moxy) into RxPM (with Outlast). See revisions diff.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This is real project screen with complex UI that was switched | |
from using the MVP with Moxy library | |
to the use of RxPM pattern (Reactive Presentation Model) with Outlast library (persistent PM layer). | |
I was doing it to see pros and cons of the RxPM pattern. | |
Pros: | |
- easy integration with RxBindings for complex UI. | |
- nice saved states in PM (for PM and MVVM lovers). | |
- easy combining of reactive streams coming from network, db, etc. in PM. | |
- declarative logic. | |
Cons: | |
- need of wrappers to pass many params in stream at once. | |
- need of boilerplate getters for use states and consumers outside of PM. | |
- it is possible to forget to bind some state in the view. No help from compilator or IDE as with the View interface. | |
- harder to quick switch to project because of more rx. Especially for middle or junior. | |
- need to remember the contract of bindPresentationModel and bindTo (see javadocs, they subscribe/unsubscribe in resume/pause). | |
As result I can say that the RxPM+Outlast is good combination | |
easy competing with MVP+Moxy and differences is largely a matter of taste. | |
RxPM will do good in full-of-rx projects, while MVP is better for no-rx projects or teams with different level of knowledge. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.ui.common.pm | |
import android.os.Bundle | |
import android.support.annotation.LayoutRes | |
import android.support.v4.app.Fragment | |
import android.view.LayoutInflater | |
import android.view.ViewGroup | |
import com.example.util.inflate | |
import io.reactivex.Observable | |
import io.reactivex.android.schedulers.AndroidSchedulers | |
import io.reactivex.disposables.CompositeDisposable | |
import io.reactivex.functions.Consumer | |
import me.jeevuz.outlast.Outlasting | |
import me.jeevuz.outlast.predefined.FragmentOutlast | |
import timber.log.Timber | |
/** | |
* Helps to handle subscriptions and binding. | |
* @author Vasili Chyrvon ([email protected]) | |
*/ | |
abstract class BaseFragment<PM : PresentationModel> : Fragment() { | |
// To let PM outlast the configuration changes | |
private lateinit var outlast: FragmentOutlast<PM> | |
// To unsubscribe when needed | |
private val composite = CompositeDisposable() | |
/** | |
* Provide layout to use in [onCreateView] | |
*/ | |
@get:LayoutRes | |
protected abstract val fragmentLayout: Int | |
/** | |
* Returns stored Presentation Model for this fragment. | |
* Call it only after onCreate(). | |
* @return Presentation Model | |
*/ | |
protected val presentationModel: PM = outlast.outlasting | |
/** | |
* Provide presentation model to use with this fragment. | |
*/ | |
protected abstract fun providePresentationModel(): PM | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
// Create the outlast delegate | |
outlast = FragmentOutlast<PM>( | |
this, | |
Outlasting.Creator { providePresentationModel() }, | |
savedInstanceState) | |
} | |
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) = | |
container?.inflate(fragmentLayout) | |
override fun onActivityCreated(savedInstanceState: Bundle?) { | |
super.onActivityCreated(savedInstanceState) | |
initViews() | |
} | |
override fun onStart() { | |
super.onStart() | |
outlast.onStart() // Delegated callback | |
} | |
override fun onResume() { | |
super.onResume() | |
outlast.onResume() // Delegated callback | |
bindPresentationModel() | |
} | |
override fun onSaveInstanceState(outState: Bundle?) { | |
super.onSaveInstanceState(outState) | |
outlast.onSaveInstanceState(outState) // Delegated callback | |
} | |
override fun onDestroy() { | |
super.onDestroy() | |
outlast.onDestroy() // Delegated callback | |
} | |
/** | |
* Bind to the Presentation Model in that method. | |
* Called from [onResume]. | |
* Use convenient extension [bindTo] (all subscriptions done using it will be cleared in [onPause]). | |
*/ | |
protected abstract fun bindPresentationModel() | |
/** | |
* Method for base views initialization. | |
* Called from [onActivityCreated]. | |
*/ | |
protected open fun initViews() { | |
// No-op | |
} | |
override fun onPause() { | |
super.onPause() | |
composite.clear() | |
} | |
/** | |
* Local extension to | |
* subscribe to the source observable with observeOn set to main thread | |
* and adds it to the subscriptions list that will be CLEARED [ON PAUSE][onPause], so use it ONLY in [bindPresentationModel]. | |
*/ | |
protected fun <T> Observable<T>.bindTo(consumer: Consumer<in T>, onError: Consumer<in Throwable> = Consumer { Timber.e(it) }) { | |
composite.add( | |
this | |
.observeOn(AndroidSchedulers.mainThread()) | |
.subscribe(consumer, onError) | |
) | |
} | |
/** | |
* Local extension to | |
* subscribe to the source observable with observeOn set to main thread | |
* and adds it to the subscriptions list that will be CLEARED [ON PAUSE][onPause], so use it ONLY in [bindPresentationModel]. | |
*/ | |
protected fun <T> Observable<T>.bindTo(consumer: (T) -> Unit, onError: (Throwable) -> Unit) { | |
composite.add( | |
this | |
.observeOn(AndroidSchedulers.mainThread()) | |
.subscribe(consumer, onError) | |
) | |
} | |
/** | |
* Local extension to | |
* subscribe to the source observable with observeOn set to main thread | |
* and adds it to the subscriptions list that will be CLEARED [ON PAUSE][onPause], so use it ONLY in [bindPresentationModel]. | |
*/ | |
protected fun <T> Observable<T>.bindTo(consumer: (T) -> Unit) { | |
bindTo(consumer, Timber::e) | |
} | |
/** | |
* Local extension to | |
* pass the value to the [Consumer]. | |
*/ | |
protected fun passTo(consumer: Consumer<Unit>) { | |
consumer.accept(Unit) | |
} | |
/** | |
* Local extension to | |
* pass the value to the [Consumer]. | |
*/ | |
protected fun <T> T.passTo(consumer: Consumer<T>) { | |
consumer.accept(this) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.ui.common.pm | |
import com.jakewharton.rxrelay2.PublishRelay | |
import io.reactivex.functions.Consumer | |
/** | |
* Base presentation model with common properties like backPresses | |
*/ | |
abstract class BasePresentationModel : PresentationModel() { | |
// Common actions | |
protected val backPresses = PublishRelay.create<Unit>() | |
val backPressesConsumer: Consumer<Unit> = backPresses | |
override fun onCreate() { | |
initialValues() | |
setUp() | |
} | |
/** | |
* Set initial values of states and actions streams here | |
*/ | |
protected open fun initialValues() { | |
// No-op | |
} | |
/** | |
* All initialization of streams and other set up goes here | |
*/ | |
abstract fun setUp() | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.ui.common.pm | |
import me.jeevuz.outlast.Outlasting | |
/** | |
* Presentation model that can outlast the configuration changes. | |
* @author Vasili Chyrvon ([email protected]) | |
*/ | |
abstract class PresentationModel : Outlasting { | |
/** | |
* All initialization goes here. | |
*/ | |
override abstract fun onCreate() | |
/** | |
* Use for the unsubscribing and clearings. | |
*/ | |
override fun onDestroy() { | |
// No-op | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.ui.settings | |
import android.view.View | |
import android.widget.CompoundButton | |
import android.widget.TextView | |
import com.bayer.aerius.ui.settings.SettingsPresentationModel | |
import com.bayer.aerius.ui.settings.Sound | |
import com.example.R | |
import com.example.entity.Day | |
import com.example.entity.RemindersRepeating | |
import com.example.ui.common.BackPressHandler | |
import com.example.ui.common.SingleChoiceDialogFragment | |
import com.example.ui.common.pm.BaseFragment | |
import com.codetroopers.betterpickers.radialtimepicker.RadialTimePickerDialogFragment | |
import com.jakewharton.rxbinding2.view.clicks | |
import com.jakewharton.rxbinding2.widget.checked | |
import com.jakewharton.rxbinding2.widget.checkedChanges | |
import com.jakewharton.rxbinding2.widget.text | |
import io.reactivex.functions.BiFunction | |
import kotlinx.android.synthetic.main.fragment_settings.* | |
/** | |
* @author Vasili Chyrvon ([email protected]) | |
*/ | |
class SettingsFragment : BaseFragment<SettingsPresentationModel>(), SingleChoiceDialogFragment.ResponseListener, BackPressHandler, RadialTimePickerDialogFragment.OnTimeSetListener { | |
companion object { | |
private val DIALOG_REPEATING = "dialog_repeating" | |
private val DIALOG_REMINDERS_START_TIME = "dialog_reminders_start_time" | |
private val DIALOG_REMINDERS_STOP_TIME = "dialog_reminders_stop_time" | |
private val DIALOG_SILENCE_START_TIME = "dialog_silence_start_time" | |
private val DIALOG_SILENCE_STOP_TIME = "dialog_silence_stop_time" | |
private val DIALOG_SOUNDS = "dialog_sounds" | |
} | |
override val fragmentLayout = R.layout.fragment_settings | |
private lateinit var sounds: List<Sound> | |
override fun providePresentationModel() = SettingsPresentationModel() | |
override fun initViews() { | |
setDaysOnCheckedChangeListener() | |
} | |
override fun bindPresentationModel() { | |
// Local function to be more compact | |
fun <T, D> returnData() = BiFunction<T, D, D> | |
{ _, data -> data } | |
presentationModel.remindersEnabledState | |
.doOnNext(this::setRemindersBlockEnabled) | |
.bindTo(remindersSwitch.checked()) | |
presentationModel.onlyUseStartTimeState | |
.bindTo { onlyStartTime -> | |
if (onlyStartTime) { | |
stopTimeLabel.visibility = View.INVISIBLE | |
stopTime.visibility = View.INVISIBLE | |
startTimeLabel.setText(R.string.settings_reminder_at) | |
} else { | |
stopTimeLabel.visibility = View.VISIBLE | |
stopTime.visibility = View.VISIBLE | |
startTimeLabel.setText(R.string.settings_reminder_from) | |
} | |
} | |
presentationModel.repeatingsState | |
.map { it.items[it.selectedItem].text } | |
.bindTo(repeatText.text()) | |
repeatText.clicks() | |
.withLatestFrom( | |
presentationModel.repeatingsState, | |
returnData() | |
) | |
.bindTo { | |
dropDown -> | |
showChoiceDialog(dropDown.items.map { it.text }.toTypedArray(), dropDown.selectedItem, DIALOG_REPEATING) | |
} | |
startTime.clicks() | |
.withLatestFrom( | |
presentationModel.remindersStartTimeState, | |
returnData() | |
) | |
.bindTo { showTimePicker(it, DIALOG_REMINDERS_START_TIME) } | |
presentationModel.remindersStartTimeState | |
.bindTo(startTime::setTime) | |
stopTime.clicks() | |
.withLatestFrom( | |
presentationModel.remindersStopTimeState, | |
returnData() | |
) | |
.bindTo { showTimePicker(it, DIALOG_REMINDERS_STOP_TIME) } | |
presentationModel.remindersStopTimeState | |
.bindTo(stopTime::setTime) | |
presentationModel.selectedDaysState | |
.bindTo { selectedDays -> | |
monday.isChecked = selectedDays.contains(Day.MON) | |
tuesday.isChecked = selectedDays.contains(Day.TUE) | |
wednesday.isChecked = selectedDays.contains(Day.WED) | |
thursday.isChecked = selectedDays.contains(Day.THU) | |
friday.isChecked = selectedDays.contains(Day.FRI) | |
saturday.isChecked = selectedDays.contains(Day.SAT) | |
sunday.isChecked = selectedDays.contains(Day.SUN) | |
} | |
presentationModel.silenceEnabledState | |
.doOnNext(this::setSilenceBlockEnabled) | |
.bindTo(silenceSwitch.checked()) | |
silenceStartTime.clicks() | |
.withLatestFrom( | |
presentationModel.silenceStartTimeState, | |
returnData() | |
) | |
.bindTo { showTimePicker(it, DIALOG_SILENCE_START_TIME) } | |
presentationModel.silenceStartTimeState | |
.bindTo(silenceStartTime::setTime) | |
silenceStopTime.clicks() | |
.withLatestFrom( | |
presentationModel.silenceStopTimeState, | |
returnData() | |
) | |
.bindTo { showTimePicker(it, DIALOG_SILENCE_STOP_TIME) } | |
presentationModel.silenceStopTimeState | |
.bindTo(silenceStopTime::setTime) | |
presentationModel.soundsState | |
.doOnNext { | |
this.sounds = it.items | |
} | |
.map { it.items[it.selectedItem].name } | |
.bindTo(soundText.text()) | |
soundText.clicks() | |
.withLatestFrom( | |
presentationModel.soundsState, | |
returnData() | |
) | |
.bindTo { | |
dropDown -> | |
showChoiceDialog(dropDown.items.map { it.name }.toTypedArray(), dropDown.selectedItem, DIALOG_SOUNDS, true) | |
} | |
backButton.clicks().bindTo(presentationModel.backPressesConsumer) | |
remindersSwitch.checkedChanges().bindTo(presentationModel.reminderSwitchChangesConsumer) | |
silenceSwitch.checkedChanges().bindTo(presentationModel.silenceSwitchChangesConsumer) | |
} | |
override fun onPause() { | |
super.onPause() | |
passTo(presentationModel.inactivitySignalsConsumer) | |
} | |
private fun setRemindersBlockEnabled(enabled: Boolean) { | |
repeatText.isEnabled = enabled | |
startTimeLabel.isEnabled = enabled | |
startTime.isEnabled = enabled | |
stopTimeLabel.isEnabled = enabled | |
stopTime.isEnabled = enabled | |
monday.isEnabled = enabled | |
tuesday.isEnabled = enabled | |
wednesday.isEnabled = enabled | |
thursday.isEnabled = enabled | |
friday.isEnabled = enabled | |
saturday.isEnabled = enabled | |
sunday.isEnabled = enabled | |
} | |
private fun showChoiceDialog(items: Array<String>, checkedPosition: Int, dialogTag: String, withButtons: Boolean = false) { | |
if (childFragmentManager.findFragmentByTag(tag) == null) { | |
SingleChoiceDialogFragment.newInstance(items, checkedPosition, dialogTag, withButtons).show(childFragmentManager, tag) | |
} | |
} | |
override fun onSingleChoiceDialogResponse(tag: String, position: Int, done: Boolean) { | |
when (tag) { | |
DIALOG_REPEATING -> { | |
RemindersRepeating.values()[position].passTo(presentationModel.repeatingChangesConsumer) | |
} | |
DIALOG_SOUNDS -> { | |
if (done) { | |
sounds[position].uri.passTo(presentationModel.soundChangesConsumer) | |
} else { | |
sounds[position].uri.passTo(presentationModel.soundTestsConsumer) | |
} | |
} | |
} | |
} | |
private fun showTimePicker(hourMinute: Pair<Int, Int>, dialogTag: String) { | |
val timePicker = RadialTimePickerDialogFragment() | |
.setThemeCustom(R.style.TimePickersDialog) | |
.setForced24hFormat() | |
.setDoneText(getString(R.string.settings_time_dialog_ok)) | |
.setCancelText(getString(R.string.settings_time_dialog_cancel)) | |
.setOnTimeSetListener(this) | |
.setStartTime(hourMinute.first, hourMinute.second) | |
timePicker.show(childFragmentManager, dialogTag) | |
} | |
override fun onTimeSet(dialog: RadialTimePickerDialogFragment, hour: Int, minute: Int) { | |
when (dialog.tag) { | |
DIALOG_REMINDERS_START_TIME -> Pair(hour, minute).passTo(presentationModel.remindersStartTimeChangesConsumer) | |
DIALOG_REMINDERS_STOP_TIME -> Pair(hour, minute).passTo(presentationModel.remindersStopTimeChangesConsumer) | |
DIALOG_SILENCE_START_TIME -> Pair(hour, minute).passTo(presentationModel.silenceStartTimeChangesConsumer) | |
DIALOG_SILENCE_STOP_TIME -> Pair(hour, minute).passTo(presentationModel.silenceStopTimeChangesConsumer) | |
} | |
} | |
private fun setDaysOnCheckedChangeListener() { | |
val daysCheckedChangeListener = CompoundButton.OnCheckedChangeListener { compoundButton, isChecked -> | |
val daySelection = SettingsPresentationModel.DaySelection( | |
when (compoundButton.id) { | |
monday.id -> Day.MON | |
tuesday.id -> Day.TUE | |
wednesday.id -> Day.WED | |
thursday.id -> Day.THU | |
friday.id -> Day.FRI | |
saturday.id -> Day.SAT | |
sunday.id -> Day.SUN | |
else -> throw IllegalArgumentException("This is not a day of week") | |
}, | |
isChecked) | |
daySelection.passTo(presentationModel.daysSelectionChangesConsumer) | |
} | |
monday.setOnCheckedChangeListener(daysCheckedChangeListener) | |
tuesday.setOnCheckedChangeListener(daysCheckedChangeListener) | |
wednesday.setOnCheckedChangeListener(daysCheckedChangeListener) | |
thursday.setOnCheckedChangeListener(daysCheckedChangeListener) | |
friday.setOnCheckedChangeListener(daysCheckedChangeListener) | |
saturday.setOnCheckedChangeListener(daysCheckedChangeListener) | |
sunday.setOnCheckedChangeListener(daysCheckedChangeListener) | |
} | |
private fun setSilenceBlockEnabled(enabled: Boolean) { | |
silenceStartTimeLabel.isEnabled = enabled | |
silenceStartTime.isEnabled = enabled | |
silenceStopTimeLabel.isEnabled = enabled | |
silenceStopTime.isEnabled = enabled | |
} | |
override fun onBackPressed(): Boolean { | |
passTo(presentationModel.backPressesConsumer) | |
return true | |
} | |
} | |
/** | |
* Local extension to set time Pair<hour, minute> to TextView | |
*/ | |
private fun TextView.setTime(timePair: Pair<Int, Int>) { | |
text = context.getString(R.string.settings_time, timePair.first, timePair.second) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.ui.settings | |
import android.net.Uri | |
import com.arellomobile.mvp.InjectViewState | |
import com.bayer.aerius.ui.settings.Repeating | |
import com.bayer.aerius.ui.settings.Sound | |
import com.example.R | |
import com.example.TheApplication | |
import com.example.entity.Day | |
import com.example.entity.RemindersRepeating | |
import com.example.notifications.Notifier | |
import com.example.repository.SettingsRepository | |
import com.example.system.ResourceHelper | |
import com.example.system.SoundsHelper | |
import com.example.ui.common.pm.BasePresentationModel | |
import com.jakewharton.rxrelay2.BehaviorRelay | |
import com.jakewharton.rxrelay2.PublishRelay | |
import io.reactivex.Observable | |
import io.reactivex.functions.BiFunction | |
import io.reactivex.functions.Consumer | |
import ru.terrakok.cicerone.Router | |
import javax.inject.Inject | |
/** | |
* @author Vasili Chyrvon ([email protected]) | |
*/ | |
@InjectViewState | |
class SettingsPresentationModel : BasePresentationModel() { | |
@Inject lateinit var router: Router | |
@Inject lateinit var settingsRepo: SettingsRepository | |
@Inject lateinit var notifier: Notifier | |
@Inject lateinit var soundsHelper: SoundsHelper | |
@Inject lateinit var resourceHelper: ResourceHelper | |
init { | |
TheApplication.appComponent.inject(this) | |
} | |
private val soundsList by lazy { soundsHelper.getNotificationSounds().toList() } | |
private val repeatingTextsList by lazy { resourceHelper.getStringsList(R.array.settings_reminder_repeat) } | |
// Wrappers for use in streams | |
data class DropDown<out T>(val items: List<T>, val selectedItem: Int) | |
data class DaySelection(val day: Day, val selected: Boolean) | |
// States | |
private val remindersEnabled = BehaviorRelay.create<Boolean>() | |
private val repeatings = BehaviorRelay.create<DropDown<Repeating>>() | |
private val onlyUseStartTime = BehaviorRelay.create<Boolean>() | |
private val remindersStartTime = BehaviorRelay.create<Pair<Int, Int>>() | |
private val remindersStopTime = BehaviorRelay.create<Pair<Int, Int>>() | |
private val selectedDays = BehaviorRelay.create<Set<Day>>() | |
private val silenceEnabled = BehaviorRelay.create<Boolean>() | |
private val silenceStartTime = BehaviorRelay.create<Pair<Int, Int>>() | |
private val silenceStopTime = BehaviorRelay.create<Pair<Int, Int>>() | |
private val sounds = BehaviorRelay.create<DropDown<Sound>>() | |
// Actions | |
private val reminderSwitchChanges = PublishRelay.create<Boolean>() | |
private val repeatingChanges = PublishRelay.create<RemindersRepeating>() | |
private val remindersStartTimeChanges = PublishRelay.create<Pair<Int, Int>>() | |
private val remindersStopTimeChanges = PublishRelay.create<Pair<Int, Int>>() | |
private val silenceSwitchChanges = PublishRelay.create<Boolean>() | |
private val silenceStartTimeChanges = PublishRelay.create<Pair<Int, Int>>() | |
private val silenceStopTimeChanges = PublishRelay.create<Pair<Int, Int>>() | |
private val daysSelectionChanges = PublishRelay.create<DaySelection>() | |
private val soundChanges = PublishRelay.create<Uri>() | |
private val soundTests = PublishRelay.create<Uri>() | |
private val inactivitySignals = PublishRelay.create<Unit>() | |
//region Getters for view states and actions consumers | |
val remindersEnabledState: Observable<Boolean> = remindersEnabled.hide() | |
val repeatingsState: Observable<DropDown<Repeating>> = repeatings.hide() | |
val onlyUseStartTimeState: Observable<Boolean> = onlyUseStartTime.hide() | |
val remindersStartTimeState: Observable<Pair<Int, Int>> = remindersStartTime.hide() | |
val remindersStopTimeState: Observable<Pair<Int, Int>> = remindersStopTime.hide() | |
val selectedDaysState: Observable<Set<Day>> = selectedDays.hide() | |
val silenceEnabledState: Observable<Boolean> = silenceEnabled.hide() | |
val silenceStartTimeState: Observable<Pair<Int, Int>> = silenceStartTime.hide() | |
val silenceStopTimeState: Observable<Pair<Int, Int>> = silenceStopTime.hide() | |
val soundsState: Observable<DropDown<Sound>> = sounds.hide() | |
val reminderSwitchChangesConsumer: Consumer<Boolean> = reminderSwitchChanges | |
val repeatingChangesConsumer: Consumer<RemindersRepeating> = repeatingChanges | |
val remindersStartTimeChangesConsumer: Consumer<Pair<Int, Int>> = remindersStartTimeChanges | |
val remindersStopTimeChangesConsumer: Consumer<Pair<Int, Int>> = remindersStopTimeChanges | |
val silenceSwitchChangesConsumer: Consumer<Boolean> = silenceSwitchChanges | |
val silenceStartTimeChangesConsumer: Consumer<Pair<Int, Int>> = silenceStartTimeChanges | |
val silenceStopTimeChangesConsumer: Consumer<Pair<Int, Int>> = silenceStopTimeChanges | |
val daysSelectionChangesConsumer: Consumer<DaySelection> = daysSelectionChanges | |
val soundChangesConsumer: Consumer<Uri> = soundChanges | |
val soundTestsConsumer: Consumer<Uri> = soundTests | |
val inactivitySignalsConsumer: Consumer<Unit> = inactivitySignals | |
//endregion | |
override fun initialValues() { | |
remindersEnabled.accept(settingsRepo.isRemindersEnabled()) | |
repeatings.accept(dropDownFromRepeating(settingsRepo.getRepeating())) | |
remindersStartTime.accept(settingsRepo.getRemindersStartTime()) | |
remindersStopTime.accept(settingsRepo.getRemindersStopTime()) | |
selectedDays.accept(settingsRepo.getSelectedDays()) | |
silenceEnabled.accept(settingsRepo.isSilenceEnabled()) | |
silenceStartTime.accept(settingsRepo.getSilenceStartTime()) | |
silenceStopTime.accept(settingsRepo.getSilenceStopTime()) | |
sounds.accept(dropDownFromSound(settingsRepo.getSound())) | |
} | |
override fun setUp() { | |
backPresses | |
.subscribe { | |
notifier.scheduleReminders() | |
router.exit() | |
} | |
reminderSwitchChanges | |
.doOnNext(settingsRepo::putRemindersEnabled) | |
.subscribe(remindersEnabled) | |
repeatingChanges | |
.doOnNext(settingsRepo::putRepeating) | |
.map { dropDownFromRepeating(it) } | |
.subscribe(repeatings) | |
repeatingChanges | |
.map { it == RemindersRepeating.ONCE_A_DAY } | |
.subscribe(onlyUseStartTime) | |
remindersStartTimeChanges | |
.doOnNext(settingsRepo::putRemindersStartTime) | |
.subscribe(remindersStartTime) | |
remindersStopTimeChanges | |
.doOnNext(settingsRepo::putRemindersStopTime) | |
.subscribe(remindersStopTime) | |
silenceSwitchChanges | |
.doOnNext(settingsRepo::putSilenceEnabled) | |
.subscribe(silenceEnabled) | |
silenceStartTimeChanges | |
.doOnNext(settingsRepo::putSilenceStartTime) | |
.subscribe(silenceStartTime) | |
silenceStopTimeChanges | |
.doOnNext(settingsRepo::putSilenceStopTime) | |
.subscribe(silenceStopTime) | |
daysSelectionChanges | |
.withLatestFrom( | |
selectedDays, | |
BiFunction<DaySelection, Set<Day>, Set<Day>> | |
{ (day, selected), selectedDays -> | |
val currentlySelectedDays = selectedDays.toMutableSet() | |
if (selected) { | |
currentlySelectedDays.add(day) | |
} else { | |
// Remove if valid (at least one day must be selected) | |
if (selectedDays.size > 1) { | |
currentlySelectedDays.remove(day) | |
} | |
} | |
currentlySelectedDays | |
} | |
) | |
.doOnNext(settingsRepo::putSelectedDays) | |
.subscribe(selectedDays) | |
soundChanges | |
.doOnNext { settingsRepo.putSound(it) } | |
.map { dropDownFromSound(it) } | |
.subscribe(sounds) | |
soundTests | |
.subscribe(soundsHelper::playSound) | |
inactivitySignals | |
.subscribe { notifier::scheduleReminders } | |
} | |
private fun dropDownFromRepeating(repeating: RemindersRepeating): DropDown<Repeating> { | |
val repeatings = RemindersRepeating.values().toList() | |
.map { Repeating(repeatingTextsList[it.ordinal], it) } | |
val repeatingPosition = repeating.ordinal | |
return DropDown(repeatings, repeatingPosition) | |
} | |
private fun dropDownFromSound(sound: Uri): DropDown<Sound> { | |
val listOfSounds = soundsList.map { Sound(it.first, it.second) } | |
val soundPosition = soundsList.map { it.second }.indexOf(sound) | |
return DropDown(listOfSounds, soundPosition) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment