Стабильная аунтификация

This commit is contained in:
2023-04-18 01:07:53 +03:00
parent 0ebc9f762b
commit 4029e93768
37 changed files with 528 additions and 97 deletions
+3 -3
View File
@@ -2,18 +2,18 @@ package ru.megboyzz.dnevnik
import android.app.Application
import androidx.room.Room
import ru.megboyzz.dnevnik.db.CredentialsDataBase
import ru.megboyzz.dnevnik.db.AppDataBase
class App : Application() {
lateinit var database: CredentialsDataBase
lateinit var database: AppDataBase
private set
override fun onCreate() {
super.onCreate()
instance = this
database = Room.databaseBuilder(this, CredentialsDataBase::class.java, "database")
database = Room.databaseBuilder(this, AppDataBase::class.java, "database")
.build()
}
@@ -4,6 +4,7 @@ import android.app.Application
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
@@ -52,10 +53,12 @@ class MainActivity : ComponentActivity() {
GlobalScope.launch(Dispatchers.IO) {
val credentialsDao = app.database.credentialsDao()
if(credentialsDao.credentialsIsExists()) {
val credentials = credentialsDao.getCredentials()
if (!credentials.rememberMe)
credentialsDao.deleteCredentials()
}
}
}
@@ -1,30 +1,54 @@
package ru.megboyzz.dnevnik.authorization
import android.util.Log
import com.google.gson.Gson
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.jsoup.Connection
import org.jsoup.Jsoup
import ru.megboyzz.dnevnik.db.dao.CredentialsDao
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import ru.megboyzz.dnevnik.db.AppDataBase
import ru.megboyzz.dnevnik.entities.context.marks.init.state.MarksContext
import ru.megboyzz.dnevnik.entities.context.marks.init.state.SchoolType
import ru.megboyzz.dnevnik.entities.db.Credentials
import ru.megboyzz.dnevnik.entities.db.GlobalUserContext
import ru.megboyzz.dnevnik.service.ContextService
import java.net.URL
class AuthorizationManager(private val credentialsDao: CredentialsDao) {
class AuthorizationManager(dataBase: AppDataBase) {
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 val apiUrl = "https://api.dnevnik.ru"
private val retrofit = Retrofit
.Builder()
.baseUrl(apiUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
private val contextService = retrofit.create(ContextService::class.java)
private val marksState = "https://dnevnik.ru/marks"
private val credentialsDao = dataBase.credentialsDao()
private val userContextDao = dataBase.globalUserContextDao()
private var credentials = Credentials(token = "", cookies = mapOf(), rememberMe = false)
private val gson = Gson()
fun login(
login: String,
password: String,
rememberMe: Boolean
): AuthorizationStatus {
val execute = runCatching<Connection.Response> {
runCatching<Connection.Response> {
Jsoup
.connect(baseUrl)
.method(Connection.Method.POST)
@@ -34,26 +58,15 @@ class AuthorizationManager(private val credentialsDao: CredentialsDao) {
.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")
}.onSuccess { response ->
if(response.statusCode() != 200)
return AuthorizationStatus.MAINTAIN
}
val url = response.url()
if(url.getResult() != "success") {
Log.i("AuthorizationManager", "Wrong login")
if(url.getResult() != "success")
return AuthorizationStatus.WRONG_CREDENTIALS
}
//Безопасная зона
val cookies = response.cookies()
@@ -64,7 +77,39 @@ class AuthorizationManager(private val credentialsDao: CredentialsDao) {
)
dbUpdate()
}else return AuthorizationStatus.NO_INTERNET //Может быть опасно
kotlin.runCatching {
Jsoup
.connect(marksState)
.method(Connection.Method.GET)
.cookies(cookies)
.ignoreContentType(true)
.followRedirects(true)
.execute()
}.onFailure {
return AuthorizationStatus.NO_INTERNET
}.onSuccess { response1 ->
val htmlByLines = response1.body().split("\n")
val contextLine = htmlByLines.find { it.contains("window.__MARKS__INITIAL__STATE__") }
val marksContextStr =
contextLine?.substring(contextLine.indexOf("{"), contextLine.length - 2)
val marksContext = gson.fromJson(marksContextStr, MarksContext::class.java)
val context = contextService.getContext(credentials.token, marksContext.context.userId).execute()
if(context.isSuccessful){
val body = context.body() ?: return AuthorizationStatus.UNKNOWN_ERROR
userContextDao.setGlobalUserContext(GlobalUserContext(
apiUserContext = body,
marksInitStateUserContext = marksContext
))
} else return AuthorizationStatus.UNKNOWN_ERROR
}
}
return AuthorizationStatus.LOGGED
@@ -7,6 +7,8 @@ enum class AuthorizationStatus {
WRONG_CREDENTIALS,
NO_INTERNET,
MAINTAIN,
NOTHING
CREDENTIALS_LOADING,
NOTHING,
UNKNOWN_ERROR
}
@@ -0,0 +1,19 @@
package ru.megboyzz.dnevnik.db
import androidx.room.Database
import androidx.room.RoomDatabase
import ru.megboyzz.dnevnik.db.dao.CredentialsDao
import ru.megboyzz.dnevnik.db.dao.GlobalUserContextDao
import ru.megboyzz.dnevnik.entities.db.Credentials
import ru.megboyzz.dnevnik.entities.db.GlobalUserContext
@Database(
entities = [Credentials::class, GlobalUserContext::class],
version = 1
)
abstract class AppDataBase: RoomDatabase() {
abstract fun credentialsDao(): CredentialsDao
abstract fun globalUserContextDao(): GlobalUserContextDao
}
@@ -1,16 +0,0 @@
package ru.megboyzz.dnevnik.db
import androidx.room.Database
import androidx.room.RoomDatabase
import ru.megboyzz.dnevnik.db.dao.CredentialsDao
import ru.megboyzz.dnevnik.entities.db.Credentials
@Database(
entities = [Credentials::class],
version = 1
)
abstract class CredentialsDataBase: RoomDatabase() {
abstract fun credentialsDao(): CredentialsDao
}
@@ -0,0 +1,24 @@
package ru.megboyzz.dnevnik.db.converters
import androidx.room.TypeConverter
import com.google.gson.Gson
import ru.megboyzz.dnevnik.entities.context.marks.init.state.MarksContext
import ru.megboyzz.dnevnik.entities.response.UserContext
class DataStructureConverter {
val gson = Gson()
@TypeConverter
fun fromUserContext(userContext: UserContext): String = gson.toJson(userContext)
@TypeConverter
fun toUserContext(savedUserContext: String): UserContext = gson.fromJson(savedUserContext, UserContext::class.java)
@TypeConverter
fun fromMarksContext(marksContext: MarksContext): String = gson.toJson(marksContext)
@TypeConverter
fun toMarksContext(savedMarksContext: String): MarksContext = gson.fromJson(savedMarksContext, MarksContext::class.java)
}
@@ -10,7 +10,6 @@ import ru.megboyzz.dnevnik.entities.db.Credentials
@Dao
interface CredentialsDao {
@Query("SELECT EXISTS(SELECT * FROM Credentials WHERE id = 0)")
fun credentialsIsExists(): Boolean
@Query("SELECT * FROM Credentials WHERE id = 0")
@@ -0,0 +1,24 @@
package ru.megboyzz.dnevnik.db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import ru.megboyzz.dnevnik.entities.db.GlobalUserContext
@Dao
interface GlobalUserContextDao {
@Query("SELECT EXISTS(SELECT * FROM GlobalUserContext WHERE id = 0)")
fun globalUserContextIsExists(): Boolean
@Query("SELECT * FROM GlobalUserContext WHERE id = 0")
fun getGlobalUserContext(): GlobalUserContext
@Query("DELETE FROM GlobalUserContext WHERE id = 0")
fun deleteGlobalUserContext()
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun setGlobalUserContext(profile: GlobalUserContext)
}
@@ -0,0 +1,5 @@
package ru.megboyzz.dnevnik.entities.context
enum class GroupType {
Quarter
}
@@ -2,6 +2,6 @@ package ru.megboyzz.dnevnik.entities.context
data class PeriodGroup(
val id: Long,
val type: String, //Обычно равен "Quarter", то есть Четверть, но как будет семестр у них????
val type: GroupType, //Обычно равен "Quarter", то есть Четверть, но как будет семестр у них????
val periods: List<PeriodInfo>
)
@@ -3,7 +3,7 @@ package ru.megboyzz.dnevnik.entities.context
data class PeriodInfo(
val id: Long,
val number: Short,
val type: String,
val type: GroupType,
val dateStart: Long,
val dateFinish: Long,
val studyYear: Short,
@@ -8,7 +8,7 @@ data class PersonContext(
val middleName: String,
val lastName: String,
val avatarUrl: String,
val schoolInfo: SchoolInfo,
val school: SchoolInfo,
val group: GroupInfo,
val reportingPeriodGroup: PeriodGroup
)
@@ -3,5 +3,6 @@ package ru.megboyzz.dnevnik.entities.context
data class SchoolInfo(
val id: Long,
val name: String,
val avatarUrl: String
val avatarUrl: String,
val type: SchoolType
)
@@ -0,0 +1,6 @@
package ru.megboyzz.dnevnik.entities.context
enum class SchoolType {
Additional,
Regular
}
@@ -0,0 +1,8 @@
package ru.megboyzz.dnevnik.entities.context.marks.init.state
data class Group(
val groupId: String,
val groupName: String,
val reportingPeriodNotSet: Boolean,
val periodTabName: String
)
@@ -0,0 +1,9 @@
package ru.megboyzz.dnevnik.entities.context.marks.init.state
data class GroupHistorical(
val id: String,
val name: String,
val studyYear: Int,
val periods: List<Period>,
val periodTabName: String
)
@@ -0,0 +1,6 @@
package ru.megboyzz.dnevnik.entities.context.marks.init.state
data class MarksContext(
val apiUrl: String,
val context: UserContext
)
@@ -0,0 +1,9 @@
package ru.megboyzz.dnevnik.entities.context.marks.init.state
data class Period(
val id: String,
val name: String,
val shortName: String,
val number: Int,
val isDefault: Boolean
)
@@ -0,0 +1,17 @@
package ru.megboyzz.dnevnik.entities.context.marks.init.state
data class SchoolMembership(
val personId: String,
val firstName: String,
val lastName: String,
val avatarUrl: String,
val schoolId: String,
val schoolName: String,
val isOdo: Boolean,
val isTermReportEnabled: Boolean,
val studyYear: Int,
val schoolType: SchoolType,
val groups: List<Group>,
val holidays: List<String>,
val groupHistory: List<GroupHistorical>
)
@@ -0,0 +1,8 @@
package ru.megboyzz.dnevnik.entities.context.marks.init.state
enum class SchoolType {
Regular,
Additional
}
@@ -0,0 +1,8 @@
package ru.megboyzz.dnevnik.entities.context.marks.init.state
data class UserContext(
val userId: String,
val role: String,
val schoolMemberships: List<SchoolMembership>,
)
@@ -0,0 +1,17 @@
package ru.megboyzz.dnevnik.entities.db
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import ru.megboyzz.dnevnik.db.converters.DataStructureConverter
import ru.megboyzz.dnevnik.entities.context.marks.init.state.MarksContext
import ru.megboyzz.dnevnik.entities.response.UserContext
@Entity
@TypeConverters(DataStructureConverter::class)
data class GlobalUserContext(
@PrimaryKey
val id: Int = 0,
val apiUserContext: UserContext, // Обычный контекст из API
val marksInitStateUserContext: MarksContext //window.__MARKS__INITIAL__STATE__
)
@@ -3,6 +3,7 @@ package ru.megboyzz.dnevnik.entities.response
import ru.megboyzz.dnevnik.entities.context.PersonContext
import ru.megboyzz.dnevnik.entities.context.UserInfo
data class UserContext(
val info: UserInfo,
val contextPersons: List<PersonContext>
@@ -1,5 +1,6 @@
package ru.megboyzz.dnevnik.navigation
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -18,6 +19,7 @@ import ru.megboyzz.dnevnik.collectAsMutableState
import ru.megboyzz.dnevnik.screens.*
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModelFactory
import ru.megboyzz.dnevnik.navigate
sealed class BaseNavRote(open val route: String)
@@ -52,11 +54,18 @@ fun AppNavHost(){
){
composable(AppNavRoute.Splash.route) {
if(loginStatus.value != AuthorizationStatus.LOGGED){
navController.navigate(AppNavRoute.Login.route)
}else
navController.navigate(AppNavRoute.Marks.route)
when(loginStatus.value){
AuthorizationStatus.CREDENTIALS_LOADING -> { CircularProgressIndicator() }
AuthorizationStatus.LOGGED -> {
navController.navigate(AppNavRoute.Marks)
}
AuthorizationStatus.NOTHING -> {
navController.navigate(AppNavRoute.Login)
}
else -> {}
}
}
composable(AppNavRoute.Login.route) { LoginScreen(navController) }
@@ -7,6 +7,7 @@ import androidx.navigation.NavController
import ru.megboyzz.dnevnik.MainActivity
import ru.megboyzz.dnevnik.authorization.AuthorizationStatus
import ru.megboyzz.dnevnik.collectAsMutableState
import ru.megboyzz.dnevnik.navigate
import ru.megboyzz.dnevnik.navigation.AppNavRoute
import ru.megboyzz.dnevnik.screens.ui.AlertMessageBox
import ru.megboyzz.dnevnik.screens.ui.LoginScreenContent
@@ -35,6 +36,12 @@ fun LoginScreen(navController: NavController) {
mutableStateOf(true)
}
var isLoading by remember {
mutableStateOf(false)
}
if(loginStatus.value == AuthorizationStatus.LOGGING) isLoading = true
if(loginStatus.value == AuthorizationStatus.WRONG_CREDENTIALS && isError) {
AlertMessageBox(
title = "Информация",
@@ -44,7 +51,7 @@ fun LoginScreen(navController: NavController) {
}
if(loginStatus.value == AuthorizationStatus.LOGGED)
navController.navigate(AppNavRoute.Marks.route)
navController.navigate(AppNavRoute.Marks)
LoginScreenContent(
login = login.value,
@@ -56,7 +63,7 @@ fun LoginScreen(navController: NavController) {
isError = loginStatus.value == AuthorizationStatus.WRONG_CREDENTIALS,
onErrorChange = { },
isLoading = loginStatus.value == AuthorizationStatus.LOGGING,
isLoading = isLoading,
isRememberMe = rememberMe.value,
onRememberChange = { rememberMe.value = it },
@@ -1,5 +1,6 @@
package ru.megboyzz.dnevnik.screens.ui
import android.app.AlertDialog
import androidx.compose.animation.*
import androidx.compose.animation.core.tween
import androidx.compose.foundation.*
@@ -8,6 +9,7 @@ import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
@@ -20,11 +22,13 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.input.KeyboardType
@@ -33,9 +37,11 @@ import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.*
import coil.compose.AsyncImage
import ru.megboyzz.dnevnik.*
import ru.megboyzz.dnevnik.R
import ru.megboyzz.dnevnik.ui.theme.*
import ru.megboyzz.dnevnik.viewmodel.model.UserProfileModel
//Рефакторинг компонентов: разбиение файла с компонентами на отдельные файлы
@@ -73,25 +79,29 @@ fun AlmostOutlinedText(
@Composable
fun ProfileCard(
textColor: Color = white,
painter: Painter
userProfileModel: UserProfileModel
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
Image(
modifier = Modifier.padding(10.dp, 15.dp, 20.dp, 15.dp),
painter = painter, //TODO заменить динмической картинкой
contentDescription = "chel"
AsyncImage(
modifier = Modifier
.padding(10.dp, 15.dp, 20.dp, 15.dp)
.clip(RoundedCornerShape(25.dp))
.size(50.dp),
contentScale = ContentScale.Crop,
model = userProfileModel.avatarUrl,
contentDescription = ""
)
Column(verticalArrangement = Arrangement.spacedBy(5.dp)) {
Text(
text = "Имя и фамилия",
text = userProfileModel.toString(),
style = H1,
color = textColor
)
Text(
text = "Класс/Группа",
text = userProfileModel.groupName,
style = H1,
color = textColor
)
@@ -640,3 +650,65 @@ fun AlertMessageBox(
)
}
@Composable
fun LeaveAlert(
isLoading: Boolean = false,
onAgree: () -> Unit,
onCancel: () -> Unit,
){
BaseAlertBox(
title = R.string.tile_leave.AsString(),
onDismissRequest = onCancel,
content = {
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
Text(
text = R.string.text_leave.AsString(),
style = H2,
color = dark
)
if(isLoading) CircularProgressIndicator()
}
},
buttons = {
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth()
) {
AlertButton(
title = R.string.title_cancel.AsString(),
onClick = onCancel
)
SpacerWidth(width = 10.dp)
AlertButton(
title = R.string.title_button_leave.AsString(),
onClick = onAgree
)
}
}
)
}
@Composable
fun AlertButton(
title: String,
onClick: () -> Unit
) {
Button(
onClick = onClick,
shape = RoundedCornerShape(8.dp),
colors = ButtonDefaults.buttonColors(
backgroundColor = mainBlue,
contentColor = white
),
modifier = Modifier
.padding(bottom = 30.dp)
){
Text(
text = title,
style = H1
)
}
}
@@ -393,3 +393,12 @@ fun Test1() {
)
}
@Preview
@Composable
fun AlertTest() {
LeaveAlert(onAgree = { /*TODO*/ }) {
}
}
@@ -5,12 +5,8 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Divider
import androidx.compose.material.ScaffoldState
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -20,24 +16,34 @@ import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.RoundRect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.imageResource
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import ru.megboyzz.dnevnik.*
import ru.megboyzz.dnevnik.R
import ru.megboyzz.dnevnik.navigation.AppNavRoute
import ru.megboyzz.dnevnik.screens.ui.AlmostOutlinedText
import ru.megboyzz.dnevnik.screens.ui.LeaveAlert
import ru.megboyzz.dnevnik.screens.ui.ProfileCard
import ru.megboyzz.dnevnik.ui.theme.H2
import ru.megboyzz.dnevnik.ui.theme.Shapes
import ru.megboyzz.dnevnik.ui.theme.mainBlue
import ru.megboyzz.dnevnik.ui.theme.white
import ru.megboyzz.dnevnik.viewmodel.ProfileViewModel
import ru.megboyzz.dnevnik.viewmodel.ProfileViewModelFactory
import ru.megboyzz.dnevnik.viewmodel.model.UserProfileModel
@Composable
@@ -77,7 +83,43 @@ fun DrawerContent(
scaffoldState: ScaffoldState
) {
val app = (LocalContext.current as MainActivity).app
val profileViewModel: ProfileViewModel = viewModel(
factory = ProfileViewModelFactory(app)
)
val profile = profileViewModel.profile.collectAsState()
val dao = app.database.credentialsDao()
var leaveAlertIsOpened by remember {
mutableStateOf(false)
}
var isLeavingProcess by remember {
mutableStateOf(false)
}
val scope = rememberCoroutineScope()
if(leaveAlertIsOpened){
LeaveAlert(
isLoading = isLeavingProcess,
onAgree = {
scope.launch(Dispatchers.IO) {
isLeavingProcess = true
dao.deleteCredentials()
leaveAlertIsOpened = false
this.launch(Dispatchers.Main) { navController.navigate(AppNavRoute.Login) }
scaffoldState.drawerState.close()
}
},
onCancel = { leaveAlertIsOpened = false }
)
}
NiceContainerForDrawer {
Column(
modifier = Modifier
@@ -88,9 +130,20 @@ fun DrawerContent(
horizontalAlignment = Alignment.Start,
) {
SpacerHeight(10.dp)
ProfileCard(painter = R.drawable.ic_author.AsPainter())
if(profile.value == null)
CircularProgressIndicator()
else
ProfileCard(userProfileModel = profile.value!!)
SpacerHeight(15.dp)
AlmostOutlinedText(text = "Идет 3-я четверть")
if(profile.value == null)
CircularProgressIndicator()
else
AlmostOutlinedText(
text = if(profile.value!!.isTerm)
String.format(R.string.title_quatter_format.AsString(), profile.value!!.term)
else
String.format(R.string.title_sem_format.AsString(), profile.value!!.term)
)
SpacerHeight(20.dp)
Column(
verticalArrangement = Arrangement.spacedBy(5.dp),
@@ -135,7 +188,9 @@ fun DrawerContent(
DrawerMainButton(
icon = R.drawable.ic_exit.AsPainter(),
text = R.string.title_leave_from_acconut.AsString()
) { /* TODO */ }
) {
leaveAlertIsOpened = true
}
}
}
}
@@ -1,10 +1,7 @@
package ru.megboyzz.dnevnik.screens.ui.main
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
@@ -25,6 +22,7 @@ import ru.megboyzz.dnevnik.SpacerWidth
import ru.megboyzz.dnevnik.ui.theme.mainBlue
import ru.megboyzz.dnevnik.ui.theme.white
import ru.megboyzz.dnevnik.R
import ru.megboyzz.dnevnik.viewmodel.model.UserProfileModel
@Composable
fun MainScaffold(
@@ -32,7 +30,7 @@ fun MainScaffold(
title: String,
navController: NavController,
scaffoldState: ScaffoldState,
content: (@Composable (it: PaddingValues) -> Unit),
content: @Composable (PaddingValues) -> Unit,
) {
val scope = rememberCoroutineScope()
@@ -52,6 +50,9 @@ fun MainScaffold(
navController = navController,
scaffoldState = scaffoldState,
bottomBar = { BottomBar(navController) },
drawerContent = {
DrawerContent(navController, scaffoldState)
},
content = content
)
}
@@ -65,6 +66,7 @@ fun BaseScaffold(
navController: NavController,
scaffoldState: ScaffoldState,
bottomBar: @Composable () -> Unit,
drawerContent: @Composable (ColumnScope) -> Unit = {},
content: (@Composable (it: PaddingValues) -> Unit),
) {
Scaffold(
@@ -100,9 +102,7 @@ fun BaseScaffold(
drawerContentColor = Color.Transparent,
//drawerShape = drawerShape,
drawerElevation = 0.dp,
drawerContent = {
DrawerContent(navController, scaffoldState)
},
drawerContent = drawerContent,
bottomBar = bottomBar
)
}
@@ -0,0 +1,12 @@
package ru.megboyzz.dnevnik.service
import retrofit2.Call
import retrofit2.http.*
import ru.megboyzz.dnevnik.entities.response.UserContext
interface ContextService{
@GET("/mobile/v4/users/{personId}/context")
fun getContext(@Header("Access-Token") accessToken: String, @Path("personId") personId: String): Call<UserContext>
}
@@ -1,18 +0,0 @@
package ru.megboyzz.dnevnik.service
import okhttp3.ResponseBody
import org.jsoup.Jsoup
import retrofit2.http.POST
import retrofit2.http.Query
interface TokenService{
@POST("/")
suspend fun getToken(
@Query("login") login: String,
@Query("password") password: String,
@Query("ReturnUrl") returnUrl: String = "https://login.dnevnik.ru/oauth2?response_type=token&client_id=bb97b3e445a340b9b9cab4b9ea0dbd6f&scope=CommonInfo,ContactInfo,FriendsAndRelatives,EducationalInfo"
): retrofit2.Call<ResponseBody>
}
@@ -19,8 +19,8 @@ class AuthorizationViewModel(application: App) : AndroidViewModel(application) {
val password = MutableStateFlow("")
val rememberMe = MutableStateFlow(false)
val loginStatus = MutableStateFlow(AuthorizationStatus.NOTHING)
private val authManager = AuthorizationManager(application.database.credentialsDao())
val loginStatus = MutableStateFlow(AuthorizationStatus.CREDENTIALS_LOADING)
private val authManager = AuthorizationManager(application.database)
init {
viewModelScope.launch(Dispatchers.IO) {
@@ -0,0 +1,65 @@
package ru.megboyzz.dnevnik.viewmodel
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import ru.megboyzz.dnevnik.App
import ru.megboyzz.dnevnik.entities.context.GroupType
import ru.megboyzz.dnevnik.entities.context.SchoolType
import ru.megboyzz.dnevnik.entities.db.GlobalUserContext
import ru.megboyzz.dnevnik.viewmodel.model.UserProfileModel
import java.lang.IllegalArgumentException
class ProfileViewModel(application: App) : AndroidViewModel(application) {
val profile = MutableStateFlow<UserProfileModel?>(null)
val userContextDao = application.database.globalUserContextDao()
init{
viewModelScope.launch(Dispatchers.IO) {
val globalUserContext = userContextDao.getGlobalUserContext()
val convertedProfile = convertGlobalUserContextToProfile(globalUserContext)
profile.emit(convertedProfile)
}
}
private fun convertGlobalUserContextToProfile(
globalUserContext: GlobalUserContext
): UserProfileModel{
val info = globalUserContext.apiUserContext.info
val personContext = globalUserContext.apiUserContext.contextPersons.find {
it.school.type == SchoolType.Regular
} ?: globalUserContext.apiUserContext.contextPersons[0]
val term = personContext.reportingPeriodGroup.periods.find {
it.isCurrent
} ?: personContext.reportingPeriodGroup.periods[0]
return UserProfileModel(
firstName = info.firstName,
middleName = info.middleName,
lastName = info.lastName,
groupName = personContext.group.name,
term = term.number + 1,
isTerm = term.type == GroupType.Quarter,
avatarUrl = info.avatarUrl
)
}
}
class ProfileViewModelFactory(private val application: App) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ProfileViewModel::class.java))
return ProfileViewModel(application) as T
throw IllegalArgumentException("Unknown ViewModel Class")
}
}
@@ -0,0 +1,13 @@
package ru.megboyzz.dnevnik.viewmodel.model
data class UserProfileModel(
val firstName: String,
val middleName: String,
val lastName: String,
val groupName: String,
val term: Int,
val isTerm: Boolean,
val avatarUrl: String
){
override fun toString() = "$lastName ${firstName.first()}. ${middleName.first()}."
}
@@ -72,4 +72,10 @@
<string name="title_choose_icons">" Select the icons for subjects"</string>
<string name="title_enable_notif">Enable notify</string>
<string name="title_ok">ОК</string>
<string name="tile_leave">Leave</string>
<string name="text_leave">Are you sure you want to exit the diary?</string>
<string name="title_button_leave">Leave</string>
<string name="title_cancel">Cancel</string>
<string name="title_quatter_format">Quatter %d</string>
<string name="title_sem_format">Semester %d</string>
</resources>
+6
View File
@@ -71,4 +71,10 @@
<string name="title_choose_icons">Выбрать иконки для предметов</string>
<string name="title_enable_notif">Включить уведомления о новых оценках</string>
<string name="title_ok">Ясно</string>
<string name="tile_leave">Выход</string>
<string name="text_leave">Вы точно хотите выйти из дневника? Авторизационные данные будут удалены.</string>
<string name="title_button_leave">Выйти</string>
<string name="title_cancel">Отмена</string>
<string name="title_quatter_format">Идет %d-я четверть</string>
<string name="title_sem_format">Идет %d-ий семестр</string>
</resources>