Внедрение аунтификации
This commit is contained in:
@@ -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()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
Reference in New Issue
Block a user