Внедрение аунтификации

This commit is contained in:
2023-04-17 01:24:41 +03:00
parent d04ceb323f
commit 0ebc9f762b
15 changed files with 357 additions and 112 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
package ru.megboyzz.dnevnik package ru.megboyzz.dnevnik
import android.accounts.Account
import android.app.Application import android.app.Application
import androidx.room.Room import androidx.room.Room
import ru.megboyzz.dnevnik.db.CredentialsDataBase import ru.megboyzz.dnevnik.db.CredentialsDataBase
class App : Application() { class App : Application() {
var database: CredentialsDataBase? = null
lateinit var database: CredentialsDataBase
private set private set
override fun onCreate() { override fun onCreate() {
@@ -1,12 +1,17 @@
package ru.megboyzz.dnevnik package ru.megboyzz.dnevnik
import android.app.Application
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Surface import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.ReportFragment.Companion.reportFragment import androidx.lifecycle.ReportFragment.Companion.reportFragment
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -15,37 +20,19 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.jsoup.Jsoup import org.jsoup.Jsoup
import ru.megboyzz.dnevnik.navigation.AppNavHost import ru.megboyzz.dnevnik.navigation.AppNavHost
import ru.megboyzz.dnevnik.ui.theme.lightGray import ru.megboyzz.dnevnik.ui.theme.lightGray
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModelFactory
import java.io.IOException import java.io.IOException
import java.net.URL import java.net.URL
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
lateinit var app: App
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
this.app = application as App
val login = "kitsekaterina"
val password = "k12345"
val returnUrl =
"https://login.dnevnik.ru/oauth2?response_type=token&client_id=bb97b3e445a340b9b9cab4b9ea0dbd6f&scope=CommonInfo,ContactInfo,FriendsAndRelatives,EducationalInfo"
val baseUrl = "https://login.dnevnik.ru/login/"
GlobalScope.launch(Dispatchers.IO) {
val execute: org.jsoup.Connection.Response = Jsoup
.connect(baseUrl)
.method(org.jsoup.Connection.Method.POST)
.data("ReturnUrl", returnUrl)
.data("login", login)
.data("password", password)
.followRedirects(true).execute()
val url: URL = execute.url()
val httpUrl = url.toString().toHttpUrlOrNull()
val queryParameter = httpUrl?.queryParameter("result")
println("$httpUrl $queryParameter")
}
setContent { setContent {
@@ -57,4 +44,19 @@ class MainActivity : ComponentActivity() {
} }
} }
} }
@OptIn(DelicateCoroutinesApi::class)
override fun onDestroy() {
super.onDestroy()
GlobalScope.launch(Dispatchers.IO) {
val credentialsDao = app.database.credentialsDao()
val credentials = credentialsDao.getCredentials()
if(!credentials.rememberMe)
credentialsDao.deleteCredentials()
}
}
} }
+23 -9
View File
@@ -28,10 +28,12 @@ import com.kizitonwose.calendar.compose.CalendarState
import com.kizitonwose.calendar.compose.weekcalendar.WeekCalendarLayoutInfo import com.kizitonwose.calendar.compose.weekcalendar.WeekCalendarLayoutInfo
import com.kizitonwose.calendar.compose.weekcalendar.WeekCalendarState import com.kizitonwose.calendar.compose.weekcalendar.WeekCalendarState
import com.kizitonwose.calendar.core.* import com.kizitonwose.calendar.core.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import ru.megboyzz.dnevnik.navigation.BaseNavRote import ru.megboyzz.dnevnik.navigation.BaseNavRote
import ru.megboyzz.dnevnik.ui.theme.dark import ru.megboyzz.dnevnik.ui.theme.dark
import java.time.DayOfWeek import java.time.DayOfWeek
import kotlin.coroutines.*
@Composable @Composable
fun Int.AsPainter() = painterResource(this) fun Int.AsPainter() = painterResource(this)
@@ -201,16 +203,28 @@ fun CalendarDay.week(): Week{
} }
fun WeekDay.week(): Week{
val weekList = mutableListOf<WeekDay>()
val dayOrder = this.date.dayOfWeek.ordinal
var mondayLocalDate = this.date.minusDays(dayOrder.toLong())
DayOfWeek.values().forEach { private class MutableStateAdapter<T>(
weekList.add(WeekDay(mondayLocalDate, WeekDayPosition.RangeDate)) private val state: State<T>,
mondayLocalDate = mondayLocalDate.plusDays(1) private val mutate: (T) -> Unit
} ) : MutableState<T> {
return Week(weekList)
override var value: T
get() = state.value
set(value) {
mutate(value)
} }
override fun component1(): T = value
override fun component2(): (T) -> Unit = { value = it }
}
// Flow
@Composable
fun <T> MutableStateFlow<T>.collectAsMutableState(
context: CoroutineContext = EmptyCoroutineContext
): MutableState<T> = MutableStateAdapter(
state = collectAsState(context),
mutate = { value = it }
)
@@ -0,0 +1,110 @@
package ru.megboyzz.dnevnik.authorization
import android.util.Log
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.jsoup.Connection
import org.jsoup.Jsoup
import ru.megboyzz.dnevnik.db.dao.CredentialsDao
import ru.megboyzz.dnevnik.entities.db.Credentials
import java.net.URL
class AuthorizationManager(private val credentialsDao: CredentialsDao) {
private val returnUrl =
"https://login.dnevnik.ru/oauth2?response_type=token&client_id=bb97b3e445a340b9b9cab4b9ea0dbd6f&scope=CommonInfo,ContactInfo,FriendsAndRelatives,EducationalInfo"
private val baseUrl = "https://login.dnevnik.ru/login/"
private var credentials = Credentials(token = "", cookies = mapOf(), rememberMe = false)
fun login(
login: String,
password: String,
rememberMe: Boolean
): AuthorizationStatus {
val execute = runCatching<Connection.Response> {
Jsoup
.connect(baseUrl)
.method(Connection.Method.POST)
.data("ReturnUrl", returnUrl)
.data("login", login)
.data("password", password)
.followRedirects(true)
.execute()
}.onFailure {
Log.i("AuthorizationManager", "No internet")
return AuthorizationStatus.NO_INTERNET
}
val response = execute.getOrNull()
if(response != null){
if(response.statusCode() != 200) {
Log.i("AuthorizationManager", "Maintain")
return AuthorizationStatus.MAINTAIN
}
val url = response.url()
if(url.getResult() != "success") {
Log.i("AuthorizationManager", "Wrong login")
return AuthorizationStatus.WRONG_CREDENTIALS
}
//Безопасная зона
val cookies = response.cookies()
credentials = Credentials(
token = url.getToken(),
cookies = cookies,
rememberMe = rememberMe
)
dbUpdate()
}else return AuthorizationStatus.NO_INTERNET //Может быть опасно
return AuthorizationStatus.LOGGED
}
fun checkCredentials(): AuthorizationStatus{
val credentialsIsExists = credentialsDao.credentialsIsExists()
return if(credentialsIsExists)
AuthorizationStatus.LOGGED
else
AuthorizationStatus.NOTHING
}
fun dbUpdate(){
credentialsDao.setCredentials(credentials)
}
fun deleteCredentials(){
credentialsDao.deleteCredentials()
}
fun URL.getResult(): String{
val httpUrl = this.toString().toHttpUrlOrNull() ?: return ""
return httpUrl.queryParameter("result") ?: ""
}
fun URL.getToken(): String{
val tokenRegex = "access_token=([A-Za-z0-9]+)"
val regex = Regex(tokenRegex)
val find = regex.find(this.toString())
if(find != null)
return find.groupValues[1]
return ""
}
}
@@ -0,0 +1,12 @@
package ru.megboyzz.dnevnik.authorization
enum class AuthorizationStatus {
LOGGED,
LOGGING,
WRONG_CREDENTIALS,
NO_INTERNET,
MAINTAIN,
NOTHING
}
@@ -0,0 +1,18 @@
package ru.megboyzz.dnevnik.db.converters
import androidx.room.TypeConverter
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
class CookiesConverter {
private val gson = Gson()
private val mapType = object : TypeToken<Map<String, String>>() {}.type
@TypeConverter
fun fromMap(cookies: Map<String, String>): String = gson.toJson(cookies)
@TypeConverter
fun toMap(savedCookies: String): Map<String, String> = gson.fromJson(savedCookies, mapType)
}
@@ -1,17 +1,22 @@
package ru.megboyzz.dnevnik.db.dao package ru.megboyzz.dnevnik.db.dao
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import ru.megboyzz.dnevnik.entities.db.Credentials import ru.megboyzz.dnevnik.entities.db.Credentials
@Dao
interface CredentialsDao { interface CredentialsDao {
@Query("SELECT * FROM Credentials WHERE id = 1")
fun getCredentials(): LiveData<Credentials>
@Query("DELETE FROM Credentials WHERE id = 1") @Query("SELECT EXISTS(SELECT * FROM Credentials WHERE id = 0)")
fun credentialsIsExists(): Boolean
@Query("SELECT * FROM Credentials WHERE id = 0")
fun getCredentials(): Credentials
@Query("DELETE FROM Credentials WHERE id = 0")
fun deleteCredentials() fun deleteCredentials()
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
@@ -1,9 +1,16 @@
package ru.megboyzz.dnevnik.entities.db package ru.megboyzz.dnevnik.entities.db
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import ru.megboyzz.dnevnik.db.converters.CookiesConverter
@Entity @Entity
@TypeConverters(CookiesConverter::class)
data class Credentials( data class Credentials(
@PrimaryKey
val id: Int = 0,
val token: String, val token: String,
val cookies: Map<String, String> val cookies: Map<String, String>,
val rememberMe: Boolean
) )
@@ -1,20 +1,23 @@
package ru.megboyzz.dnevnik.navigation package ru.megboyzz.dnevnik.navigation
import android.window.SplashScreen
import androidx.compose.animation.*
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.material.rememberScaffoldState import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.IntOffset
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import ru.megboyzz.dnevnik.MainActivity
import ru.megboyzz.dnevnik.authorization.AuthorizationStatus
import ru.megboyzz.dnevnik.collectAsMutableState
import ru.megboyzz.dnevnik.screens.* import ru.megboyzz.dnevnik.screens.*
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModelFactory
sealed class BaseNavRote(open val route: String) sealed class BaseNavRote(open val route: String)
@@ -29,18 +32,33 @@ sealed class AppNavRoute(override val route: String): BaseNavRote(route){
object Settings: AppNavRoute("settings_screen") object Settings: AppNavRoute("settings_screen")
} }
@OptIn(ExperimentalAnimationApi::class)
@Composable @Composable
fun AppNavHost(){ fun AppNavHost(){
val scaffoldState = rememberScaffoldState() val scaffoldState = rememberScaffoldState()
val navController = rememberNavController() val navController = rememberNavController()
val app = (LocalContext.current as MainActivity).app
val authorizationViewModel: AuthorizationViewModel = viewModel(
factory = AuthorizationViewModelFactory(app)
)
val loginStatus = authorizationViewModel.loginStatus.collectAsMutableState()
NavHost( NavHost(
navController = navController, navController = navController,
startDestination = AppNavRoute.Login.route /* сделать проверку куки файлов */ startDestination = AppNavRoute.Splash.route
){ ){
composable(AppNavRoute.Splash.route) { } composable(AppNavRoute.Splash.route) {
if(loginStatus.value != AuthorizationStatus.LOGGED){
navController.navigate(AppNavRoute.Login.route)
}else
navController.navigate(AppNavRoute.Marks.route)
}
composable(AppNavRoute.Login.route) { LoginScreen(navController) } composable(AppNavRoute.Login.route) { LoginScreen(navController) }
composable(AppNavRoute.Marks.route) { MarksScreen(navController, scaffoldState) } composable(AppNavRoute.Marks.route) { MarksScreen(navController, scaffoldState) }
composable(AppNavRoute.Schedule.route) { ScheduleScreen(navController, scaffoldState) } composable(AppNavRoute.Schedule.route) { ScheduleScreen(navController, scaffoldState) }
@@ -1,4 +0,0 @@
package ru.megboyzz.dnevnik.repository
class AuthorizationRepository() {
}
@@ -1,35 +1,70 @@
package ru.megboyzz.dnevnik.screens package ru.megboyzz.dnevnik.screens
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.platform.LocalContext
import androidx.compose.runtime.remember import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import ru.megboyzz.dnevnik.MainActivity
import ru.megboyzz.dnevnik.authorization.AuthorizationStatus
import ru.megboyzz.dnevnik.collectAsMutableState
import ru.megboyzz.dnevnik.navigation.AppNavRoute import ru.megboyzz.dnevnik.navigation.AppNavRoute
import ru.megboyzz.dnevnik.screens.ui.AlertMessageBox
import ru.megboyzz.dnevnik.screens.ui.LoginScreenContent import ru.megboyzz.dnevnik.screens.ui.LoginScreenContent
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModelFactory
//TODO Переверстать экран входа //TODO Переверстать экран входа
@Composable @Composable
fun LoginScreen(navController: NavController) { fun LoginScreen(navController: NavController) {
val login = remember { val app = (LocalContext.current as MainActivity).app
mutableStateOf("")
} val authorizationViewModel: AuthorizationViewModel = viewModel(
val password = remember { factory = AuthorizationViewModelFactory(app)
mutableStateOf("") )
}
val isError = remember { val login = authorizationViewModel.login.collectAsMutableState()
mutableStateOf(false)
} val password = authorizationViewModel.password.collectAsMutableState()
val rememberMe = remember {
mutableStateOf(false) val loginStatus = authorizationViewModel.loginStatus.collectAsMutableState()
val rememberMe = authorizationViewModel.rememberMe.collectAsMutableState()
var isError by remember{
mutableStateOf(true)
} }
if(loginStatus.value == AuthorizationStatus.WRONG_CREDENTIALS && isError) {
AlertMessageBox(
title = "Информация",
alertText = "Неверный логин или пароль",
onClick = { isError =! isError }
)
}
if(loginStatus.value == AuthorizationStatus.LOGGED)
navController.navigate(AppNavRoute.Marks.route)
LoginScreenContent( LoginScreenContent(
login = login, login = login.value,
password = password, onLoginChange = { login.value = it },
isError = isError,
isRememberMe = rememberMe, password = password.value,
onSignIn = { navController.navigate(AppNavRoute.Marks.route) }, onPasswordChange = { password.value = it },
isError = loginStatus.value == AuthorizationStatus.WRONG_CREDENTIALS,
onErrorChange = { },
isLoading = loginStatus.value == AuthorizationStatus.LOGGING,
isRememberMe = rememberMe.value,
onRememberChange = { rememberMe.value = it },
onSignIn = {
if(loginStatus.value != AuthorizationStatus.LOGGED)
authorizationViewModel.login()
},
onSignUp = { /*TODO*/ }, onSignUp = { /*TODO*/ },
onForgetPassword = { /* TODO */ } onForgetPassword = { /* TODO */ }
) )
@@ -33,7 +33,6 @@ import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.* import androidx.compose.ui.unit.*
import androidx.compose.ui.window.DialogProperties
import ru.megboyzz.dnevnik.* import ru.megboyzz.dnevnik.*
import ru.megboyzz.dnevnik.R import ru.megboyzz.dnevnik.R
import ru.megboyzz.dnevnik.ui.theme.* import ru.megboyzz.dnevnik.ui.theme.*
@@ -102,18 +101,20 @@ fun ProfileCard(
@Composable @Composable
fun MainTextField( fun MainTextField(
value: MutableState<String>, value: String,
isError: MutableState<Boolean> = remember { mutableStateOf(false) }, onChange: (String) -> Unit,
isError: Boolean,
isLoading: Boolean = false,
onErrorChanged: (Boolean) -> Unit,
label: String = "", label: String = "",
keyboardType: KeyboardType = KeyboardType.Text, keyboardType: KeyboardType = KeyboardType.Text,
onChange: (it: String) -> Unit = { value.value = it }
) { ) {
var passwordVisible by rememberSaveable { mutableStateOf(false) } var passwordVisible by rememberSaveable { mutableStateOf(false) }
val focusRequester = FocusRequester() val focusRequester = FocusRequester()
OutlinedTextField( OutlinedTextField(
value = value.value, value = value,
onValueChange = onChange, onValueChange = onChange,
label = { label = {
Text( Text(
@@ -126,8 +127,8 @@ fun MainTextField(
textStyle = H2, textStyle = H2,
colors = TextFieldDefaults.outlinedTextFieldColors( colors = TextFieldDefaults.outlinedTextFieldColors(
textColor = dark, textColor = dark,
unfocusedBorderColor = if (isError.value) falseColor else mainBlue, unfocusedBorderColor = if (isError) falseColor else mainBlue,
focusedBorderColor = if (isError.value) falseColor else mainBlue, focusedBorderColor = if (isError) falseColor else mainBlue,
focusedLabelColor = dark, focusedLabelColor = dark,
unfocusedLabelColor = dark, unfocusedLabelColor = dark,
cursorColor = dark cursorColor = dark
@@ -152,42 +153,50 @@ fun MainTextField(
IconButton(onClick = { passwordVisible = !passwordVisible }) { IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(imageVector = image, "") Icon(imageVector = image, "")
} }
} else {
if(isLoading) LoginProgress()
} }
}, },
modifier = Modifier modifier = Modifier
.focusRequester(focusRequester) .focusRequester(focusRequester)
.onFocusChanged { .onFocusChanged {
if (it.isFocused) if (it.isFocused)
if (isError.value) isError.value = false if (isError) onErrorChanged(false)
} }
.fillMaxWidth(), .fillMaxWidth(),
) )
} }
@Composable
fun LoginProgress() {
CircularProgressIndicator(
modifier = Modifier.size(20.dp),
strokeWidth = 2.dp,
color = mainBlue
)
}
@Composable @Composable
fun MainCheckbox( fun MainCheckbox(
isChecked: MutableState<Boolean>, isChecked: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
size: Float = 24f, size: Float = 24f,
checkedColor: Color = mainBlue, checkedColor: Color = mainBlue,
uncheckedColor: Color = lightGray, uncheckedColor: Color = lightGray,
checkmarkColor: Color = white, checkmarkColor: Color = white,
onValueChange: () -> Unit onValueChange: (Boolean) -> Unit
) { ) {
val checkboxColor: Color by animateColorAsState(if (isChecked.value) checkedColor else uncheckedColor) val checkboxColor: Color by animateColorAsState(if (isChecked) checkedColor else uncheckedColor)
val density = LocalDensity.current val density = LocalDensity.current
val duration = 200 val duration = 200
Row( Row(
modifier = modifier modifier = modifier
.toggleable( .toggleable(
value = isChecked.value, value = isChecked,
role = Role.Checkbox, role = Role.Checkbox,
onValueChange = { onValueChange = onValueChange
isChecked.value = !isChecked.value
onValueChange.invoke()
}
) )
) { ) {
Card( Card(
@@ -202,7 +211,7 @@ fun MainCheckbox(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
androidx.compose.animation.AnimatedVisibility( androidx.compose.animation.AnimatedVisibility(
visible = isChecked.value, visible = isChecked,
enter = slideInHorizontally( enter = slideInHorizontally(
animationSpec = tween(duration) animationSpec = tween(duration)
) { ) {
@@ -618,7 +627,8 @@ fun AlertMessageBox(
contentColor = white contentColor = white
), ),
modifier = Modifier modifier = Modifier
.defaultMinSize(170.dp).padding(bottom = 30.dp) .defaultMinSize(170.dp)
.padding(bottom = 30.dp)
) { ) {
Text( Text(
text = R.string.title_ok.AsString(), text = R.string.title_ok.AsString(),
@@ -22,10 +22,15 @@ import ru.megboyzz.dnevnik.R
@Composable @Composable
fun LoginScreenContent( fun LoginScreenContent(
login: MutableState<String>, login: String,
password: MutableState<String>, onLoginChange: (String) -> Unit,
isError: MutableState<Boolean>, password: String,
isRememberMe: MutableState<Boolean>, onPasswordChange: (String) -> Unit,
isLoading: Boolean,
isError: Boolean,
onErrorChange: (Boolean) -> Unit,
isRememberMe: Boolean,
onRememberChange: (Boolean) -> Unit,
onSignIn: () -> Unit, onSignIn: () -> Unit,
onSignUp: () -> Unit, onSignUp: () -> Unit,
onForgetPassword: () -> Unit onForgetPassword: () -> Unit
@@ -65,14 +70,19 @@ fun LoginScreenContent(
MainTextField( MainTextField(
value = login, value = login,
label = R.string.title_login.AsString(), label = R.string.title_login.AsString(),
isError = isError isError = isError,
isLoading = isLoading,
onErrorChanged = onErrorChange,
onChange = onLoginChange
) )
SpacerHeight(15.dp) SpacerHeight(15.dp)
MainTextField( MainTextField(
value = password, value = password,
keyboardType = KeyboardType.Password, keyboardType = KeyboardType.Password,
label = R.string.title_password.AsString(), label = R.string.title_password.AsString(),
isError = isError isError = isError,
onErrorChanged = onErrorChange,
onChange = onPasswordChange
) )
} }
SpacerHeight(5.dp) SpacerHeight(5.dp)
@@ -87,7 +97,10 @@ fun LoginScreenContent(
modifier = Modifier.fillMaxHeight(), modifier = Modifier.fillMaxHeight(),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
MainCheckbox(isRememberMe) { } MainCheckbox(
isChecked = isRememberMe,
onValueChange = onRememberChange
)
SpacerWidth(5.dp) SpacerWidth(5.dp)
Text( Text(
text = R.string.title_remember_me.AsString(), text = R.string.title_remember_me.AsString(),
@@ -1,7 +0,0 @@
package ru.megboyzz.dnevnik.service
enum class AuthorizationStatus {
WRONG_CREDENTIALS,
NO_INTERNET,
MAINTAIN
}
@@ -1,36 +1,48 @@
package ru.megboyzz.dnevnik.viewmodel package ru.megboyzz.dnevnik.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import ru.megboyzz.dnevnik.App
import ru.megboyzz.dnevnik.authorization.AuthorizationManager
import ru.megboyzz.dnevnik.authorization.AuthorizationStatus
import java.lang.IllegalArgumentException import java.lang.IllegalArgumentException
//Если есть попадание в этот вьюмодел то нам нужна авторизация //Если есть попадание в этот вьюмодел то нам нужна авторизация
class AuthorizationViewModel(application: Application) : AndroidViewModel(application) { class AuthorizationViewModel(application: App) : AndroidViewModel(application) {
val login = MutableStateFlow("") val login = MutableStateFlow("")
val password = MutableStateFlow("") val password = MutableStateFlow("")
val isLogged = MutableStateFlow(false) val rememberMe = MutableStateFlow(false)
val loginStatus = MutableStateFlow(AuthorizationStatus.NOTHING)
private val authManager = AuthorizationManager(application.database.credentialsDao())
init { init {
viewModelScope.launch(Dispatchers.IO) {
loginStatus.emit(authManager.checkCredentials())
}
} }
fun login(){ fun login(){
viewModelScope.launch(Dispatchers.IO) {
loginStatus.emit(AuthorizationStatus.LOGGING)
loginStatus.emit(authManager.login(login.value, password.value, rememberMe.value))
}
} }
} }
class AuthorizationViewModelFactory(private val application: Application) : ViewModelProvider.Factory { class AuthorizationViewModelFactory(private val application: App) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(AuthorizationViewModel::class.java)) if (modelClass.isAssignableFrom(AuthorizationViewModel::class.java))