Commit 73a20fe6 authored by Filip Wiesner's avatar Filip Wiesner

Login without name input, (CVUT RSS WIP)

parent 45c2148f
package com.cvut.blackbird.flows.authentication
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.lifecycle.ViewModelProviders
......@@ -10,6 +11,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AlphaAnimation
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintSet
import androidx.navigation.findNavController
import androidx.transition.TransitionManager
......@@ -28,8 +30,6 @@ import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.auth_fragment.*
class AuthFragment : Fragment() {
private var lastValidUser = Student()
private lateinit var viewModel: AuthViewModel
private var snack: Snackbar? = null
......@@ -54,32 +54,7 @@ class AuthFragment : Fragment() {
private fun setupBinding() {
observe(viewModel.studentResult) { result ->
if(result is Success) {
infoMessage.text = "Found user: ${result.value.firstName} ${result.value.surname}\nStudying: ${result.value.programme}"
lastValidUser = result.value
usernameInputLayout.isErrorEnabled = false
closeKeyboard()
} else if (usernameInput.text!!.isNotBlank()) {
usernameInputLayout.isErrorEnabled = true
usernameInputLayout.error = "Username not found!"
}
}
observe(viewModel.authStatus) { result ->
if (result == AuthResult.SUCCESS) {
if(UserInfo.username.isNotBlank()) onLogged()
else onAuthorized()
} else if (result != AuthResult.NOT_YET) {
snack?.setText("Error: ${result.name}")
infoMessage.text = result.errorDesc
if (!result.critical) authorize()
else snack!!.show()
}
}
observe(viewModel.userInitResult) {result ->
observe(viewModel.userInitResult) { result ->
infoMessage.text = when (result) {
is Success -> {
onAuthFinished()
......@@ -89,62 +64,34 @@ class AuthFragment : Fragment() {
UserInfo.username = ""
result.message
}
else -> infoMessage.text
else -> "Loading"
}
}
observe(viewModel.authLoadingStatus) { if (it) snack?.dismiss() }
bind(authProgress) visibilityTo viewModel.authLoadingStatus
bind(usernameStatus) visibilityTo viewModel.userLoadingStatus
bind(acceptBtn) enabledToSuccessOf viewModel.studentResult
bind(acceptBtn) clickTo ::onLogged
observe(viewModel.authStatus) { result ->
if (result == AuthResult.SUCCESS)
viewModel.initUser()
else if (result != AuthResult.NOT_YET) {
snack?.setText("Error: ${result.name}")
infoMessage.text = result.errorDesc
if (!result.critical) authorize()
else snack!!.show()
}
bindText(usernameInput)
.changeTo(viewModel::fetchStudent)
.ignoreBlank()
}
bindText(infoMessage)
.to(viewModel.userInitState,this)
.ignoreBlank()
}
observe(viewModel.authLoadingStatus) { if (it) snack?.dismiss() }
private fun onAuthorized() {
val animLength: Long = 1000
val anim = AlphaAnimation(0.0f, 1.0f)
anim.duration = animLength
acceptBtn.startAnimation(anim)
usernameInputLayout.startAnimation(anim)
bind(authProgress) visibilityTo viewModel.authLoadingStatus
acceptBtn.visibility = View.VISIBLE
usernameInputLayout.visibility = View.VISIBLE
bind(viewModel.userInitState) toPassValueTo infoMessage::setText
}
/**
* Auth processes handling
*/
private fun onLogged() {
UserInfo.setStudent(lastValidUser)
viewModel.initUser()
please {
animate(usernameInputLayout) toBe { invisible() }
animate(acceptBtn) toBe { invisible() }
animate(infoMessage) toBe { textSize(18f) }
}.withEndAction {
infoMessage.gravity = Gravity.CENTER or Gravity.BOTTOM
infoMessage.setTextColor(getColor(R.color.colorTextLight))
} .start()
val newConstraints = ConstraintSet().apply { clone(activity, R.layout.auth_fragment_transition) }
TransitionManager.beginDelayedTransition(auth)
newConstraints.applyTo(auth)
}
private fun onAuthFinished() {
view!!.findNavController().navigate(R.id.onLogged)
}
......@@ -180,4 +127,4 @@ class AuthFragment : Fragment() {
startActivity(intent)
}
}
}
}
\ No newline at end of file
......@@ -15,7 +15,6 @@ 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() {
......@@ -23,16 +22,12 @@ class AuthViewModel : ViewModel() {
//Output
private val _authStatus = MutableLiveData<AuthResult>() withDefault AuthResult.NOT_YET
private val _studentResult = MutableLiveData<Result<Student>>() withDefault NotYet()
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
......@@ -50,20 +45,18 @@ class AuthViewModel : ViewModel() {
.indicateProgressBy(_authLoadingStatus)
}
private var studentJob: Job? = null
fun fetchStudent(username: String) {
studentJob?.cancel()
studentJob = _studentResult
.fetchUsing { model.requestStudent(username) }
.indicateProgressBy(_userLoadingStatus)
}
private var initJob: Job? = null
fun initUser() {
initJob?.cancel()
initJob = _userInitResult.fetchUsing {
_userInitState.postValue("Downloading your timetable")
val events = model.loadEvents()
_userInitState.postValue("Looking up your account")
val user = model.loadUser()
val events = if (user is Success) {
_userInitState.postValue("Downloading your timetable")
model.loadEvents()
} else return@fetchUsing user as Failure
val teachers = if (events is Success) {
_userInitState.postValue("Downloading all your teachers")
......@@ -76,6 +69,6 @@ class AuthViewModel : ViewModel() {
} else return@fetchUsing teachers as Failure
courses
}
} indicateProgressBy _authLoadingStatus
}
}
\ No newline at end of file
......@@ -34,7 +34,7 @@ abstract class BlackBirdModel {
protected suspend fun refreshToken(): Result<Unit> {
Log.d(BlackBirdAC.LOG_TAG, "Refreshing token")
val result = fetch(authService.refreshTokenEx())
val result = fetch(authService.refreshToken())
return when (result) {
is Success -> {
AuthInfo.accessToken = result.value.token
......@@ -54,7 +54,7 @@ abstract class BlackBirdModel {
Success(response.body()!!)
} else {
if (response.code() == 401 && refreshToken() is Success)
fetch(call)
fetch(call.clone()) //Clone to not get "Already executed" exception
else
Failure(response.errorBody()?.string() ?: "No error message")
}
......
......@@ -7,4 +7,9 @@ data class AccessToken(
@SerializedName("refresh_token") val refreshToken: String?,
@SerializedName("token_type") val tokenType: String,
@SerializedName("expires_in") val expiresIn: Int
)
class User(
@SerializedName("username") val username: String,
@SerializedName("email") val email: String
)
\ No newline at end of file
package com.cvut.blackbird.model.entities
import com.tickaroo.tikxml.annotation.Element
import com.tickaroo.tikxml.annotation.Path
import com.tickaroo.tikxml.annotation.PropertyElement
import com.tickaroo.tikxml.annotation.Xml
@Xml
class NewsRoot(
@Path("channel")
@Element(name = "item")
val news: List<News>
)
@Xml
class News(
@PropertyElement
val title: String,
@PropertyElement(name = "datum")
val date: String,
@PropertyElement
val description: String,
@PropertyElement
val category: String,
@PropertyElement
val link: String
)
\ No newline at end of file
......@@ -9,9 +9,11 @@ import com.cvut.blackbird.model.database.getTeachers
import com.cvut.blackbird.model.entities.Course
import com.cvut.blackbird.model.entities.Student
import com.cvut.blackbird.model.entities.Teacher
import com.cvut.blackbird.model.entities.User
import com.cvut.blackbird.model.services.AuthInfo
import com.cvut.blackbird.model.services.KosService
import com.cvut.blackbird.model.services.SiriusService
import com.cvut.blackbird.model.services.UserInfo
import com.cvut.blackbird.model.support.AuthResult
import kotlinx.coroutines.experimental.Deferred
import kotlinx.coroutines.experimental.async
......@@ -32,7 +34,7 @@ class AuthModel: BlackBirdModel() {
suspend fun initToken(code: String): AuthResult {
val result: AuthResult
try {
val response = authService.getTokenEx(code).execute()
val response = authService.getToken(code).execute()
if (response.body()?.token != null && response.body()?.refreshToken != null) {
AuthInfo.accessToken = response.body()!!.token
AuthInfo.refreshToken = response.body()!!.refreshToken!!
......@@ -68,22 +70,16 @@ class AuthModel: BlackBirdModel() {
return returnVal
}
suspend fun requestStudent(username: String) : Result<Student> {
val result: Result<Student>
suspend fun loadUser(): Result<Unit> {
val userInfo = fetch(authService.getUserInfo())
val user = if (userInfo is Success) {
fetch(kosService.getStudent(userInfo.value.username))
} else Failure((userInfo as? Failure)?.message ?: "Unexpected Error")
try {
val response = kosService.getStudent(username).execute()
result = if (response.body() != null)
Success(response.body()!!)
else {
Failure("Failed while fetching student\n" +
"Message: ${response.message()}")
}
} catch (e: Throwable) {
return Failure("Failed while fetching student\n" +
"Error message: ${e.localizedMessage}")
}
return result
return if (user is Success) {
UserInfo.setStudent(user.value)
Success(Unit)
} else Failure((user as? Failure)?.message ?: "Unexpected Error")
}
suspend fun loadEvents() = refreshEvents(siriusService, eventDao)
......
......@@ -2,6 +2,7 @@ package com.cvut.blackbird.model.services
import com.cvut.blackbird.extensions.base64Encoded
import com.cvut.blackbird.model.entities.AccessToken
import com.cvut.blackbird.model.entities.User
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.*
......@@ -13,6 +14,8 @@ interface AuthService {
const val clientId = "7d7f8a83-b883-4029-b747-d3a99a9da235"
const val clientSecret = "nCx6ggFvQLpfe4zTX61LYrCJ0Eygfa49"
private val accessToken = "Bearer ${AuthInfo.accessToken}"
val auth:String get() = "Basic " + ("$clientId:$clientSecret").base64Encoded()
}
......@@ -20,9 +23,10 @@ interface AuthService {
* @param clientAuth String made of client id and client secret encoded in base64
* string should be "Basic [clientId]:[clientSecret]" and encoded
*/
@FormUrlEncoded
@POST("token")
fun getTokenEx(
fun getToken(
@Field("code") code: String,
@Header("Authorization") clientAuth: String = AuthService.auth,
@Field("redirect_uri") redirectUri: String = "kosapp://callback",
......@@ -31,9 +35,14 @@ interface AuthService {
@FormUrlEncoded
@POST("token")
fun refreshTokenEx(
fun refreshToken(
@Field("refresh_token") refreshToken: String = AuthInfo.refreshToken,
@Header("Authorization") clientAuth: String = AuthService.auth,
@Field("grant_type") grantType: String = "refresh_token"
): Call<AccessToken>
@GET("userinfo")
fun getUserInfo(
@Header("Authorization") token: String = AuthService.accessToken
): Call<User>
}
\ No newline at end of file
......@@ -59,8 +59,4 @@ infix fun <T> Glue.clickToInvoke(receiver: () -> T) = this.apply {
}
inline fun<T, R> Glue.clickToInvoke(crossinline source: () -> T, crossinline receiver: (T) -> R) = this.apply {
actor.setOnClickListener { receiver.invoke(source()) }
}
//TRANSFORM
}
\ No newline at end of file
......@@ -331,6 +331,7 @@ class Wobbly: ScrollView {
.toList()
.filter { it.tag?.toString()?.startsWith(WobblyElement.TAG) ?: false }
var wobblyScrollX
get() = headerScroll.scrollX
set(value) { headerScroll.scrollX = value }
......
......@@ -8,104 +8,43 @@
android:background="@color/colorPrimaryDark"
tools:context=".flows.authentication.AuthFragment">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/usernameInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:hint="CVUT Username"
android:textColorHint="@android:color/white"
android:visibility="invisible"
app:boxBackgroundMode="outline"
app:boxCornerRadiusBottomEnd="5dp"
app:boxCornerRadiusBottomStart="5dp"
app:boxCornerRadiusTopEnd="5dp"
app:boxCornerRadiusTopStart="5dp"
app:boxStrokeColor="@color/colorAccent"
app:boxStrokeWidth="1dp"
app:hintAnimationEnabled="true"
app:hintEnabled="true"
app:layout_constraintBottom_toTopOf="@+id/acceptBtn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/usernameInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPersonName"
android:paddingLeft="10dp"
android:paddingRight="32dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
android:textColor="@android:color/white" />
</com.google.android.material.textfield.TextInputLayout>
<ProgressBar
android:id="@+id/usernameStatus"
style="?android:attr/progressBarStyle"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dp"
android:layout_marginBottom="12dp"
android:layout_weight="1"
android:progressTint="@color/colorAccent"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@+id/usernameInputLayout"
app:layout_constraintEnd_toEndOf="@+id/usernameInputLayout"
app:layout_constraintTop_toTopOf="@+id/usernameInputLayout"
app:layout_constraintVertical_bias="0.476" />
<Button
android:id="@+id/acceptBtn"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:text="Accept"
android:textSize="18sp"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:id="@+id/cvutLogo"
android:tag="authBird"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:tag="authBird"
android:tint="@android:color/white"
app:layout_constraintBottom_toTopOf="@+id/usernameInputLayout"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_raven"/>
app:layout_constraintVertical_bias="0.19"
app:srcCompat="@drawable/ic_raven" />
<TextView
android:id="@+id/infoMessage"
android:tag="authInfo"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="16dp"
android:gravity="bottom|left"
android:textColor="@android:color/darker_gray"
app:layout_constraintBottom_toTopOf="@+id/usernameInputLayout"
android:layout_marginBottom="8dp"
android:fontFamily="sans-serif-condensed"
android:gravity="center"
android:tag="authInfo"
android:textColor="@android:color/white"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@+id/authProgress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cvutLogo"
tools:text="Found user: FOO\nStudying: BAR" />
tools:text="Spying on your every move" />
<ProgressBar
android:id="@+id/authProgress"
android:tag="authProgress"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
......@@ -113,9 +52,11 @@
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:tag="authProgress"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cvutLogo" />
app:layout_constraintTop_toBottomOf="@+id/cvutLogo"
app:layout_constraintVertical_bias="0.75" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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:id="@+id/auth"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimaryDark"
tools:context=".flows.authentication.AuthFragment">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/usernameInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:hint="CVUT Username"
android:textColorHint="@android:color/white"
android:visibility="invisible"
app:boxBackgroundMode="outline"
app:boxCornerRadiusBottomEnd="5dp"
app:boxCornerRadiusBottomStart="5dp"
app:boxCornerRadiusTopEnd="5dp"
app:boxCornerRadiusTopStart="5dp"
app:boxStrokeColor="@color/colorAccent"
app:boxStrokeWidth="1dp"
app:hintAnimationEnabled="true"
app:hintEnabled="true"
app:layout_constraintBottom_toTopOf="@+id/acceptBtn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/usernameInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPersonName"
android:paddingLeft="10dp"
android:paddingRight="32dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
android:textColor="@android:color/white" />
</com.google.android.material.textfield.TextInputLayout>
<ProgressBar
android:id="@+id/usernameStatus"
style="?android:attr/progressBarStyle"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dp"
android:layout_marginBottom="12dp"
android:layout_weight="1"
android:progressTint="@color/colorAccent"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@+id/usernameInputLayout"
app:layout_constraintEnd_toEndOf="@+id/usernameInputLayout"
app:layout_constraintTop_toTopOf="@+id/usernameInputLayout"
app:layout_constraintVertical_bias="0.476" />
<Button
android:id="@+id/acceptBtn"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:text="Accept"
android:textSize="18sp"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:id="@+id/cvutLogo"
android:layout_width="256dp"
android:layout_height="256dp"
android:layout_marginEnd="16dp"