Commit e638ff1b authored by Filip Wiesner's avatar Filip Wiesner

Init load progress text, multiple teachers in event detail, other tweaks

parent 27412ac6
package com.cvut.blackbird.flows.authentication
import android.animation.Animator
import android.content.Intent
import android.net.Uri
import androidx.lifecycle.ViewModelProviders
......@@ -10,15 +9,10 @@ import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.AlphaAnimation
import android.widget.Toast
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.navigation.findNavController
import androidx.transition.Transition
import androidx.transition.TransitionManager
import bg.devlabs.transitioner.Transitioner
import com.cvut.blackbird.R
import com.cvut.blackbird.extensions.*
......@@ -29,11 +23,9 @@ import com.cvut.blackbird.model.services.AuthService
import com.cvut.blackbird.model.services.UserInfo
import com.cvut.blackbird.model.support.AuthResult
import com.cvut.blackbird.support.glue.*
import com.github.florent37.kotlin.pleaseanimate.core.custom.CustomAnimExpectation
import com.github.florent37.kotlin.pleaseanimate.please
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.auth_fragment.*
import org.jetbrains.anko.childrenSequence
class AuthFragment : Fragment() {
private var lastValidUser = Student()
......@@ -107,10 +99,14 @@ class AuthFragment : Fragment() {
bind(usernameStatus) visibilityTo viewModel.userLoadingStatus
bind(acceptBtn) enabledToSuccessOf viewModel.studentResult
bind(acceptBtn) clickTo ::onLogged
send(usernameInput)
.textChangeTo(viewModel::fetchStudent)
bindText(usernameInput)
.changeTo(viewModel::fetchStudent)
.ignoreBlank()
bindText(infoMessage)
.to(viewModel.userInitState,this)
.ignoreBlank()
}
private fun onAuthorized() {
......
......@@ -8,11 +8,14 @@ import com.cvut.blackbird.extensions.asBoolProgressStatus
import com.cvut.blackbird.extensions.fetchUsing
import com.cvut.blackbird.extensions.indicateProgressBy
import com.cvut.blackbird.extensions.withDefault
import com.cvut.blackbird.model.Failure
import com.cvut.blackbird.model.NotYet
import com.cvut.blackbird.model.Result
import com.cvut.blackbird.model.Success
import com.cvut.blackbird.model.entities.Student
import com.cvut.blackbird.model.support.AuthResult
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.launch
class AuthViewModel : ViewModel() {
......@@ -24,12 +27,14 @@ class AuthViewModel : ViewModel() {
private val _authLoadingStatus = MutableLiveData<Boolean>() withDefault false
private val _userLoadingStatus = MutableLiveData<Boolean>() withDefault false
private val _userInitResult = MutableLiveData<Result<Unit>>() withDefault NotYet()
private val _userInitState = MutableLiveData<String>() withDefault "Didn't start yet"
val authStatus: LiveData<AuthResult> get() = _authStatus
val studentResult: LiveData<Result<Student>> get() = _studentResult
val authLoadingStatus: LiveData<Boolean> get() = _authLoadingStatus
val userLoadingStatus: LiveData<Boolean> get() = _userLoadingStatus
val userInitResult: LiveData<Result<Unit>> get() = _userInitResult
val userInitState: LiveData<String> get() = _userInitState
fun initToken(code: String) {
launch { _authLoadingStatus.asBoolProgressStatus {
......@@ -56,7 +61,21 @@ class AuthViewModel : ViewModel() {
private var initJob: Job? = null
fun initUser() {
initJob?.cancel()
initJob = _userInitResult
.fetchUsing { model.initLoad() }
initJob = _userInitResult.fetchUsing {
_userInitState.postValue("Downloading your timetable")
val events = model.loadEvents()
val teachers = if (events is Success) {
_userInitState.postValue("Downloading all your teachers")
model.loadTeachers()
} else return@fetchUsing events as Failure
val courses = if (teachers is Success) {
_userInitState.postValue("Downloading your courses")
model.loadCourses()
} else return@fetchUsing teachers as Failure
courses
}
}
}
\ No newline at end of file
......@@ -9,14 +9,11 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import com.cvut.blackbird.R
import com.cvut.blackbird.extensions.CircularRevealSettings
import com.cvut.blackbird.extensions.lastIndexOfNumber
import com.cvut.blackbird.model.Success
import com.cvut.blackbird.model.entities.Event
import com.cvut.blackbird.model.entities.EventType
import com.cvut.blackbird.support.glue.bind
import com.cvut.blackbird.support.glue.observe
import com.cvut.blackbird.support.glue.toPassValueTo
import kotlinx.android.synthetic.main.fragment_timetable_detail.*
......@@ -28,31 +25,42 @@ class TimetableDetailFragment : Fragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(activity!!).get(TimetableViewModel::class.java)
setupBinding()
}
this bind viewModel.eventDetail toPassValueTo ::setupEvent
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View?
= inflater.inflate(R.layout.fragment_timetable_detail, container, false)
private fun setupBinding() {
observe(viewModel.eventDetail) {
event = it
viewModel.requestCourse(event.linked?.course ?: "")
if (event.linked?.teachers != null)
viewModel.requestTeachers(event.linked!!.teachers!!)
setupEvent(it)
}
observe(viewModel.course) {
if (it is Success && it.value != null)
detail_fullName.text = it.value.name
}
observe(viewModel.teacher) {
if (it is Success && it.value != null)
detail_teachersContent.text = "${it.value.firstName} ${it.value.lastName}"
if (it.isNotEmpty()) {
detail_teachersContent.text = ""
for (teacher in it)
detail_teachersContent.text =
detail_teachersContent.text.toString() +
"${teacher.firstName} ${teacher.lastName}\n"
detail_teachersContent.text = detail_teachersContent.text.dropLast(1)
}
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View?
= inflater.inflate(R.layout.fragment_timetable_detail, container, false)
private fun setupEvent(event: Event) {
viewModel.requestCourse(event.linked?.course ?: "")
viewModel.requestTeacher(event.linked?.teachers?.firstOrNull() ?: "")
detail_abbr.background = when {
event.eventType == EventType.LECTURE -> context?.resources?.getDrawable(R.drawable.ic_timetable_lecture, context?.theme)
event.eventType == EventType.TUTORIAL -> context?.resources?.getDrawable(R.drawable.ic_timetable_tutorial, context?.theme)
else -> context?.resources?.getDrawable(R.drawable.ic_timetable_info, context?.theme)
detail_abbr.background = when(event.eventType) {
EventType.LECTURE -> context?.resources?.getDrawable(R.drawable.ic_timetable_lecture, context?.theme)
EventType.TUTORIAL -> context?.resources?.getDrawable(R.drawable.ic_timetable_tutorial, context?.theme)
else -> context?.resources?.getDrawable(R.drawable.ic_timetable_info, context?.theme)
}
detail_abbr.text = event.linked?.course?.courseAbbr ?: "-"
......@@ -67,9 +75,10 @@ class TimetableDetailFragment : Fragment() {
detail_noteContent.text = event.note?.cs ?: "None"
}
val String.courseAbbr get() =this.substring(
val String.courseAbbr get() = this.substring(
this.lastIndexOfNumber()
.coerceAtMost(this.length - 3)
.coerceAtLeast(0),
this.length)
this.length
)
}
\ No newline at end of file
package com.cvut.blackbird.flows.timetable
import androidx.lifecycle.*
import com.cvut.blackbird.extensions.CircularRevealSettings
import com.cvut.blackbird.extensions.atEndOfTheDay
import com.cvut.blackbird.extensions.atStartOfTheDay
import com.cvut.blackbird.extensions.withDefault
......@@ -27,14 +26,14 @@ class TimetableViewModel : ViewModel() {
private val _timetable = MutableLiveData<Map<Int,List<Event>>>() withDefault mapOf()
private val _displayedWeek = MutableLiveData<DateTime>() withDefault DateTime.now()
private val _course = MutableLiveData<Result<Course?>>() withDefault NotYet()
private val _teacher = MutableLiveData<Result<Teacher?>>() withDefault NotYet()
private val _teachers = MutableLiveData<List<Teacher>>() withDefault listOf()
val loadingStatus: LiveData<Boolean> get() = _loadingStatus
val timetableUpdateResult: LiveData<Result<List<Event>>> get() = _timetableUpdateRequestResult
val timetable: LiveData<Map<Int,List<Event>>> get() = _timetable
val displayedWeek: LiveData<DateTime> get() = _displayedWeek
val course: LiveData<Result<Course?>> get() = _course
val teacher: LiveData<Result<Teacher?>> get() = _teacher
val teacher: LiveData<List<Teacher>> get() = _teachers
val eventDetail = MutableLiveData<Event>() withDefault Event.empty
......@@ -76,8 +75,8 @@ class TimetableViewModel : ViewModel() {
_course.postValue(Success(model.getCourse(code)))
} }
fun requestTeacher(username: String) { launch {
_teacher.postValue(Success(model.getTeacher(username)))
fun requestTeachers(usernames: List<String>) { launch {
_teachers.postValue(model.getTeachers(usernames))
} }
private var refreshJob: Job? = null
......
......@@ -14,4 +14,7 @@ interface CourseDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(courses: List<Course>)
@Query("DELETE FROM Course")
fun deleteAll()
}
\ No newline at end of file
......@@ -13,4 +13,7 @@ interface TeacherDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(courses: List<Teacher>)
@Query("DELETE FROM Teacher")
fun deleteAll()
}
\ No newline at end of file
......@@ -86,52 +86,40 @@ class AuthModel: BlackBirdModel() {
return result
}
suspend fun initLoad(): Result<Unit> {
val events = async { refreshEvents(siriusService, eventDao) }
val teachers = async {
val deferredTeacherList = ArrayList<Deferred<Result<Teacher>>>()
val completedTeacherList = ArrayList<Result<Teacher>>()
events.await()
val teachers = eventDao.getTeachers()
for (teacher in teachers)
deferredTeacherList.add(async { fetch(kosService.getTeacher(teacher)) })
deferredTeacherList.forEach { completedTeacherList.add(it.await()) }
completedTeacherList
}
val courses = async {
val deferredCourseList = ArrayList<Deferred<Result<Course>>>()
val completedCourses = ArrayList<Result<Course>>()
val courses = fetch(kosService.getStudentsCourses())
if (courses is Success) {
for (link in courses.value.courses)
deferredCourseList.add(async { fetch(kosService.getCourse(link.name)) })
deferredCourseList.forEach { completedCourses.add(it.await()) }
}
completedCourses
}
suspend fun loadEvents() = refreshEvents(siriusService, eventDao)
suspend fun loadTeachers(): Result<Unit> {
val deferredTeacherList = ArrayList<Deferred<Result<Teacher>>>()
val completedTeacherList = ArrayList<Result<Teacher>>()
val teachers = eventDao.getTeachers()
for (teacher in teachers)
deferredTeacherList.add(async { fetch(kosService.getTeacher(teacher)) })
deferredTeacherList.forEach { completedTeacherList.add(it.await()) }
val result = completedTeacherList.evaluateResult()
if (result is Success) teacherDao.insertAll(completedTeacherList.map { (it as Success).value })
return result
}
suspend fun loadCourses(): Result<Unit> {
val deferredCourseList = ArrayList<Deferred<Result<Course>>>()
val completedCourses = ArrayList<Result<Course>>()
val resultCourses = courses.await()
val resultEvents = events.await()
val resultTeachers = teachers.await()
return if (
resultCourses.all { it is Success } &&
resultTeachers.all { it is Success } &&
resultEvents is Success)
{
courseDao.insertAll(resultCourses.map { (it as Success).value })
teacherDao.insertAll(resultTeachers.map { (it as Success).value })
Success(Unit)
} else Failure("Failed initializing account.\n" +
"Error message: " +
when {
resultEvents is Failure -> resultEvents.message
resultTeachers.any { it is Failure } -> (resultTeachers.firstOrNull { it is Failure } as? Failure)
?.message ?: "Unknow error"
else -> (resultCourses.firstOrNull { it is Failure } as? Failure)
?.message ?: "Unknow error"
}
)
val courses = fetch(kosService.getStudentsCourses())
if (courses is Success) {
for (link in courses.value.courses)
deferredCourseList.add(async { fetch(kosService.getCourse(link.name)) })
deferredCourseList.forEach { completedCourses.add(it.await()) }
} else return Failure((courses as Failure).message)
val result = completedCourses.evaluateResult()
if (result is Success) courseDao.insertAll(completedCourses.map { (it as Success).value })
return result
}
}
\ No newline at end of file
}
fun List<Result<*>>.evaluateResult(): Result<Unit> =
if(any { it is Failure })
Failure((first { it is Failure } as Failure).message)
else Success(Unit)
\ No newline at end of file
......@@ -2,20 +2,27 @@ package com.cvut.blackbird.model.flows
import com.cvut.blackbird.BlackBirdAC
import com.cvut.blackbird.model.BlackBirdModel
import com.cvut.blackbird.model.database.CourseDao
import com.cvut.blackbird.model.database.EventDao
import com.cvut.blackbird.model.database.TeacherDao
import com.cvut.blackbird.model.services.AuthInfo
import com.cvut.blackbird.model.services.UserInfo
import javax.inject.Inject
class ProfileModel: BlackBirdModel() {
@Inject
lateinit var eventDao: EventDao
@Inject lateinit var eventDao: EventDao
@Inject lateinit var courseDao: CourseDao
@Inject lateinit var teacherDao: TeacherDao
init {
BlackBirdAC.graph.inject(this)
}
suspend fun deleteDB() = eventDao.deleteAll()
suspend fun deleteDB() {
eventDao.deleteAll()
courseDao.deleteAll()
teacherDao.deleteAll()
}
suspend fun deleteSharedPrefs() {
UserInfo.clear()
......
......@@ -5,6 +5,7 @@ import com.cvut.blackbird.model.*
import com.cvut.blackbird.model.database.CourseDao
import com.cvut.blackbird.model.database.EventDao
import com.cvut.blackbird.model.database.TeacherDao
import com.cvut.blackbird.model.entities.Teacher
import com.cvut.blackbird.model.services.SiriusService
import javax.inject.Inject
......@@ -24,5 +25,11 @@ class TimetableModel: BlackBirdModel() {
suspend fun getCourse(code: String) = courseDao.getCourse(code)
suspend fun getTeacher(username: String) = teacherDao.getTeacher(username)
suspend fun getTeachers(usernames: List<String>) = ArrayList<Teacher>().apply {
for (username in usernames) {
val teacher = teacherDao.getTeacher(username)
if (teacher != null)
add(teacher)
}
}
}
\ No newline at end of file
......@@ -3,32 +3,46 @@ package com.cvut.blackbird.support.glue
import android.text.Editable
import android.text.TextWatcher
import android.widget.TextView
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
class TextGlue(val sender: TextView) {
var blankIgnore = false
private set(value) {field = value}
fun ignoreBlank(ignore: Boolean = true): TextGlue = this.apply { blankIgnore = ignore }
}
fun send(actor: TextView) = TextGlue(actor)
fun bindText(actor: TextView) = TextGlue(actor)
infix fun TextGlue.textChangeTo(receiver: (String) -> Unit): TextGlue {
sender.onTextChanged {
if ((blankIgnore && it.isNotEmpty()) ||
!blankIgnore)
receiver(it)
infix fun TextGlue.changeTo(receiver: (String) -> Unit) = this.apply {
sender.onTextChanged { text ->
if ((blankIgnore && text.isNotEmpty()) || !blankIgnore)
receiver(text)
}
return this
}
infix fun TextGlue.textChangeTo(receiver: MutableLiveData<String>) = this.let {
infix fun TextGlue.changeTo(receiver: MutableLiveData<String>) = this.apply {
sender.onTextChanged { text ->
if ((blankIgnore && text.isNotEmpty()) ||
!blankIgnore)
if ((blankIgnore && text.isNotEmpty()) || !blankIgnore)
receiver.postValue(text)
}
}
infix fun TextGlue.changeTo(receiver: TextView) = this.let {
sender.onTextChanged { text ->
if ((blankIgnore && text.isNotEmpty()) || !blankIgnore)
receiver.text = text
}
}
fun TextGlue.to(data: LiveData<String>, life: LifecycleOwner) = this.apply {
life.observe(data) { text ->
if (!(blankIgnore && text.isBlank()))
sender.text = text
}
}
fun TextView.onTextChanged(action: (text: String) -> Unit) {
addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(s: Editable?) = Unit
......
......@@ -214,9 +214,10 @@
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:fontFamily="sans-serif-condensed"
android:lineSpacingExtra="4sp"
android:textSize="22sp"
android:textStyle="bold"
tools:text="Petr Křemen" />
tools:text="Petr Křemen\nMartin Balík" />
<TextView
android:id="@+id/detail_parallelTitle"
......
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