Commit cc9cca77 authored by Filip Wiesner's avatar Filip Wiesner

Timetable Corutine refresh, TextGlue clickTo moved to Glue

parent 529aad8f
......@@ -10,7 +10,7 @@ import com.cvut.blackbird.R
import com.cvut.blackbird.flows.tasks.TasksFragment
import com.cvut.blackbird.flows.profile.ProfileFragment
import com.cvut.blackbird.flows.search.SearchFragment
import com.cvut.blackbird.flows.timetable.TimetableDots
import com.cvut.blackbird.flows.timetable.TimetableFragment
import kotlinx.android.synthetic.main.navigation_fragment.*
class NavigationFragment : Fragment() {
......@@ -26,7 +26,7 @@ class NavigationFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
tasksFrament = TasksFragment.newInstance()
timetableFragment = TimetableDots.newInstance()
timetableFragment = TimetableFragment.newInstance()
searchFragment = SearchFragment.newInstance()
profileFragment = ProfileFragment.newInstance()
}
......
......@@ -54,7 +54,7 @@ class AuthFragment : Fragment() {
.setAction("Try Again") { viewModel.refreshToken() }
send(acceptBtn) clickTo ::onLogged
this bind acceptBtn clickTo ::onLogged
}
private fun setupBinding() {
......
......@@ -5,42 +5,34 @@ import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.GradientDrawable
import android.os.Bundle
import android.text.method.Touch
import android.util.EventLog
import android.util.Log
import android.util.TypedValue
import android.view.*
import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import bg.devlabs.transitioner.Transitioner
import com.cvut.blackbird.R
import com.cvut.blackbird.extensions.*
import com.cvut.blackbird.model.entities.Event
import com.cvut.blackbird.model.services.UserInfo
import com.cvut.blackbird.support.glue.bind
import com.cvut.blackbird.support.glue.clickTo
import com.cvut.blackbird.support.glue.send
import com.cvut.blackbird.support.glue.toPassValueTo
import org.joda.time.DateTime
import org.joda.time.DateTimeConstants
import com.cvut.blackbird.support.wobbly.WobblyAdapter
import com.cvut.blackbird.support.wobbly.WobblyElement
import com.github.florent37.kotlin.pleaseanimate.please
import kotlinx.android.synthetic.main.timetable_dots_fragment.*
import kotlinx.android.synthetic.main.timetable_element_detail.*
import org.jetbrains.anko.toast
import kotlin.math.abs
class TimetableDots : Fragment() {
class TimetableFragment : Fragment() {
companion object {
const val DAY_BALLS_SPACING = 0
const val MIN_DIFF_TO_NOTIFY = 15
fun newInstance() = TimetableDots()
fun newInstance() = TimetableFragment()
}
private lateinit var viewModel: TimetableViewModel
......@@ -53,6 +45,7 @@ class TimetableDots : Fragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(activity!!).get(TimetableViewModel::class.java)
viewModel.refreshTimetable()
updateDateText(viewModel.date)
setupUI()
......@@ -79,27 +72,25 @@ class TimetableDots : Fragment() {
}
private fun setupBinding() {
viewModel.timetable.observe(this, setupDays())
}
private fun setupDays(): Observer<List<Event>> {
wobblyTimetable.adapter = getAdapter()
this bind viewModel.timetable toPassValueTo ::onWeekChange
}
return Observer { events ->
wobblyTimetable.fadeRefresh {
events.groupBy { it.startsAt.dayOfWeek }.entries
.forEach {(day, events) ->
wobblyTimetable.addLineDescription(day - 1, 0, events.first().startsAt.toString("HH:mm"))
events.forEachIndexed { index, event ->
if (index > 0) {
val difference = event.startsAt.minuteOfDay - events[index - 1].endsAt.minuteOfDay
if (difference > MIN_DIFF_TO_NOTIFY)
wobblyTimetable.addLineDescription(day - 1, index, "$difference min")
}
private fun onWeekChange(events: List<Event>) {
wobblyTimetable.fadeRefresh {
events.groupBy { it.startsAt.dayOfWeek }.entries
.forEach {(day, events) ->
wobblyTimetable.addLineDescription(day - 1, 0, events.first().startsAt.toString("HH:mm"))
events.forEachIndexed { index, event ->
if (index > 0) {
val difference = event.startsAt.minuteOfDay - events[index - 1].endsAt.minuteOfDay
if (difference > MIN_DIFF_TO_NOTIFY)
wobblyTimetable.addLineDescription(day - 1, index, "$difference min")
}
}
}
}
}
}
private fun onLectureClick(element: View) {
......
......@@ -2,8 +2,13 @@ package com.cvut.blackbird.flows.timetable
import androidx.lifecycle.LiveData
import androidx.lifecycle.LiveDataReactiveStreams
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.cvut.blackbird.extensions.fetchUsing
import com.cvut.blackbird.extensions.indicateProgressBy
import com.cvut.blackbird.extensions.withDefault
import com.cvut.blackbird.model.Loading
import com.cvut.blackbird.model.NotYet
import com.cvut.blackbird.model.Result
import com.cvut.blackbird.model.Success
import com.cvut.blackbird.model.entities.Event
......@@ -12,65 +17,76 @@ import com.cvut.blackbird.model.flows.TimetableModel
import io.reactivex.BackpressureStrategy
import io.reactivex.Observable
import io.reactivex.subjects.PublishSubject
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
import org.joda.time.DateTime
class TimetableViewModel : ViewModel() {
private val model = TimetableModel()
//Input
private val timetableRequestSubject = PublishSubject.create<Pair<DateSpan, Int>>()
fun requestTimetable(from: Long, to: Long, id: Int = 0) { timetableRequestSubject.onNext(Pair(DateSpan(from, to), id)) }
private val timetableRefreshSubject = PublishSubject.create<Unit>()
fun refreshTimetable() { timetableRefreshSubject.onNext(Unit) }
//Output
var date = DateTime.now()!!
private val _loadingStatus: LiveData<Pair<Boolean, Int>>
private val _timetableUpdateRequestResult: LiveData<Result<Unit>>
private val _timetable: LiveData<List<Event>>
val loadingStatus get() = _loadingStatus
val timetableUpdateResult get() = _timetableUpdateRequestResult
val timetable get() = _timetable
private val _loadingStatus = MutableLiveData<Boolean>() withDefault false
private val _timetableUpdateRequestResult = MutableLiveData<Result<List<Event>>>() withDefault NotYet()
private val _timetable = MutableLiveData<List<Event>>() withDefault listOf()
val loadingStatus: LiveData<Boolean> get() = _loadingStatus
val timetableUpdateResult: LiveData<Result<List<Event>>> get() = _timetableUpdateRequestResult
val timetable: LiveData<List<Event>> get() = _timetable
init {
if (date.dayOfWeek > 5) date = date.plusWeeks(1)
_timetable = configTimetable(model.resultTimetableRequest)
_timetableUpdateRequestResult = LiveDataReactiveStreams.fromPublisher(
model.resultTimetableRefreshRequest
.toFlowable(BackpressureStrategy.LATEST))
// _timetable = configTimetable(model.resultTimetableRequest)
// _timetableUpdateRequestResult = LiveDataReactiveStreams.fromPublisher(
// model.resultTimetableRefreshRequest
// .toFlowable(BackpressureStrategy.LATEST))
//
// _loadingStatus = configLoadingStatus(model.resultTimetableRefreshRequest, model.resultTimetableRequest)
//
// model.requestTimetable = timetableRequestSubject
// model.refreshTimetable = timetableRefreshSubject
_loadingStatus = configLoadingStatus(model.resultTimetableRefreshRequest, model.resultTimetableRequest)
model.requestTimetable = timetableRequestSubject
model.refreshTimetable = timetableRefreshSubject
model.prepareInput()
// model.prepareInput()
}
private fun configTimetable(result: Observable<Result<List<Event>>>): LiveData<List<Event>> {
return LiveDataReactiveStreams.fromPublisher(
result .skip(1)
.distinctUntilChanged()
.filter { it is Success }
.map { (it as Success).value }
.toFlowable(BackpressureStrategy.LATEST)
)
//Input
private var requestJob: Job? = null
fun requestTimetable(from: Long, to: Long) {
requestJob?.cancel()
requestJob = launch(UI) {
model.getTimetable(DateSpan(from, to))
.observeForever { _timetable.postValue(it) }
}
}
private fun configLoadingStatus(
refresh: Observable<Result<Unit>>,
request: Observable<Result<List<Event>>>
): LiveData<Pair<Boolean, Int>> {
return LiveDataReactiveStreams.fromPublisher(
Observable
.merge(request, refresh)
.map { Pair(it is Loading, it.id)}
.toFlowable(BackpressureStrategy.LATEST)
)
private var refreshJob: Job? = null
fun refreshTimetable() {
refreshJob?.cancel()
refreshJob = launch { model.refreshTimetable() }
}
// private fun configTimetable(result: Observable<Result<List<Event>>>): LiveData<List<Event>> {
// return LiveDataReactiveStreams.fromPublisher(
// result .skip(1)
// .distinctUntilChanged()
// .filter { it is Success }
// .map { (it as Success).value }
// .toFlowable(BackpressureStrategy.LATEST)
// )
// }
//
// private fun configLoadingStatus(
// refresh: Observable<Result<Unit>>,
// request: Observable<Result<List<Event>>>
// ): LiveData<Pair<Boolean, Int>> {
// return LiveDataReactiveStreams.fromPublisher(
// Observable
// .merge(request, refresh)
// .map { Pair(it is Loading, it.id)}
// .toFlowable(BackpressureStrategy.LATEST)
// )
// }
}
package com.cvut.blackbird.model.database
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
......@@ -18,7 +19,7 @@ interface EventDao {
fun futureExams(now: Long = DateTime.now().millis): Flowable<List<Event>>
@Query("SELECT * FROM Event Where startsAt BETWEEN :from AND :to ORDER BY startsAt")
fun timeSpan(from: Long, to: Long): Flowable<List<Event>>
fun timeSpan(from: Long, to: Long): LiveData<List<Event>>//Flowable<List<Event>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(events: List<Event>)
......
package com.cvut.blackbird.model.flows
import androidx.lifecycle.LiveData
import com.cvut.blackbird.BlackBirdAC
import com.cvut.blackbird.extensions.Variable
import com.cvut.blackbird.extensions.disposeTo
......@@ -10,6 +11,7 @@ import com.cvut.blackbird.model.services.SiriusService
import com.cvut.blackbird.model.services.UserInfo
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import retrofit2.Call
import javax.inject.Inject
typealias DateSpan = Pair<Long, Long>
......@@ -20,20 +22,20 @@ class TimetableModel: BlackBirdModel() {
@Inject
lateinit var eventDao: EventDao
// Pipes
private val timetableRequestPipe: Variable<Result<List<Event>>> = Variable(NotYet())
private val timetableRefreshPipe: Variable<Result<Unit>> = Variable(NotYet())
// Input
var requestTimetable: Observable<Pair<DateSpan, Int>> = Observable.never()
var refreshTimetable: Observable<Unit> = Observable.never()
// Output
var resultTimetableRefreshRequest = timetableRefreshPipe.asObservable()
private set
var resultTimetableRequest = timetableRequestPipe.asObservable()
private set
// // Pipes
// private val timetableRequestPipe: Variable<Result<List<Event>>> = Variable(NotYet())
// private val timetableRefreshPipe: Variable<Result<Unit>> = Variable(NotYet())
//
//
// // Input
// var requestTimetable: Observable<Pair<DateSpan, Int>> = Observable.never()
// var refreshTimetable: Observable<Unit> = Observable.never()
//
// // Output
// var resultTimetableRefreshRequest = timetableRefreshPipe.asObservable()
// private set
// var resultTimetableRequest = timetableRequestPipe.asObservable()
// private set
init {
......@@ -41,46 +43,87 @@ class TimetableModel: BlackBirdModel() {
}
// Private methods
private fun resetTimetablePipe() { timetableRequestPipe.value = NotYet() }
// private fun resetTimetablePipe() { timetableRequestPipe.value = NotYet() }
fun prepareInput() {
requestTimetable.subscribe { (date, to) ->
resetTimetablePipe()
requestTimetable(date, to).subscribe {
timetableRequestPipe.value = it
} disposeTo disposeBag
} disposeTo disposeBag
refreshTimetable.subscribe {
resetTimetablePipe()
requestTimetableRefresh().subscribe {
timetableRefreshPipe.value = it
} disposeTo disposeBag
} disposeTo disposeBag
// requestTimetable.subscribe { (date, to) ->
// resetTimetablePipe()
// requestTimetable(date, to).subscribe {
// timetableRequestPipe.value = it
// } disposeTo disposeBag
// } disposeTo disposeBag
// refreshTimetable.subscribe {
// resetTimetablePipe()
// requestTimetableRefresh().subscribe {
// timetableRefreshPipe.value = it
// } disposeTo disposeBag
// } disposeTo disposeBag
}
private fun requestTimetableRefresh(): Observable<Result<Unit>> {
return siriusService
.getEvents(UserInfo.username)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.newThread())
.map {
if (it.isSuccessful && it.body() != null) {
eventDao.insertAll(it.body()!!.events!!)
Success(Unit)
} else Failure<Unit>("Failed when fetching events\nError: ${it.errorBody()?.string()}")
}.startWith(Loading())
.onErrorReturn { Failure(it.localizedMessage) }
// private fun requestTimetableRefresh(): Observable<Result<Unit>> {
// return siriusService
// .getEvents(UserInfo.username)
// .subscribeOn(Schedulers.io())
// .observeOn(Schedulers.newThread())
// .map {
// if (it.isSuccessful && it.body() != null) {
// eventDao.insertAll(it.body()!!.events!!)
// Success(Unit)
// } else Failure<Unit>("Failed when fetching events\nError: ${it.errorBody()?.string()}")
// }.startWith(Loading())
// .onErrorReturn { Failure(it.localizedMessage) }
// }
// private fun requestTimetable(span: DateSpan, id: Int): Observable<Result<List<Event>>> {
// return Observable.just(eventDao)
// .subscribeOn(Schedulers.io())
// .observeOn(Schedulers.newThread())
// .map { it.timeSpan(span.first, span.second) }
// .flatMap { it.toObservable() }
// .map<Result<List<Event>>> { Success(it, id) }
// .startWith(Loading(id))
// .onErrorReturn { Failure(it.localizedMessage, id) }
// }
// suspend fun refreshTimetable(): Result<Unit> {
// val result: Result<Unit>
// try {
// val response = siriusService.getEventsEx(UserInfo.username).execute()
// result = if (response.isSuccessful && response.body() != null) {
// eventDao.insertAll(response.body()!!.events!!)
// Success(Unit)
// } else Failure("Failed when fetching events\nError: ${response.errorBody()?.string()}")
// } catch (e: Throwable) {
// return Failure(e.localizedMessage)
// }
// return result
// }
suspend fun getTimetable(span: DateSpan): LiveData<List<Event>> = eventDao.timeSpan(span.first, span.second)
suspend fun refreshTimetable(): Result<List<Event>> {
val result = fetch(siriusService.getEventsEx(UserInfo.username))
if (result is Success) {
val events = result.value.events ?: listOf()
eventDao.insertAll(events)
return Success(events)
} else if (result is Failure) return Failure(result.message)
return Failure("No error message")
}
private fun requestTimetable(span: DateSpan, id: Int): Observable<Result<List<Event>>> {
return Observable.just(eventDao)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.newThread())
.map { it.timeSpan(span.first, span.second) }
.flatMap { it.toObservable() }
.map<Result<List<Event>>> { Success(it, id) }
.startWith(Loading(id))
.onErrorReturn { Failure(it.localizedMessage, id) }
fun<T> fetch(call: Call<T>): Result<T> {
val result: Result<T>
try {
val response = call.execute()
result = if (response.isSuccessful && response.body() != null) {
Success(response.body()!!)
} else Failure(response.errorBody()?.string() ?: "No error message")
} catch (e: Throwable) {
return Failure(e.localizedMessage)
}
return result
}
}
\ No newline at end of file
}
......@@ -2,6 +2,7 @@ package com.cvut.blackbird.model.services
import com.cvut.blackbird.model.entities.EventParent
import io.reactivex.Observable
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.*
......@@ -18,6 +19,13 @@ interface SiriusService {
@Header("Authorization") token: String = accessToken
): Observable<Response<EventParent>>
@GET("people/{username}/events")
fun getEventsEx(
@Path("username") username: String = UserInfo.username,
@Query("limit") resultsLimit: Int = 1000,
@Header("Authorization") token: String = accessToken
): Call<EventParent>
// @FormUrlEncoded
// @GET("exams")
// fun getExams(
......
......@@ -22,3 +22,10 @@ infix fun Glue.enabledTo(source: LiveData<Boolean>) {
source.observe(life, Observer { actor.isEnabled = it })
}
infix fun Glue.clickTo(receiver: () -> Unit) = this.apply {
actor.setOnClickListener { receiver.invoke() }
}
inline fun<T> Glue.clickTo(payload: T, crossinline receiver: (T) -> Unit) = this.apply {
actor.setOnClickListener { receiver.invoke(payload) }
}
package com.cvut.blackbird.support.glue
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
class SuperGlue<T>(val life: LifecycleOwner, val emmiter: LiveData<T>)
infix fun<T> LifecycleOwner.bind(data: LiveData<T>)
= SuperGlue(this, data)
infix fun<T> SuperGlue<T>.toPassValueTo(action: (T) -> Unit) = this.apply {
emmiter.observe(life, Observer { action(it) })
}
infix fun<T> SuperGlue<T>.toNotify(action: () -> Unit) = this.apply {
emmiter.observe(life, Observer { action() })
}
\ No newline at end of file
......@@ -3,8 +3,9 @@ package com.cvut.blackbird.support.glue
import android.text.Editable
import android.text.TextWatcher
import android.widget.TextView
import androidx.lifecycle.LifecycleOwner
class TextGlue(val sender: TextView) {
class TextGlue(val sender: TextView, var lifecycleOwner: LifecycleOwner? = null) {
var blankIgnore = false
fun ignoreBlank(ignore: Boolean = true): TextGlue = this.apply { blankIgnore = ignore }
}
......@@ -24,11 +25,4 @@ infix fun TextGlue.textChangeTo(receiver: (String) -> Unit): TextGlue {
})
return this
}
infix fun TextGlue.clickTo(receiver: () -> Unit) = this.apply {
sender.setOnClickListener { receiver.invoke() }
}
fun<T> TextGlue.clickTo(receiver: (T) -> Unit, payload: T) = this.apply {
sender.setOnClickListener { receiver.invoke(payload) }
}
\ No newline at end of file
......@@ -5,7 +5,7 @@
android:id="@+id/dotsParentLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".flows.timetable.TimetableDots">
tools:context=".flows.timetable.TimetableFragment">
<TextView
android:id="@+id/dotsWeekDateTxt"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment