Стабильная аунтификация
This commit is contained in:
@@ -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()
|
||||
val credentials = credentialsDao.getCredentials()
|
||||
if(!credentials.rememberMe)
|
||||
credentialsDao.deleteCredentials()
|
||||
|
||||
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
|
||||
)
|
||||
+9
@@ -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
|
||||
)
|
||||
+6
@@ -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
|
||||
)
|
||||
+17
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user