Commit 69551af9 authored by nk2's avatar nk2 🙃

init views

parent 078e8ff0
......@@ -75,6 +75,8 @@ dependencies {
compile "com.evernote:android-job:$job_version"
//View
compile "com.jakewharton:butterknife:$butterknife_version"
kapt "com.jakewharton:butterknife-compiler:$butterknife_version"
compile "com.flipboard:bottomsheet-core:$bottomsheet_version"
compile "com.flipboard:bottomsheet-commons:$bottomsheet_version"
compile "com.txusballesteros:snake:$snakeview_version"
......
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="ga.nk2ishere.dev.musclecar">
<application
android:name=".App"
tools:replace="android:allowBackup"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
......@@ -18,6 +21,15 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".features.calibration.CalibrationActivity"
android:theme="@style/AppTheme.NoActionBar"
android:screenOrientation="portrait" >
</activity>
<service
android:name=".features.pulse.PulseCatchService"
android:enabled="true"/>
</application>
</manifest>
package ga.nk2ishere.dev.musclecar.common.libs
import android.graphics.Color
import android.graphics.ColorSpace
import android.support.annotation.ColorInt
/**
* Created by nk2 on 11/02/2018.
*/
fun Int.toColor(): Int = Color.parseColor("#" + Integer.toHexString(this))
\ No newline at end of file
package ga.nk2ishere.dev.musclecar.common.libs
import java.util.*
/**
* Created by nk2 on 06/02/2018.
*/
fun ClosedRange<Int>.random() = Random().nextInt(endInclusive - start) + start
\ No newline at end of file
package ga.nk2ishere.dev.musclecar.common.libs
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.Subject
import io.reactivex.Maybe
/**
* Created by nk2 on 04/02/2018.
*/
fun <T> Single<T>.applySchedulers() = this.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())!!
\ No newline at end of file
fun <T> Single<T>.applySchedulers() = this.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())!!
fun <T> Subject<T>.applySchedulers() = this.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())!!
fun <T> Observable<T>.applySchedulers() = this.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())!!
fun <T> MaybeOfNullable(value: T?): Maybe<T> {
return if (value == null) Maybe.empty<T>() else Maybe.just(value)
}
\ No newline at end of file
package ga.nk2ishere.dev.musclecar.common.libs
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.support.v4.app.NotificationCompat
import ga.nk2ishere.dev.musclecar.R
import ga.nk2ishere.dev.musclecar.features.pulse.PulseActivity
import ga.nk2ishere.dev.musclecar.features.pulse.PulseCatchService
/**
* Created by nk2 on 11/02/2018.
*/
fun Service.sendNotification(id: Int, ticker: String, title: String, text: String) {
//These three lines makes Notification to open main activity after clicking on it
val notificationIntent = Intent(this, PulseActivity::class.java)
notificationIntent.action = Intent.ACTION_MAIN
notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER)
val contentIntent = PendingIntent.getActivity(applicationContext, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT)
val builder = NotificationCompat.Builder(this)
builder.setContentIntent(contentIntent)
.setOngoing(true)
.setSmallIcon(R.mipmap.ic_launcher)
.setTicker(ticker)
.setContentTitle(title)
.setContentText(text)
.setWhen(System.currentTimeMillis())
val notification = builder.build()
startForeground(id, notification)
}
\ No newline at end of file
......@@ -2,7 +2,8 @@ package ga.nk2ishere.dev.musclecar.common.modules
import android.content.Context
import dagger.Component
import ga.nk2ishere.dev.musclecar.features.pulse.data.repositories.RepositoryModule
import ga.nk2ishere.dev.musclecar.features.calibration.CalibrationPresenter
import ga.nk2ishere.dev.musclecar.features.pulse.PulsePresenter
import javax.inject.Singleton
/**
......@@ -12,8 +13,12 @@ import javax.inject.Singleton
@Singleton
@Component(modules = arrayOf(
ContextModule::class,
RepositoryModule::class
ga.nk2ishere.dev.musclecar.features.pulse.data.repositories.RepositoryModule::class,
ga.nk2ishere.dev.musclecar.features.calibration.data.repositories.RepositoryModule::class
)) interface AppModule {
fun getContext(): Context
//Injectors
fun inject(presenter: PulsePresenter)
fun inject(presenter: CalibrationPresenter)
}
\ No newline at end of file
package ga.nk2ishere.dev.musclecar.features.calibration
import android.os.Bundle
import android.os.PersistableBundle
import android.view.View
import com.arellomobile.mvp.MvpAppCompatActivity
import com.arellomobile.mvp.presenter.InjectPresenter
import com.arellomobile.mvp.presenter.PresenterType
import ga.nk2ishere.dev.musclecar.R
import kotlinx.android.synthetic.main.activity_calibration.*
import org.jetbrains.anko.sdk25.coroutines.onClick
/**
* Created by nk2 on 10/02/2018.
*/
class CalibrationActivity : MvpAppCompatActivity(), CalibrationView {
@InjectPresenter(tag = "CALIBRATION", type = PresenterType.WEAK, presenterId = "CALIBRATION")
lateinit var presenter: CalibrationPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_calibration)
presenter.onInit(this)
doneButton.onClick { presenter.onDoneButtonClick() }
retryButton.onClick { presenter.onRetryButtonClick() }
}
override fun showDoneButton() {
doneButton.visibility = View.VISIBLE
}
override fun showRetryButton() {
retryButton.visibility = View.VISIBLE
}
override fun hideDoneButton() {
doneButton.visibility = View.GONE
}
override fun hideRetryButton() {
retryButton.visibility = View.GONE
}
override fun addCalibratedValue(value: Int) {
calibrationNumbersText.text = "${calibrationNumbersText.text}\n$value"
}
override fun resetCalibratedValues() {
calibrationNumbersText.text = ""
}
}
\ No newline at end of file
package ga.nk2ishere.dev.musclecar.features.calibration
import android.app.Activity
import android.content.Intent
import com.arellomobile.mvp.InjectViewState
import com.arellomobile.mvp.MvpPresenter
import ga.nk2ishere.dev.musclecar.App
import ga.nk2ishere.dev.musclecar.common.libs.applySchedulers
import ga.nk2ishere.dev.musclecar.features.calibration.data.repositories.ThresholdRepository
import ga.nk2ishere.dev.musclecar.features.pulse.PulseCatchService
import kotlinx.coroutines.experimental.android.UI
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import android.media.RingtoneManager
import android.media.Ringtone
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.android.schedulers.AndroidSchedulers.mainThread
import io.reactivex.schedulers.Schedulers
/**
* Created by nk2 on 10/02/2018.
*/
@InjectViewState
class CalibrationPresenter: MvpPresenter<CalibrationView>() {
lateinit var context: Activity
@Inject lateinit var thresholdRepository: ThresholdRepository
fun onInit(context: Activity) {
App.appComponent.inject(this)
this.context = context
if(!PulseCatchService.started)
context.startService(Intent(context, PulseCatchService::class.java))
PulseCatchService.observer
.delay(1, TimeUnit.SECONDS, mainThread())
.subscribe({ with(UI) {onPulseUpdate(it)} })
}
private var counter = 1 ; private var iterCounter = 1 ; private var overallSum = 0 ; private var average = 0
private fun onPulseUpdate(pulse: Int) {
if (counter <= 15) { counter++
if(counter % 4 == 0) {
RingtoneManager.getRingtone(context, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)).play()
}
if(counter % 5 == 0) {
viewState.addCalibratedValue(average)
iterCounter++; overallSum += pulse; average = overallSum / iterCounter
}
} else {
viewState.showDoneButton()
viewState.showRetryButton()
}
}
fun onDoneButtonClick() {
val data = Intent()
data.putExtra("threshold", average)
context.setResult(0, data)
context.finish()
}
fun onRetryButtonClick() {
counter = 1; iterCounter = 1; overallSum = 0; average = 0
viewState.hideDoneButton()
viewState.hideRetryButton()
viewState.resetCalibratedValues()
}
}
\ No newline at end of file
package ga.nk2ishere.dev.musclecar.features.calibration
import com.arellomobile.mvp.MvpView
/**
* Created by nk2 on 10/02/2018.
*/
interface CalibrationView: MvpView {
fun showDoneButton()
fun showRetryButton()
fun hideDoneButton()
fun hideRetryButton()
fun addCalibratedValue(value: Int)
fun resetCalibratedValues()
}
\ No newline at end of file
package ga.nk2ishere.dev.musclecar.features.pulse.data
package ga.nk2ishere.dev.musclecar.features.calibration.data
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
......@@ -7,8 +7,13 @@ import java.util.*
/**
* Created by nk2 on 04/02/2018.
*/
open class ThresholdData : RealmObject() {
open class ThresholdData constructor() : RealmObject() {
@PrimaryKey var uuid: String = UUID.randomUUID().toString()
var name: String = ""
var threshold: Int = 0
constructor(name: String, threshold: Int): this() {
this.name = name
this.threshold = threshold
}
}
\ No newline at end of file
package ga.nk2ishere.dev.musclecar.features.calibration.data.repositories
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
/**
* Created by nk2 on 04/02/2018.
*/
@Module class RepositoryModule {
@Singleton @Provides fun provideThresholdRepository() = ThresholdRepository()
}
\ No newline at end of file
package ga.nk2ishere.dev.musclecar.features.calibration.data.repositories
import com.pixplicity.easyprefs.library.Prefs
import ga.nk2ishere.dev.musclecar.common.libs.MaybeOfNullable
import ga.nk2ishere.dev.musclecar.common.libs.applySchedulers
import ga.nk2ishere.dev.musclecar.features.calibration.data.ThresholdData
import io.reactivex.Maybe
import io.reactivex.Single
import io.realm.Realm
/**
* Created by nk2 on 04/02/2018.
*/
class ThresholdRepository {
fun findAll() = Single.fromCallable {
val realm = Realm.getDefaultInstance()
val list = realm.copyFromRealm(realm.where(ThresholdData::class.java).findAll())
realm.close()
return@fromCallable list
}.applySchedulers()
fun findByUuid(uuid: String): Single<Maybe<ThresholdData>> = Single.fromCallable {
val realm = Realm.getDefaultInstance()
val rawData = realm.where(ThresholdData::class.java).equalTo("uuid", uuid).findFirst() ?: return@fromCallable MaybeOfNullable<ThresholdData>(null)
val data: ThresholdData? = realm.copyFromRealm(rawData)
realm.close()
return@fromCallable MaybeOfNullable(data)
}.applySchedulers()
fun findCurrent(): Single<Maybe<out ThresholdData>> = Single.fromCallable {
val realm = Realm.getDefaultInstance()
val rawData = realm.where(ThresholdData::class.java).equalTo("uuid", Prefs.getString("threshold", "null")).findFirst() ?: return@fromCallable MaybeOfNullable(null)
val data: ThresholdData? = realm.copyFromRealm(rawData)
realm.close()
return@fromCallable MaybeOfNullable(data)
}.applySchedulers()
fun add(data: ThresholdData) = Single.fromCallable {
val realm = Realm.getDefaultInstance()
realm.executeTransaction {
realm.insert(data)
}
realm.close()
Prefs.putString("threshold", data.uuid)
}.applySchedulers()
}
\ No newline at end of file
package ga.nk2ishere.dev.musclecar.features.pulse
import android.animation.ArgbEvaluator
import android.animation.ObjectAnimator
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.support.design.widget.BottomSheetBehavior
import android.support.v7.app.AppCompatActivity
import android.view.View
import com.arellomobile.mvp.MvpAppCompatActivity
import com.arellomobile.mvp.presenter.InjectPresenter
import com.arellomobile.mvp.presenter.PresenterType
import ga.nk2ishere.dev.musclecar.R
import kotlinx.android.synthetic.main.activity_pulse.*
import org.jetbrains.anko.sdk25.coroutines.onClick
import android.animation.ValueAnimator
import android.animation.ValueAnimator.AnimatorUpdateListener
/**
* Created by nk2 on 04/02/2018.
*/
class PulseActivity : AppCompatActivity() {
class PulseActivity : MvpAppCompatActivity(), PulseView {
@InjectPresenter(tag = "PULSEACTIVITY", type = PresenterType.WEAK, presenterId = "PULSEACTIVITY")
lateinit var presenter: PulsePresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pulse)
presenter.onInit(this)
settings.onClick { presenter.onSettingsClick() }
presenter.onDailyViewCreate(daysList)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if(data != null)
presenter.onResult(data)
}
override fun updateMusclePulse(pulse: Int) {
currentPulse.text = pulse.toString()
rateGraph.addValue(pulse.toFloat())
}
override fun updateCurrentCount(count: Int) {
currentDone.text = count.toString()
}
override fun initCurrentThreshold(threshold: Int) {
currentThreshold.text = "$threshold"
}
override fun showBottomSheet(view: View) {
BottomSheetBehavior.from(view).state = BottomSheetBehavior.STATE_EXPANDED
}
override fun hideBottomSheet(view: View) {
BottomSheetBehavior.from(view).state = BottomSheetBehavior.STATE_COLLAPSED
}
override fun changeBackgroundColor(fromColor: Int, toColor: Int) {
val colorAnimation = ValueAnimator.ofObject(ArgbEvaluator(), fromColor, toColor)
colorAnimation.duration = 250
colorAnimation.addUpdateListener { animator -> topPanel.setBackgroundColor(animator.animatedValue as Int) }
colorAnimation.start()
}
}
\ No newline at end of file
package ga.nk2ishere.dev.musclecar.features.pulse
import android.app.Notification
import android.app.Service
import android.content.Intent
import android.os.IBinder
import ga.nk2ishere.dev.musclecar.R.mipmap.ic_launcher
import android.app.PendingIntent
import android.app.NotificationManager
import android.support.v4.app.NotificationCompat
import ga.nk2ishere.dev.musclecar.R
import ga.nk2ishere.dev.musclecar.common.libs.applySchedulers
import ga.nk2ishere.dev.musclecar.common.libs.random
import ga.nk2ishere.dev.musclecar.common.libs.sendNotification
import io.reactivex.Observable
import io.reactivex.Observer
import io.reactivex.subjects.PublishSubject
import io.reactivex.subjects.Subject
import org.jetbrains.anko.notificationManager
import java.lang.Thread.sleep
import kotlin.concurrent.thread
/**
* Created by nk2 on 06/02/2018.
*/
class PulseCatchService : Service() {
companion object {
val observer: Subject<Int> = PublishSubject.create()
val DEFAULT_NOTIFICATION_ID = 101
var started = false
private set
}
val worker = thread(false) {
//Task
while (started) {
//TODO BLUETOOTH SEX
observer.onNext((-1000..1000).random())
sleep(500)
}
}
override fun onCreate() {
super.onCreate()
started = true
worker.start()
observer.applySchedulers()
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
sendNotification(DEFAULT_NOTIFICATION_ID, "Ticker", "MuscleCar", "Linstening")
return Service.START_REDELIVER_INTENT
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onDestroy() {
super.onDestroy()
started = false
worker.interrupt()
notificationManager.cancel(DEFAULT_NOTIFICATION_ID)
stopSelf()
}
}
\ No newline at end of file
package ga.nk2ishere.dev.musclecar.features.pulse
import android.animation.ArgbEvaluator
import android.app.Activity
import android.app.AlertDialog
import android.content.Intent
import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.RecyclerView
import android.widget.ImageView
import android.widget.LinearLayout
import com.arellomobile.mvp.InjectViewState
import com.arellomobile.mvp.MvpPresenter
import ga.nk2ishere.dev.musclecar.App
import ga.nk2ishere.dev.musclecar.R
import ga.nk2ishere.dev.musclecar.common.libs.applySchedulers
import ga.nk2ishere.dev.musclecar.common.libs.setUp
import ga.nk2ishere.dev.musclecar.features.calibration.CalibrationActivity
import ga.nk2ishere.dev.musclecar.features.calibration.data.ThresholdData
import ga.nk2ishere.dev.musclecar.features.calibration.data.repositories.ThresholdRepository
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.android.schedulers.AndroidSchedulers.mainThread
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.experimental.android.UI
import org.jetbrains.anko.sdk25.coroutines.onClick
import org.jetbrains.anko.toast
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import android.content.DialogInterface
import android.text.Editable
import android.view.View
import android.widget.EditText
import android.widget.TextView
import com.pixplicity.easyprefs.library.Prefs
import org.jetbrains.anko.AlertBuilder
import android.animation.ObjectAnimator
import android.graphics.Color
import android.support.v4.content.res.ResourcesCompat
import ga.nk2ishere.dev.musclecar.common.libs.toColor
import ga.nk2ishere.dev.musclecar.features.pulse.data.DailydoneData
import ga.nk2ishere.dev.musclecar.features.pulse.data.repositories.DailydoneRepository
import ga.nk2ishere.dev.musclecar.features.pulse.misc.DailydoneAdapter
import org.jetbrains.anko.find
import org.joda.time.DateTime
import org.joda.time.DateTime.now
import org.w3c.dom.Text
/**
* Created by nk2 on 06/02/2018.
*/
@InjectViewState
class PulsePresenter: MvpPresenter<PulseView>() {
lateinit var context: Activity
var bottomSheet: View? = null
var current: ThresholdData? = null
var dailydone: DailydoneData? = null
@Inject lateinit var thresholdRepository: ThresholdRepository
@Inject lateinit var dailydoneRepository: DailydoneRepository
fun onInit(context: Activity) {
App.appComponent.inject(this)
this.context = context
if(!PulseCatchService.started)
context.startService(Intent(context, PulseCatchService::class.java))
PulseCatchService.observer
.delay(1, TimeUnit.SECONDS, mainThread())
.subscribe({ with(UI) { onPulseUpdate(it) } })
thresholdRepository.findCurrent().subscribe({
current = it.blockingGet()
updateCurrentThreshold()
}, { it.printStackTrace() })
dailydoneRepository.findCurrent().subscribe({
if(it.blockingGet() == null) {
dailydone = DailydoneData(now(), 0)
dailydoneRepository.add(dailydone!!).subscribe({ updateCurrentDailydone() }, { it.printStackTrace() })
} else {
dailydone = it.blockingGet()
updateCurrentDailydone()
}
}, { it.printStackTrace() })
}
var counter = -1
private fun onPulseUpdate(pulse: Int) {
when {
counter < 0 -> {
if (current != null) {
if (current!!.threshold < pulse) {
viewState.changeBackgroundColor(
ResourcesCompat.getColor(context.resources, R.color.colorPrimary, null).toColor(),
ResourcesCompat.getColor(context.resources, R.color.colorAccent, null).toColor())
counter = 0
//TODO drive!
updateCurrentDailydone(1)
}
}
viewState.updateMusclePulse(pulse)
}
counter == 3 -> {
counter = -1
viewState.changeBackgroundColor(
ResourcesCompat.getColor(context.resources, R.color.colorAccent, null).toColor(),
ResourcesCompat.getColor(context.resources, R.color.colorPrimary, null).toColor())
}
else -> counter++
}
}
private fun updateCurrentThreshold() {
viewState.initCurrentThreshold(current?.threshold ?: 0)
}
private fun updateCurrentThreshold(threshold: Int) {
viewState.initCurrentThreshold(threshold)
}
private fun updateCurrentDailydone() {
if(dailydone != null)
viewState.updateCurrentCount(dailydone!!.count)
}
private fun updateCurrentDailydone(toAdd: Int) {
if(dailydone != null) {
dailydone!!.count = dailydone!!.count + toAdd
dailydoneRepository.update(dailydone!!).subscribe({
viewState.updateCurrentCount(dailydone!!.count + toAdd)
}, { it.printStackTrace() })
}
}
fun onResult(data: Intent) {
context.toast("got result ${data.extras.getInt("threshold")}")
val textView = EditText(context)
AlertDialog.Builder(context)
.setView(textView)
.setTitle("Calibration")
.setMessage("Enter title of threhold")
.setPositiveButton("Yes", { _, _ ->
thresholdRepository.add(ThresholdData(textView.text.toString(), data.extras.getInt("threshold"))).subscribe({
viewState.hideBottomSheet(bottomSheet!!)
updateCurrentThreshold()
}, { it.printStackTrace() }) })
.show()
}
fun onSettingsClick() {
if(bottomSheet == null) onSettingsCreate()
viewState.showBottomSheet(bottomSheet!!)
}
fun onSettingsCreate() {