Compare commits

..
4 Commits
Author SHA1 Message Date
Sweetbread 9e94a67a85 fix: network connectivity related issues 2026-09-17 14:11:34 +03:00
Sweetbread b7183805c2 fix: download recent news 2026-09-17 13:47:10 +03:00
Sweetbread ad64fdf4c5 feat: update GroupsNameText.kt to the new API 2026-09-17 13:07:02 +03:00
Sweetbread d66ffaae7b ref: models.kt 2026-09-17 13:01:54 +03:00
23 changed files with 270 additions and 139 deletions
+2 -1
View File
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2025 Gleb Zaharov. License: GPLv3 (see LICENSE). -->
<!-- Copyright (c) 2026 Gleb Zaharov. License: GPLv3 (see LICENSE). -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:name=".app.UNNApp"
@@ -38,7 +38,6 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import ru.sweetbread.unn.R
import ru.sweetbread.unn.core.AuthRepository
import ru.sweetbread.unn.ui.theme.UNNTheme
@@ -51,12 +50,10 @@ class LoginActivity : ComponentActivity() {
val authRepo = (application as UNNApp).container.authRepo
runBlocking {
if (authRepo.restoreSession()) {
start<MainActivity>()
finish()
}
if (authRepo.hasSavedCredentials()) {
start<MainActivity>()
finish()
return
}
setContent {
@@ -161,4 +158,4 @@ fun LoginPanel(
Text(stringResource(R.string.sign_in))
}
}
}
}
@@ -1,5 +1,10 @@
// Copyright (c) 2026 Gleb Zaharov. License: GPLv3 (see LICENSE).
package ru.sweetbread.unn.app
import android.content.Intent
import android.net.ConnectivityManager
import android.net.Network
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@@ -50,23 +55,33 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import coil3.compose.AsyncImage
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import ru.sweetbread.unn.R
import ru.sweetbread.unn.core.network.LoginResult
import ru.sweetbread.unn.core.network.portalURL
import ru.sweetbread.unn.feature.About
import ru.sweetbread.unn.feature.news.Blogposts
import ru.sweetbread.unn.feature.schedule.Schedule
import ru.sweetbread.unn.feature.About
import ru.sweetbread.unn.ui.theme.UNNTheme
import splitties.resources.appStr
import splitties.toast.toast
class MainActivity : ComponentActivity() {
private lateinit var connectivityManager: ConnectivityManager
private var restoreSessionJob: Job? = null
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
restoreSavedSession()
}
}
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
@@ -74,6 +89,15 @@ class MainActivity : ComponentActivity() {
WindowCompat.setDecorFitsSystemWindows(window, false)
val authRepo = (application as UNNApp).container.authRepo
lifecycleScope.launch {
authRepo.authenticationFailures.collect {
openLogin()
}
}
connectivityManager = getSystemService(ConnectivityManager::class.java)
restoreSavedSession()
setContent {
UNNTheme {
Surface(
@@ -193,6 +217,44 @@ class MainActivity : ComponentActivity() {
}
}
override fun onStart() {
super.onStart()
connectivityManager.registerDefaultNetworkCallback(networkCallback)
}
override fun onStop() {
connectivityManager.unregisterNetworkCallback(networkCallback)
super.onStop()
}
private fun restoreSavedSession() {
val container = (application as UNNApp).container
if (
container.sessionManager.sessionId != null ||
restoreSessionJob?.isActive == true
) {
return
}
restoreSessionJob = lifecycleScope.launch {
if (container.authRepo.restoreSession() == LoginResult.InvalidCredentials) {
openLogin()
}
}
}
private fun openLogin() {
if (isFinishing) return
startActivity(
Intent(this, LoginActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
)
finish()
}
@Composable
fun DrawerContent(modifier: Modifier = Modifier, navController: NavController) {
val me = (application as UNNApp).container.sessionManager.user.collectAsState().value
@@ -287,4 +349,4 @@ class MainActivity : ComponentActivity() {
Text("User not found")
}
}
}
}
@@ -8,7 +8,9 @@ import ru.sweetbread.unn.core.AuthRepository
import ru.sweetbread.unn.core.CredentialsStore
import ru.sweetbread.unn.core.SessionManager
import ru.sweetbread.unn.core.db.AppDatabase
import ru.sweetbread.unn.core.network.LoginResult
import ru.sweetbread.unn.core.network.PortalApi
import ru.sweetbread.unn.core.network.SessionRefreshHandler
import ru.sweetbread.unn.core.network.createHttpClient
import ru.sweetbread.unn.feature.news.NewsRepository
import ru.sweetbread.unn.feature.profile.UserRepository
@@ -33,8 +35,9 @@ class UNNApp : Application() {
class AppContainer(context: Context) {
val credentialsStore = CredentialsStore(context)
val sessionManager = SessionManager()
val sessionRefreshHandler = SessionRefreshHandler()
val httpClient = createHttpClient()
val httpClient = createHttpClient(sessionRefreshHandler)
val portalApi = PortalApi(
client = httpClient,
@@ -43,6 +46,12 @@ class AppContainer(context: Context) {
val authRepo = AuthRepository(credentialsStore, portalApi)
init {
sessionRefreshHandler.setRefreshAction {
authRepo.restoreSession() == LoginResult.Success
}
}
val database = roomDb<AppDatabase>(name = "cache") {
fallbackToDestructiveMigration(dropAllTables = true)
}
@@ -61,4 +70,4 @@ class AppContainer(context: Context) {
api = portalApi,
db = database,
)
}
}
@@ -2,8 +2,10 @@
package ru.sweetbread.unn.core
import android.util.Log
import kotlinx.io.IOException
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import ru.sweetbread.unn.core.network.LoginResult
import ru.sweetbread.unn.core.network.PortalApi
@@ -11,6 +13,10 @@ class AuthRepository(
private val credentialsStore: CredentialsStore,
private val api: PortalApi
) {
private val restoreMutex = Mutex()
private val _authenticationFailures = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
val authenticationFailures = _authenticationFailures.asSharedFlow()
suspend fun login(
login: String,
password: String
@@ -26,21 +32,22 @@ class AuthRepository(
return res
}
suspend fun restoreSession(): Boolean {
val credentials =
credentialsStore.get()
?: return false
fun hasSavedCredentials(): Boolean = credentialsStore.get() != null
return try {
val res = api.login(
credentials.login,
credentials.password
)
suspend fun restoreSession(): LoginResult? = restoreMutex.withLock {
val credentials = credentialsStore.get() ?: return@withLock null
res != LoginResult.InvalidCredentials
} catch (e: IOException) {
Log.e("AuthRepo", "restoreSession error", e)
false
val result = api.login(
credentials.login,
credentials.password
)
if (result == LoginResult.InvalidCredentials) {
credentialsStore.clear()
api.session.clear()
_authenticationFailures.emit(Unit)
}
result
}
}
}
@@ -7,6 +7,7 @@ import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
import androidx.core.content.edit
import java.security.GeneralSecurityException
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
@@ -97,48 +98,55 @@ class CredentialsStore(
}
fun get(): Credentials? {
val encryptedString =
prefs.getString("data", null)
?: return null
return try {
val encryptedString =
prefs.getString("data", null)
?: return null
val ivString =
prefs.getString("iv", null)
?: return null
val ivString =
prefs.getString("iv", null)
?: return null
val encrypted = Base64.decode(
encryptedString,
Base64.NO_WRAP
)
val encrypted = Base64.decode(
encryptedString,
Base64.NO_WRAP
)
val iv = Base64.decode(
ivString,
Base64.NO_WRAP
)
val iv = Base64.decode(
ivString,
Base64.NO_WRAP
)
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(
Cipher.DECRYPT_MODE,
key,
GCMParameterSpec(128, iv)
)
cipher.init(
Cipher.DECRYPT_MODE,
key,
GCMParameterSpec(128, iv)
)
val plainText = cipher
.doFinal(encrypted)
.decodeToString()
val plainText = cipher
.doFinal(encrypted)
.decodeToString()
val parts = plainText.split('\u0000', limit = 2)
val parts = plainText.split('\u0000', limit = 2)
if (parts.size != 2)
return null
return Credentials(
login = parts[0],
password = parts[1]
)
if (parts.size != 2)
null
else Credentials(
login = parts[0],
password = parts[1]
)
} catch (_: GeneralSecurityException) {
clear()
null
} catch (_: IllegalArgumentException) {
clear()
null
}
}
fun clear() {
prefs.edit { clear() }
}
}
}
@@ -22,9 +22,12 @@ interface NewsDao {
@Query("SELECT * FROM newsDB ORDER BY `publishedAt` DESC")
fun paging(): PagingSource<Int, NewsDB>
@Query("DELETE FROM newsDB")
suspend fun clear()
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(news: List<NewsDB>)
@Delete
suspend fun delete(unit: NewsDB)
}
}
@@ -6,8 +6,8 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.withTransaction
import ru.sweetbread.unn.api.Auditorium
import ru.sweetbread.unn.core.db.AppDatabase
import ru.sweetbread.unn.model.Auditorium
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
@@ -5,7 +5,7 @@ package ru.sweetbread.unn.core.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import ru.sweetbread.unn.api.Building
import ru.sweetbread.unn.model.Building
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
@@ -18,4 +18,4 @@ data class BuildingDB(
)
fun BuildingDB.toModel() = Building(name, gid, oid)
fun Building.toEntity() = BuildingDB(oid, name, gid, LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME))
fun Building.toEntity() = BuildingDB(oid, name, gid, LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME))
@@ -5,7 +5,7 @@ package ru.sweetbread.unn.core.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import ru.sweetbread.unn.api.Discipline
import ru.sweetbread.unn.model.Discipline
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
@@ -19,4 +19,4 @@ data class DisciplineDB(
fun DisciplineDB.toModel() = Discipline(name, oid, type)
fun Discipline.toEntity() = DisciplineDB(oid, name, type, LocalDateTime.now().format(
DateTimeFormatter.ISO_DATE_TIME))
DateTimeFormatter.ISO_DATE_TIME))
@@ -5,7 +5,7 @@ package ru.sweetbread.unn.core.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import ru.sweetbread.unn.api.KindOfWork
import ru.sweetbread.unn.model.KindOfWork
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
@@ -25,4 +25,4 @@ fun KindOfWork.toEntity() = KindOfWorkDB(
uid,
complexity,
LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)
)
)
@@ -5,8 +5,8 @@ package ru.sweetbread.unn.core.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import ru.sweetbread.unn.api.Lecturer
import ru.sweetbread.unn.api.LecturerRank
import ru.sweetbread.unn.model.Lecturer
import ru.sweetbread.unn.model.LecturerRank
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
@@ -6,8 +6,8 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.withTransaction
import ru.sweetbread.unn.api.ScheduleUnit
import ru.sweetbread.unn.core.db.AppDatabase
import ru.sweetbread.unn.model.ScheduleUnit
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
@@ -67,4 +67,4 @@ suspend fun cacheSchedule(db: AppDatabase, units: List<ScheduleUnit>) {
db.lecturerDao().insert(units.flatMap { it.lecturers }.map { it.toEntity() })
db.scheduleDao().insert(units.map { it.toEntity() })
}
}
}
@@ -6,7 +6,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import io.ktor.util.reflect.instanceOf
import ru.sweetbread.unn.api.AvatarSet
import ru.sweetbread.unn.model.AvatarSet
import ru.sweetbread.unn.model.Student
import ru.sweetbread.unn.model.Type
import ru.sweetbread.unn.model.User
@@ -6,14 +6,37 @@ import android.util.Log
import io.ktor.client.HttpClient
import io.ktor.client.engine.android.Android
import io.ktor.client.plugins.HttpRequestRetry
import io.ktor.client.plugins.HttpSend
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.cache.HttpCache
import io.ktor.client.plugins.cookies.HttpCookies
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.plugins.plugin
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.http.HttpStatusCode
import io.ktor.util.AttributeKey
fun createHttpClient() = HttpClient(Android) {
private val SkipSessionRefresh = AttributeKey<Unit>("SkipSessionRefresh")
private val SessionRefreshAttempted = AttributeKey<Unit>("SessionRefreshAttempted")
fun HttpRequestBuilder.skipSessionRefresh() {
attributes.put(SkipSessionRefresh, Unit)
}
class SessionRefreshHandler {
@Volatile
private var refresh: suspend () -> Boolean = { false }
fun setRefreshAction(action: suspend () -> Boolean) {
refresh = action
}
suspend fun refreshSession(): Boolean = refresh()
}
fun createHttpClient(sessionRefreshHandler: SessionRefreshHandler) = HttpClient(Android) {
install(HttpCache)
install(HttpCookies)
install(Logging) {
@@ -34,4 +57,24 @@ fun createHttpClient() = HttpClient(Android) {
request.headers.append("x-retry-count", retryCount.toString())
}
}
}
}.apply {
plugin(HttpSend).intercept { request ->
val call = execute(request)
if (
call.response.status == HttpStatusCode.Forbidden &&
request.attributes.getOrNull(SkipSessionRefresh) == null &&
request.attributes.getOrNull(SessionRefreshAttempted) == null
) {
request.attributes.put(SessionRefreshAttempted, Unit)
if (sessionRefreshHandler.refreshSession()) {
execute(request)
} else {
call
}
} else {
call
}
}
}
@@ -11,15 +11,15 @@ import io.ktor.client.statement.bodyAsText
import io.ktor.http.parameters
import org.json.JSONArray
import org.json.JSONObject
import ru.sweetbread.unn.api.Auditorium
import ru.sweetbread.unn.api.Building
import ru.sweetbread.unn.api.Discipline
import ru.sweetbread.unn.api.KindOfWork
import ru.sweetbread.unn.api.Lecturer
import ru.sweetbread.unn.api.LecturerRank
import ru.sweetbread.unn.api.ScheduleUnit
import ru.sweetbread.unn.core.SessionManager
import ru.sweetbread.unn.model.Auditorium
import ru.sweetbread.unn.model.Building
import ru.sweetbread.unn.model.Discipline
import ru.sweetbread.unn.model.KindOfWork
import ru.sweetbread.unn.model.Lecturer
import ru.sweetbread.unn.model.LecturerRank
import ru.sweetbread.unn.model.NewsDto
import ru.sweetbread.unn.model.ScheduleUnit
import ru.sweetbread.unn.model.User
import java.time.LocalDate
import java.time.LocalDateTime
@@ -50,7 +50,10 @@ class Auth (
append("USER_LOGIN", login)
append("USER_PASSWORD", password)
}
) { retry { noRetry() } }
) {
retry { noRetry() }
skipSessionRefresh()
}
if (r.status.value == 302)
return """PHPSESSID=([\w\d]+)""".toRegex().find(r.headers["Set-Cookie"]!!)!!.groupValues[1]
@@ -182,7 +185,9 @@ class Bitrix(
val client: HttpClient,
) {
suspend fun getCurrentUser(): User {
val r = client.get("$vuzapiURL/profile/current")
val r = client.get("$vuzapiURL/profile/current") {
skipSessionRefresh()
}
return User.fromProfile(JSONObject(r.bodyAsText()))
}
@@ -198,4 +203,4 @@ class Bitrix(
}
return NewsDto.fromJsonArray(JSONArray(r.bodyAsText()))
}
}
}
@@ -64,9 +64,9 @@ class NewsRemoteMediator(
}
db.withTransaction {
// if (loadType == LoadType.REFRESH) {
// db.newsDao().clear()
// }
if (loadType == LoadType.REFRESH) {
db.newsDao().clear()
}
db.newsDao().insertAll(
news.map { it.toEntity() }
@@ -86,4 +86,4 @@ class NewsRemoteMediator(
MediatorResult.Error(e)
}
}
}
}
@@ -44,15 +44,15 @@ import androidx.compose.ui.zIndex
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.delay
import ru.sweetbread.unn.R
import ru.sweetbread.unn.api.Auditorium
import ru.sweetbread.unn.api.Building
import ru.sweetbread.unn.api.Discipline
import ru.sweetbread.unn.api.KindOfWork
import ru.sweetbread.unn.api.Lecturer
import ru.sweetbread.unn.api.LecturerRank
import ru.sweetbread.unn.api.ScheduleUnit
import ru.sweetbread.unn.feature.schedule.components.BoundedWeekPicker
import ru.sweetbread.unn.feature.schedule.components.GroupsNameText
import ru.sweetbread.unn.model.Auditorium
import ru.sweetbread.unn.model.Building
import ru.sweetbread.unn.model.Discipline
import ru.sweetbread.unn.model.KindOfWork
import ru.sweetbread.unn.model.Lecturer
import ru.sweetbread.unn.model.LecturerRank
import ru.sweetbread.unn.model.ScheduleUnit
import ru.sweetbread.unn.ui.theme.UNNTheme
import splitties.resources.appStr
import java.time.LocalDate
@@ -5,12 +5,11 @@ package ru.sweetbread.unn.feature.schedule
import androidx.room.withTransaction
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import ru.sweetbread.unn.api.ScheduleUnit
import ru.sweetbread.unn.core.SessionManager
import ru.sweetbread.unn.core.db.AppDatabase
import ru.sweetbread.unn.core.db.entity.cacheSchedule
import ru.sweetbread.unn.core.db.entity.toModel
import ru.sweetbread.unn.core.network.PortalApi
import ru.sweetbread.unn.model.ScheduleUnit
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
@@ -43,4 +42,4 @@ class ScheduleRepository (
}
}
}
}
}
@@ -15,7 +15,7 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import ru.sweetbread.unn.app.UNNApp
import ru.sweetbread.unn.api.ScheduleUnit
import ru.sweetbread.unn.model.ScheduleUnit
import java.time.LocalDate
data class ScheduleUiState(
@@ -88,4 +88,4 @@ class ScheduleViewModel(
}
}
}
}
}
@@ -27,7 +27,16 @@ fun GroupsNameText(
) {
val suffix = if (text.endsWith("-В-OUP")) "-В-OUP" else null
val mainPart = if (suffix != null) text.dropLast(6) else text
val blocks = mainPart.split('|')
val blocks = if (mainPart.startsWith("Поток_(") && mainPart.endsWith(')')) {
mainPart
.removePrefix("Поток_(")
.dropLast(1)
.split(';')
.map(String::trim)
.filter(String::isNotEmpty)
} else {
mainPart.split('|')
}
FlowRow(
modifier = modifier,
@@ -47,4 +56,4 @@ fun GroupsNameText(
suffix?.let { Text(text = it) }
}
}
}
@@ -1,19 +1,12 @@
// Copyright (c) 2026 Gleb Zaharov. License: GPLv3 (see LICENSE).
package ru.sweetbread.unn.api
package ru.sweetbread.unn.model
import ru.sweetbread.unn.R
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
enum class LecturerRank(val id: Int) {
Assistant(R.string.assistant),
Lecturer(R.string.lecturer),
SLecturer(R.string.slecturer),
AProfessor(R.string.aprofessor)
}
class ScheduleUnit(
val oid: Int,
val auditorium: Auditorium,
@@ -27,6 +20,13 @@ class ScheduleUnit(
val lastUpdated: LocalDateTime,
)
enum class LecturerRank(val id: Int) {
Assistant(R.string.assistant),
Lecturer(R.string.lecturer),
SLecturer(R.string.slecturer),
AProfessor(R.string.aprofessor)
}
class Auditorium(
val name: String,
val oid: Int,
@@ -59,19 +59,4 @@ class Lecturer(
val email: String,
val unnId: Int,
val uid: String
)
class Post(
val id: Int,
val authorId: Int,
val enableComments: Boolean,
val numComments: Int,
val date: LocalDateTime,
val content: String
)
class AvatarSet(
val original: String,
val thumbnail: String,
val small: String
)
@@ -4,15 +4,12 @@ package ru.sweetbread.unn.model
import org.json.JSONObject
import ru.sweetbread.unn.R
import ru.sweetbread.unn.api.AvatarSet
import splitties.resources.appStr
enum class Type(val s: String) {
student(appStr(R.string.student)),
// group("group"),
lecturer(appStr(R.string.lecturer)),
// auditorium("auditorium"),
employee(appStr(R.string.employee))
Student(appStr(R.string.student)),
Lecturer(appStr(R.string.lecturer)),
Employee(appStr(R.string.employee))
}
open class User(
@@ -32,8 +29,8 @@ open class User(
.getJSONObject(0)
.getString("type")
) {
"student" -> Type.student
else -> Type.employee
"student" -> Type.Student
else -> Type.Employee
},
json.getString("email"),
json.getString("fullname"),
@@ -53,8 +50,8 @@ open class User(
return User(
user.getInt("id"),
when (json.getString("type")) {
"student" -> Type.student
else -> Type.employee
"student" -> Type.Student
else -> Type.Employee
},
user.getString("email"),
user.getString("fullname"),
@@ -71,7 +68,7 @@ open class User(
fun getPlaceholder() = User(
0,
Type.employee,
Type.Employee,
"",
"Удалённый пользователь",
"Deleted user",
@@ -107,4 +104,10 @@ class Student(
user.avatar,
group
)
}
}
class AvatarSet(
val original: String,
val thumbnail: String,
val small: String
)