Commit 1ae4b014 authored by Filip Wiesner's avatar Filip Wiesner

Logout, Event detail(WIP)

parent 9e9148a0
......@@ -71,8 +71,8 @@
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="-241" />
<option name="y" value="10" />
<option name="x" value="12" />
<option name="y" value="12" />
</Point>
</option>
<option name="myPositions">
......@@ -92,17 +92,12 @@
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="12" />
<option name="y" value="54" />
<option name="x" value="265" />
<option name="y" value="12" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="toAuth">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="toEventDetail">
<value>
<LayoutPositions />
......@@ -118,13 +113,18 @@
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="389" />
<option name="y" value="328" />
<option name="x" value="518" />
<option name="y" value="12" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="toAuth">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
......
......@@ -6,7 +6,7 @@
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# and specify the fully qualified class firstName to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
......@@ -17,5 +17,5 @@
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
# hide the original source file firstName.
#-renamesourcefileattribute SourceFile
......@@ -13,8 +13,10 @@ import javax.inject.Singleton
@Component(modules = [ContextModule::class, RoomModule::class, ServicesModule::class])
interface ApplicationComponent {
fun inject(model: BlackBirdModel)
fun inject(model: AuthModel)
fun inject(model: TasksModel)
fun inject(model: TimetableModel)
fun inject(model: SearchModel)
fun inject(model: ProfileModel)
}
\ No newline at end of file
......@@ -4,13 +4,16 @@ import androidx.fragment.app.Fragment
import android.app.Activity
import android.view.View
import android.view.inputmethod.InputMethodManager
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getSystemService
public fun Fragment.closeKeyboard() {
fun Fragment.closeKeyboard() {
val imm = activity?.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
val view = activity?.currentFocus ?: View(activity)
imm.hideSoftInputFromWindow(view.windowToken, 0)
}
\ No newline at end of file
}
fun Fragment.getColor(id: Int) = ContextCompat.getColor(requireContext(),id)
\ No newline at end of file
package com.cvut.blackbird.extensions
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import android.view.View
import androidx.lifecycle.MutableLiveData
import com.cvut.blackbird.model.BlackBirdModel
import com.cvut.blackbird.model.Loading
import com.cvut.blackbird.model.Result
import com.cvut.blackbird.model.Success
import com.cvut.blackbird.support.glue.Glue
import com.cvut.blackbird.support.glue.SuperGlue
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.launch
fun <T> View.bindEnabledToSuccess(owner: LifecycleOwner, liveData: LiveData<Result<T>>) {
liveData.observe(owner , Observer{
isClickable = it is Success
})
}
infix fun<T> Glue.enabledToSuccessOf(source: LiveData<Result<T>>) {
source.observe(life, Observer { actor.isEnabled = it is Success })
}
public infix fun<T> MutableLiveData<T>.withDefault(init: T) = apply { value = init }
infix fun<T> SuperGlue<Result<T>>.toNotifyOnSuccess(action: () -> Unit) = this.apply {
emmiter.observe(life, Observer { if (it is Success) action() })
}
infix fun<T> MutableLiveData<T>.withDefault(init: T) = apply { value = init }
suspend fun MutableLiveData<Boolean>.asBoolProgressStatus(job: suspend () -> Unit) {
inline fun MutableLiveData<Boolean>.asBoolProgressStatus(job: () -> Unit) {
postValue(true)
job.invoke()
postValue(false)
}
infix fun Job.indicateProgressBy(status: MutableLiveData<Boolean>): Job {
status.postValue(true)
invokeOnCompletion { status.postValue(false) }
......@@ -39,13 +36,27 @@ infix fun Job.indicateProgressBy(status: MutableLiveData<Boolean>): Job {
}
suspend infix fun<T> MutableLiveData<Result<T>>.asProgressStatus(job: suspend () -> Unit) {
inline infix fun<T> MutableLiveData<Result<T>>.asProgressStatus(job: () -> Result<T>) {
postValue(Loading())
job.invoke()
postValue(job.invoke())
}
infix fun<T> MutableLiveData<Result<T>>.fetchUsing(job: suspend () -> Result<T>) =
inline infix fun<T> MutableLiveData<Result<T>>.fetchUsing(crossinline job: suspend () -> Result<T>) =
launch {
postValue(Loading())
postValue(job.invoke())
}
/**
* JOBS
*/
enum class JobStrategy { CANCEL, EXIT }
inline fun doWith(job: Job?, strategy: JobStrategy = JobStrategy.CANCEL, crossinline todo: () -> Job): Job? {
if (job?.isActive == true) {
when (strategy) {
JobStrategy.CANCEL -> job.cancel()
JobStrategy.EXIT -> return job
}
}
return todo()
}
......@@ -4,14 +4,11 @@ import android.content.Intent
import android.net.Uri
import androidx.lifecycle.ViewModelProviders
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AlphaAnimation
import android.widget.TextView
import androidx.navigation.findNavController
import com.cvut.blackbird.R
......@@ -54,7 +51,7 @@ class AuthFragment : Fragment() {
observe(viewModel.studentResult) { result ->
if(result is Success) {
infoMessage.text = "Found user: ${result.value.name} ${result.value.surname}\nStudying: ${result.value.programme}"
infoMessage.text = "Found user: ${result.value.firstName} ${result.value.surname}\nStudying: ${result.value.programme}"
lastValidUser = result.value
usernameInputLayout.isErrorEnabled = false
closeKeyboard()
......
......@@ -8,24 +8,37 @@ import android.view.View
import android.view.ViewGroup
import com.cvut.blackbird.R
import com.cvut.blackbird.extensions.toNotifyOnSuccess
import com.cvut.blackbird.flows.findMainNav
import com.cvut.blackbird.model.services.UserInfo
import com.cvut.blackbird.support.glue.bind
import com.cvut.blackbird.support.glue.clickTo
import kotlinx.android.synthetic.main.profile_fragment.*
class ProfileFragment : Fragment() {
companion object {
fun newInstance() = ProfileFragment()
}
private lateinit var viewModel: ProfileViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.profile_fragment, container, false)
}
savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.profile_fragment, container, false)
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(ProfileViewModel::class.java)
// TODO: Use the ViewModel
setupUi()
setupBinding()
}
private fun setupUi() {
profileFullName.text = "${UserInfo.firstName} ${UserInfo.surname}"
}
private fun setupBinding() {
this bind logOutBtn clickTo viewModel::logOut
this bind viewModel.logoutStatus toNotifyOnSuccess ::loggedOut
}
private fun loggedOut() { findMainNav()?.navigate(R.id.toAuth) }
}
package com.cvut.blackbird.flows.profile
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.cvut.blackbird.extensions.JobStrategy
import com.cvut.blackbird.extensions.asProgressStatus
import com.cvut.blackbird.extensions.doWith
import com.cvut.blackbird.extensions.withDefault
import com.cvut.blackbird.model.NotYet
import com.cvut.blackbird.model.Result
import com.cvut.blackbird.model.Success
import com.cvut.blackbird.model.flows.ProfileModel
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.launch
class ProfileViewModel : ViewModel() {
// TODO: Implement the ViewModel
private val model = ProfileModel()
private val _logoutStatus = MutableLiveData<Result<Unit>>() withDefault NotYet()
val logoutStatus: LiveData<Result<Unit>> get() = _logoutStatus
var logoutJob: Job? = null
fun logOut() {
logoutJob = doWith(logoutJob, JobStrategy.EXIT) {
launch { _logoutStatus asProgressStatus {
model.deleteDB()
model.deleteSharedPrefs()
Success(Unit)
} }
}
}
}
......@@ -36,7 +36,7 @@ class TimetableBall(context: Context): WobblyElement(context) {
setTextSize(TypedValue.COMPLEX_UNIT_SP, 17f)
// backgroundTintList = ColorStateList
// .valueOf(Kolor.fromSeed(name.hashCode())
// .valueOf(Kolor.fromSeed(firstName.hashCode())
// .withBrightness(0.75f)
// .withSaturation(0.65f)
// .colorInt)
......
......@@ -7,14 +7,12 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.findNavController
import com.cvut.blackbird.R
import com.cvut.blackbird.extensions.CircularRevealSettings
import com.cvut.blackbird.extensions.registerCircularReveal
import com.cvut.blackbird.extensions.startCircularExit
import com.cvut.blackbird.flows.findMainNav
import com.cvut.blackbird.extensions.lastIndexOfNumber
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.toPassValueTo
import kotlinx.android.synthetic.main.fragment_timetable_detail.*
......@@ -32,23 +30,45 @@ class TimetableDetailFragment : Fragment() {
this bind viewModel.eventDetail toPassValueTo ::setupEvent
detailCloseBtn.setOnClickListener { _ ->
view?.startCircularExit(revealSettings) {
findMainNav()?.navigateUp()
}
}
// detailExitBtn.setOnClickListener { _ ->
// view?.startCircularExit(revealSettings) {
// findMainNav()?.navigateUp()
// }
// }
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_timetable_detail, container, false)
revealSettings = TimetableDetailFragmentArgs.fromBundle(arguments).revealSettings
view.registerCircularReveal(revealSettings)
// revealSettings = TimetableDetailFragmentArgs.fromBundle(arguments).revealSettings
// view.registerCircularReveal(revealSettings)
return view
}
private fun setupEvent(event: Event) {
eventDetail.text = event.linked?.course
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.text = event.linked?.course?.courseAbbr ?: "-"
detail_codeName.text = event.linked?.course
detail_sequenceNum.text = event.sequenceNum.toString()
detail_lengthTxt.text = "${event.endsAt.minuteOfDay - event.startsAt.minuteOfDay} min"
detail_capacityTxt.text = "${event.occupied}/${event.capacity}"
detail_timeTxt.text = "${event.startsAt.toString("HH:mm")} - ${event.endsAt.toString("HH:mm")}"
detail_teachersContent.text = event.linked?.teachers?.firstOrNull() ?: "None"
detail_parallelContent.text = if (event.parallel.isNotBlank()) event.parallel else "-"
detail_roomContent.text = event.linked?.room ?: "None"
detail_noteContent.text = event.note?.cs ?: "None"
//TODO: paralelka, ucitel, ucebna, note
}
val String.courseAbbr get() =this.substring(
this.lastIndexOfNumber()
.coerceAtMost(this.length - 3)
.coerceAtLeast(0),
this.length)
}
\ No newline at end of file
......@@ -94,8 +94,6 @@ class TimetableFragment : Fragment() {
}
}
fun Fragment.getColor(id: Int) = ContextCompat.getColor(requireContext(),id)
private fun onLectureClick(element: View) {
if (element is TimetableBall) {
val directions = NavigationFragmentDirections.toEventDetail(
......@@ -107,7 +105,7 @@ class TimetableFragment : Fragment() {
Color.WHITE
))
viewModel.eventDetail.value = element.payload
findMainNav()!!.navigate(directions)
findMainNav()!!.navigate(R.id.toEventDetail)
}
}
......
......@@ -33,6 +33,7 @@ class TimetableViewModel : ViewModel() {
init {
refresh()
if (_displayedWeek.value!!.dayOfWeek > 5) _displayedWeek.apply { value = value?.plusWeeks(1) }
_displayedWeek.observeForever { date ->
requestTimetable(
......
package com.cvut.blackbird.model
import android.util.Log
import com.cvut.blackbird.BlackBirdAC
import com.cvut.blackbird.model.database.EventDao
import com.cvut.blackbird.model.services.*
......@@ -32,6 +33,7 @@ abstract class BlackBirdModel {
}
protected suspend fun refreshToken(): Result<Unit> {
Log.d(BlackBirdAC.LOG_TAG, "Refreshing token")
val result = fetch(authService.refreshTokenEx())
return when (result) {
is Success -> {
......@@ -39,7 +41,7 @@ abstract class BlackBirdModel {
Success(Unit)
}
is Failure -> Failure(result.message)
else -> Failure("unexpected error")
else -> Failure("Unexpected error")
}
}
......@@ -51,10 +53,10 @@ abstract class BlackBirdModel {
result = if (response.isSuccessful && response.body() != null) {
Success(response.body()!!)
} else {
if (response.code() == 401) {
if (refreshToken() is Success) fetch(call)
else Failure(response.errorBody()?.string() ?: "No error message")
} else Failure(response.errorBody()?.string() ?: "No error message")
if (response.code() == 401 && refreshToken() is Success)
fetch(call)
else
Failure(response.errorBody()?.string() ?: "No error message")
}
} catch (e: Throwable) {
return Failure(e.localizedMessage)
......
package com.cvut.blackbird.model.database
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.*
import com.cvut.blackbird.model.entities.Event
import org.joda.time.DateTime
......@@ -22,4 +19,7 @@ interface EventDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(events: List<Event>)
@Query("DELETE FROM Event")
fun deleteAll()
}
\ No newline at end of file
......@@ -3,12 +3,13 @@ package com.cvut.blackbird.model.entities
import com.tickaroo.tikxml.annotation.Path
import com.tickaroo.tikxml.annotation.PropertyElement
import com.tickaroo.tikxml.annotation.Xml
import org.joda.time.DateTime
@Xml(name = "atom:content")
class Student {
@Path("atom:content")
@PropertyElement(name = "firstName")
var name: String? = null
var firstName: String? = null
@Path("atom:content")
@PropertyElement(name = "lastName")
......@@ -33,4 +34,9 @@ class Student {
@Path("atom:content")
@PropertyElement(name = "studyGroup")
var studyGroup: Int? = null
@Path("atom:content")
@PropertyElement(name = "startDate")
var startDate: String? = null
}
\ No newline at end of file
package com.cvut.blackbird.model.flows
public class ProfileModel {
import com.cvut.blackbird.BlackBirdAC
import com.cvut.blackbird.model.BlackBirdModel
import com.cvut.blackbird.model.database.EventDao
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
init {
BlackBirdAC.graph.inject(this)
}
suspend fun deleteDB() = eventDao.deleteAll()
suspend fun deleteSharedPrefs() {
UserInfo.clear()
AuthInfo.clear()
}
}
\ No newline at end of file
......@@ -12,7 +12,7 @@ object AuthInfo: KotprefModel() {
object UserInfo: KotprefModel() {
var username by stringPref()
var name by stringPref()
var firstName by stringPref()
var surname by stringPref()
var email by stringPref()
var faculty by stringPref()
......@@ -21,6 +21,7 @@ object UserInfo: KotprefModel() {
fun setStudent(student: Student): Unit = bulk {
username = student.username ?: ""
firstName = student.firstName ?: ""
surname = student.surname ?: ""
email = student.email ?: ""
faculty = student.faculty ?: ""
......
......@@ -8,10 +8,14 @@ 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 {
inline infix fun<T> SuperGlue<T>.toPassValueTo(crossinline action: (T) -> Unit) = this.apply {
emmiter.observe(life, Observer { action(it) })
}
infix fun<T> SuperGlue<T>.toNotify(action: () -> Unit) = this.apply {
inline infix fun<T> SuperGlue<T>.toNotify(crossinline action: () -> Unit) = this.apply {
emmiter.observe(life, Observer { action() })
}
inline infix fun SuperGlue<Boolean>.toNotifyOnTrue(crossinline action: () -> Unit) = this.apply {
emmiter.observe(life, Observer { if(it) action() })
}
\ No newline at end of file
......@@ -228,16 +228,16 @@ class Wobbly: ScrollView {
}
private lateinit var lineCheckpoint: WobblyElement
override fun onDraw(canvas: Canvas?) {
for ((index, lead) in leads) {
for (i in 0 until leads.size) { //TODO Fix ArrayList leak
canvas?.drawLine(
headers[index].centerX - headerScroll.scrollX,
headers[index].centerY,
lead.centerX,
lead.centerY + headerScroll.height,
headers[leads[i].first].centerX - headerScroll.scrollX,
headers[leads[i].first].centerY,
leads[i].second.centerX,
leads[i].second.centerY + headerScroll.height,
stringPathPaint)
lineCheckpoint = lead as WobblyElement
lineCheckpoint = leads[i].second as WobblyElement
while (lineCheckpoint.child != null) {
canvas?.drawLine(
lineCheckpoint.centerX,
......@@ -250,7 +250,8 @@ class Wobbly: ScrollView {
}
}
lineDescriptors.forEach { it.updatePosition() }
for (i in 0 until lineDescriptors.size) lineDescriptors[i].updatePosition()