Last active
March 18, 2018 02:00
-
-
Save Pooh3Mobi/21a7290baf1c69bb4fb624fcc0e79432 to your computer and use it in GitHub Desktop.
Kotlin-FRP Metronome sample code
This file contains hidden or 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
class Formatters { | |
fun formatBpmInfo(msec: Long) = "$msec msec\n${(60000/msec).toFloat()} BPM" | |
} |
This file contains hidden or 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
<android.support.constraint.ConstraintLayout | |
xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
xmlns:tools="http://schemas.android.com/tools" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
tools:context="mobi.pooh3.frpstudydummy.metronome.FRPSeekBarMetronomeFragment"> | |
<SeekBar | |
android:id="@+id/seekBar" | |
style="@style/Widget.AppCompat.SeekBar.Discrete" | |
android:layout_width="200dp" | |
android:layout_height="wrap_content" | |
android:max="1000" | |
android:progress="500" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintHorizontal_bias="0.5" | |
app:layout_constraintStart_toEndOf="@+id/textView" | |
app:layout_constraintTop_toBottomOf="@+id/pulse_img"/> | |
<TextView | |
android:id="@+id/out_interval" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
app:layout_constraintBottom_toTopOf="@+id/seekBar" | |
app:layout_constraintEnd_toStartOf="@+id/pulse_img" | |
app:layout_constraintHorizontal_bias="0.5" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toTopOf="parent" | |
tools:text="interval"/> | |
<ImageView | |
android:id="@+id/pulse_img" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:layout_marginStart="8dp" | |
android:src="@mipmap/ic_launcher_round" | |
app:layout_constraintBottom_toBottomOf="@+id/out_interval" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintHorizontal_bias="0.5" | |
app:layout_constraintStart_toEndOf="@+id/out_interval" | |
app:layout_constraintTop_toTopOf="@+id/out_interval"/> | |
<TextView | |
android:id="@+id/textView" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:text="Tepmpo" | |
app:layout_constraintBottom_toBottomOf="@+id/seekBar" | |
app:layout_constraintEnd_toStartOf="@+id/seekBar" | |
app:layout_constraintHorizontal_bias="0.5" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toTopOf="@+id/seekBar"/> | |
</android.support.constraint.ConstraintLayout> |
This file contains hidden or 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
import android.media.ToneGenerator | |
import android.os.Bundle | |
import android.support.v4.app.Fragment | |
import android.view.LayoutInflater | |
import android.view.View | |
import android.view.ViewGroup | |
import com.jakewharton.rxbinding2.widget.text | |
import com.jakewharton.rxbinding2.widget.userChanges | |
import io.reactivex.Observable | |
import io.reactivex.android.schedulers.AndroidSchedulers | |
import io.reactivex.rxkotlin.withLatestFrom | |
import io.reactivex.subjects.BehaviorSubject | |
import kotlinx.android.synthetic.main.fragment_metronome.* | |
import mobi.pooh3.frpstudydummy.R | |
import java.util.concurrent.TimeUnit | |
class FRPSeekBarMetronomeFragment : Fragment() { | |
override fun onCreateView(inf: LayoutInflater?, ctr: ViewGroup?, savedInstanceState: Bundle?): View? | |
= inf!!.inflate(R.layout.fragment_rx_seekbar_interval, ctr, false) | |
override fun onViewCreated(v: View?, bdl: Bundle?) { | |
val sProgress = seekBar.userChanges() | |
.map { if (it <= 50) 50 else it }.share() | |
// inputs to outputs | |
val outputs = create(Inputs( | |
seekBar.progress.toLong(), | |
sProgress, | |
sProgress.debounce(500, TimeUnit.MILLISECONDS) | |
)) | |
outputs.bpmText.subscribe(out_interval.text()) | |
outputs.duration | |
.subscribe { duration -> | |
pulse_img?.also { | |
it.fadeOut(newDuration = duration) | |
ToneGeneratorHolder.instance() | |
.startTone(ToneGenerator.TONE_PROP_PROMPT) | |
} | |
} | |
} | |
fun create(inputs: Inputs): Outputs { | |
val mn = Metronome( | |
inputs.sProgress, | |
inputs.sDebouncedProgress, | |
inputs.defaultDuration) | |
return Outputs( | |
bpmText = mn.bpmText.map { Formatters().formatBpmInfo(it) }, | |
duration = mn.duration | |
) | |
} | |
companion object { | |
fun newInstance(): FRPSeekBarMetronomeFragment { | |
return FRPSeekBarMetronomeFragment() | |
} | |
} | |
} | |
data class Inputs(val defaultDuration: Long, val sProgress: Observable<Int>, val sDebouncedProgress: Observable<Int>) | |
data class Outputs( | |
val duration: Observable<Long> = BehaviorSubject.createDefault(0L), | |
val bpmText: Observable<String> = BehaviorSubject.createDefault("") | |
) | |
class Metronome(sProgress: Observable<Int>, sDebouncedProgress: Observable<Int>, defaultDuration: Long) { | |
val bpmText: Observable<Long> | |
val duration: Observable<Long> | |
init { | |
val bpmTextValue = BehaviorSubject.createDefault(defaultDuration) | |
bpmText = bpmTextValue | |
sProgress.map { it.toLong() }.subscribe(bpmTextValue) | |
val durationValue = BehaviorSubject.createDefault(defaultDuration) | |
duration = durationValue | |
.switchInterval() | |
sDebouncedProgress | |
.map { it.toLong() } | |
.subscribe(durationValue) | |
} | |
} | |
private fun View.fadeOut(from: Float = 1f, to: Float = 0f, newDuration: Long) { | |
this.apply { this.alpha = from } | |
.animate() | |
.apply { duration = newDuration } | |
.alpha(to) | |
} | |
fun Observable<Long>.switchInterval(): Observable<Long> = | |
this.switchMap { | |
Observable.interval(it, TimeUnit.MILLISECONDS) | |
.withLatestFrom(this, {_, duration_ -> duration_}) | |
.observeOn(AndroidSchedulers.mainThread()) | |
} |
This file contains hidden or 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
import android.media.AudioManager | |
import android.media.ToneGenerator | |
object ToneGeneratorHolder { | |
private val toneGenerator: ToneGenerator by lazy { ToneGenerator(AudioManager.STREAM_SYSTEM, ToneGenerator.MAX_VOLUME) } | |
fun instance() = toneGenerator | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment