ref: removing api calls to separate functions

This commit is contained in:
Sweetbread 2025-04-26 02:17:20 +03:00
parent 654acf1b77
commit 1286860d55
Signed by: Sweetbread
GPG Key ID: 17A5CB9A7DD85319
10 changed files with 352 additions and 316 deletions

View File

@ -0,0 +1,99 @@
/*
* Created by sweetbread
* Copyright (c) 2025. All rights reserved.
*/
package ru.sweetbread.unn.api
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.withContext
import ru.sweetbread.unn.db.cacheSchedule
import ru.sweetbread.unn.db.loadSchedule
import ru.sweetbread.unn.ui.layout.LoginData
import java.time.LocalDate
lateinit var PHPSESSID: String
lateinit var CSRF: String
lateinit var ME: User
/**
* Authorize user by [login] and [password]
*
* Also defines local vars [PHPSESSID] and [ME]
*/
suspend fun auth(
login: String = LoginData.login,
password: String = LoginData.password,
forced: Boolean = false
): Boolean {
if (!forced) {
if (::PHPSESSID.isInitialized and ::ME.isInitialized)
return true
}
val id = getToken(login, password)
if (id != null) {
PHPSESSID = id
getMyself(login)
getCSRF()
return true
}
return false
}
/**
* Save info about current [User] in memory
*/
private suspend fun getMyself(login: String) = coroutineScope {
val idDeferred = async { getId(login) }
val userDeferred = async { getUser() }
val id = idDeferred.await()
val user = userDeferred.await()
ME = User(
unnId = id,
userId = user.userId,
type = Type.student,
email = user.email,
nameRu = user.nameRu,
nameEn = user.nameEn,
isMale = user.isMale,
birthday = user.birthday,
avatar = user.avatar
)
}
suspend fun getScheduleDay(
type: Type = ME.type,
id: Int = ME.unnId!!,
date: LocalDate
): ArrayList<ScheduleUnit> {
if ((type == ME.type) and (id == ME.unnId!!)) {
val schedule = withContext(Dispatchers.IO) { loadSchedule(date) }
Log.d("Schedule", schedule.joinToString())
if (schedule.isNotEmpty())
return schedule
}
return getSchedule(type, id, date, date)
}
suspend fun getSchedule(
type: Type = ME.type,
id: Int = ME.unnId!!,
start: LocalDate,
finish: LocalDate
): ArrayList<ScheduleUnit> {
val schedule = downloadSchedule(type, id, start, finish)
if ((type == ME.type) and (id == ME.unnId!!))
cacheSchedule(schedule)
return schedule
}

View File

@ -3,9 +3,17 @@
* Copyright (c) 2025. All rights reserved. * Copyright (c) 2025. All rights reserved.
*/ */
package ru.sweetbread.unn package ru.sweetbread.unn.api
import android.util.Log 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.HttpTimeout
import io.ktor.client.plugins.cache.HttpCache
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.request.forms.submitForm import io.ktor.client.request.forms.submitForm
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.header import io.ktor.client.request.header
@ -16,13 +24,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import ru.sweetbread.unn.db.cacheSchedule
import ru.sweetbread.unn.db.cacheUser import ru.sweetbread.unn.db.cacheUser
import ru.sweetbread.unn.db.loadSchedule
import ru.sweetbread.unn.db.loadUserByBitrixId import ru.sweetbread.unn.db.loadUserByBitrixId
import ru.sweetbread.unn.ui.layout.LoginData
import ru.sweetbread.unn.ui.layout.client
import splitties.resources.appStr
import java.time.Instant import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
@ -31,10 +34,6 @@ import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
private lateinit var PHPSESSID: String
private lateinit var CSRF: String
lateinit var ME: User
const val portalURL = "https://portal.unn.ru" const val portalURL = "https://portal.unn.ru"
const val ruzapiURL = "$portalURL/ruzapi" const val ruzapiURL = "$portalURL/ruzapi"
const val vuzapiURL = "$portalURL/bitrix/vuz/api" const val vuzapiURL = "$portalURL/bitrix/vuz/api"
@ -42,110 +41,29 @@ const val prtl2URL = "$portalURL/portal2/api"
const val restURL = "$portalURL/rest" const val restURL = "$portalURL/rest"
enum class Type(val s: String) { val client = HttpClient(Android) {
Student(appStr(R.string.student)), install(HttpCache)
Group("group"), install(Logging) {
Lecturer(appStr(R.string.lecturer)), logger = object : Logger {
Auditorium("auditorium"), override fun log(message: String) {
Employee(appStr(R.string.employee)) Log.i("Ktor", message)
}
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,
val date: LocalDate,
val discipline: Discipline,
val kindOfWork: KindOfWork,
val lecturers: ArrayList<Lecturer>,
val stream: String,
val begin: LocalTime,
val end: LocalTime
)
class Auditorium(
val name: String,
val oid: Int,
val floor: Int,
val building: Building
)
class Building(
val name: String,
val gid: Int,
val oid: Int
)
class Discipline(
val name: String,
val oid: Int,
val type: Int
)
class KindOfWork(
val name: String,
val oid: Int,
val uid: String,
val complexity: Int
)
class Lecturer(
val name: String,
val rank: LecturerRank,
val email: String,
val unnId: Int,
val uid: String
)
class User(
val unnId: Int?,
val bitrixId: Int,
val userId: Int,
val type: Type,
val email: String,
val nameRu: String,
val nameEn: String,
val isMale: Boolean,
val birthday: LocalDate,
val avatar: AvatarSet
)
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
)
/**
* Authorize user by [login] and [password]
*
* Also defines local vars [PHPSESSID] and [ME]
*/
suspend fun auth(
login: String = LoginData.login,
password: String = LoginData.password,
forced: Boolean = false
): Boolean {
if (!forced) {
if (::PHPSESSID.isInitialized and ::ME.isInitialized)
return true
} }
}
level = LogLevel.ALL
}
install(HttpTimeout) {
connectTimeoutMillis = 5000
}
install(HttpRequestRetry) {
retryOnException(maxRetries = 3, retryOnTimeout = true)
exponentialDelay()
modifyRequest { request ->
request.headers.append("x-retry-count", retryCount.toString())
}
}
}
suspend fun getToken(login: String, password: String): String? {
val r = client.submitForm("$portalURL/auth/?login=yes", val r = client.submitForm("$portalURL/auth/?login=yes",
formParameters = parameters { formParameters = parameters {
append("AUTH_FORM", "Y") append("AUTH_FORM", "Y")
@ -155,52 +73,43 @@ suspend fun auth(
append("USER_PASSWORD", password) append("USER_PASSWORD", password)
} }
) )
if (r.status.value == 302) {
PHPSESSID = if (r.status.value == 302)
"""PHPSESSID=([\w\d]+)""".toRegex().find(r.headers["Set-Cookie"]!!)!!.groupValues[1] return """PHPSESSID=([\w\d]+)""".toRegex().find(r.headers["Set-Cookie"]!!)!!.groupValues[1]
getMyself(login) return null
getCSRF()
return true
}
return false
} }
/** suspend fun getId(login: String): Int {
* Save info about current [User] in memory return JSONObject(client.get("$ruzapiURL/studentinfo/") {
*/
private suspend fun getMyself(login: String) {
// WARNING: trailing / is important, 'cuz API devs are eating shit
// TODO: make up another way to get unnId: this is not useful for lectures
val studentinfo = JSONObject(client.get("$ruzapiURL/studentinfo/") {
header("Cookie", "PHPSESSID=$PHPSESSID") header("Cookie", "PHPSESSID=$PHPSESSID")
parameter("uns", login.drop(1)) parameter("uns", login.drop(1))
}.bodyAsText()) }.bodyAsText()).getString("id").toInt()
}
val user = JSONObject( suspend fun getUser(userId: Int? = null): User {
client.get("$vuzapiURL/user") { // WARNING: trailing / is important, 'cuz API devs are eating shit
// TODO: make up another way to get unnId: this is not useful for lectures
val json = JSONObject(
client.get("$vuzapiURL/user/${userId ?: ""}") {
header("Cookie", "PHPSESSID=$PHPSESSID") header("Cookie", "PHPSESSID=$PHPSESSID")
}.bodyAsText() }.bodyAsText()
) )
Log.d("studentInfo", studentinfo.toString(2)) return User(
unnId = null,
ME = User( userId = json.getInt("id"),
unnId = studentinfo.getString("id").toInt(), type = if (json.getJSONArray("profiles").getJSONObject(0)
bitrixId = user.getInt("bitrix_id"), // TODO: remove .getString("type") == "employee"
userId = user.getInt("id"), ) Type.employee else Type.student,
type = when (studentinfo.getString("type")) { email = json.getString("email"),
"lecturer" -> Type.Lecturer // ig,,, nameRu = json.getString("fullname"),
else -> Type.Student nameEn = json.getString("fullname_en"),
}, isMale = json.getString("sex") == "M",
email = user.getString("email"),
nameRu = user.getString("fullname"),
nameEn = user.getString("fullname_en"),
isMale = user.getString("sex") == "M",
birthday = Instant birthday = Instant
.parse(user.getString("birthdate")) .parse(json.getString("birthdate"))
.atZone(ZoneId.of("Europe/Moscow")) .atZone(ZoneId.of("Europe/Moscow"))
.toLocalDate(), .toLocalDate(),
avatar = user.getJSONObject("photo").let { avatar = json.getJSONObject("photo").let {
AvatarSet( AvatarSet(
it.getString("orig"), it.getString("orig"),
it.getString("thumbnail"), it.getString("thumbnail"),
@ -210,23 +119,7 @@ private suspend fun getMyself(login: String) {
) )
} }
suspend fun getScheduleDay( suspend fun downloadSchedule(
type: Type = ME.type,
id: Int = ME.unnId!!,
date: LocalDate
): ArrayList<ScheduleUnit> {
if ((type == ME.type) and (id == ME.unnId!!)) {
val schedule = withContext(Dispatchers.IO) { loadSchedule(date) }
Log.d("Schedule", schedule.joinToString())
if (schedule.isNotEmpty())
return schedule
}
return getSchedule(type, id, date, date)
}
suspend fun getSchedule(
type: Type = ME.type, type: Type = ME.type,
id: Int = ME.unnId!!, id: Int = ME.unnId!!,
start: LocalDate, start: LocalDate,
@ -234,13 +127,13 @@ suspend fun getSchedule(
): ArrayList<ScheduleUnit> { ): ArrayList<ScheduleUnit> {
val unnDatePattern = DateTimeFormatter.ofPattern("yyyy-MM-dd") val unnDatePattern = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val r = client.get("$ruzapiURL/schedule/${type.s}/$id") { val r = client.get("$ruzapiURL/schedule/${type.name}/$id") {
parameter("start", start.format(unnDatePattern)) parameter("start", start.format(unnDatePattern))
parameter("finish", finish.format(unnDatePattern)) parameter("finish", finish.format(unnDatePattern))
parameter("lng", "1") parameter("lng", "1")
} }
val json = JSONArray(r.bodyAsText())
val json = JSONArray(r.bodyAsText())
val out = arrayListOf<ScheduleUnit>() val out = arrayListOf<ScheduleUnit>()
for (i in 0 until json.length()) { for (i in 0 until json.length()) {
val unit = json.getJSONObject(i) val unit = json.getJSONObject(i)
@ -304,9 +197,6 @@ suspend fun getSchedule(
) )
} }
if ((type == ME.type) and (id == ME.unnId!!)) {
cacheSchedule(out)
}
return out return out
} }
@ -318,6 +208,23 @@ suspend fun getCSRF() {
CSRF = JSONObject(r.bodyAsText()).getString("sessid") CSRF = JSONObject(r.bodyAsText()).getString("sessid")
} }
suspend fun getUserByBitrixId(id: Int): User {
withContext(Dispatchers.IO) {
loadUserByBitrixId(id)
}?.let { return it }
val userId = JSONObject(client.get("$vuzapiURL/user/bx/$id") {
header("Cookie", "PHPSESSID=$PHPSESSID")
}.bodyAsText()).getInt("id")
getUser(userId).let { user ->
withContext(Dispatchers.IO) {
cacheUser(user)
}
return user
}
}
suspend fun getBlogposts(): ArrayList<Post> { suspend fun getBlogposts(): ArrayList<Post> {
val r = client.get("$prtl2URL/news.php") { val r = client.get("$prtl2URL/news.php") {
header("Cookie", "PHPSESSID=$PHPSESSID") header("Cookie", "PHPSESSID=$PHPSESSID")
@ -344,54 +251,3 @@ suspend fun getBlogposts(): ArrayList<Post> {
} }
return out return out
} }
suspend fun getUserByBitrixId(id: Int): User {
withContext(Dispatchers.IO) {
loadUserByBitrixId(id)
}?.let { return it }
val userId = JSONObject(client.get("$vuzapiURL/user/bx/$id") {
header("Cookie", "PHPSESSID=$PHPSESSID")
}.bodyAsText()).getInt("id")
getUser(userId).let { user ->
withContext(Dispatchers.IO) {
cacheUser(user)
}
return user
}
}
suspend fun getUser(id: Int): User {
val json = JSONObject(
client.get("$vuzapiURL/user/$id") {
header("Cookie", "PHPSESSID=$PHPSESSID")
}.bodyAsText()
)
Log.d("type", json.getJSONArray("profiles").getJSONObject(0).getString("type"))
return User(
unnId = null,
bitrixId = json.getInt("bitrix_id"),
userId = json.getInt("id"),
type = if (json.getJSONArray("profiles").getJSONObject(0)
.getString("type") == "employee"
) Type.Employee else Type.Student,
email = json.getString("email"),
nameRu = json.getString("fullname"),
nameEn = json.getString("fullname_en"),
isMale = json.getString("sex") == "M",
birthday = Instant
.parse(json.getString("birthdate"))
.atZone(ZoneId.of("Europe/Moscow"))
.toLocalDate(),
avatar = json.getJSONObject("photo").let {
AvatarSet(
it.getString("orig"),
it.getString("thumbnail"),
it.getString("small"),
)
}
)
}

View File

@ -0,0 +1,100 @@
/*
* Created by sweetbread
* Copyright (c) 2025. All rights reserved.
*/
package ru.sweetbread.unn.api
import ru.sweetbread.unn.R
import splitties.resources.appStr
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
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))
}
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,
val date: LocalDate,
val discipline: Discipline,
val kindOfWork: KindOfWork,
val lecturers: ArrayList<Lecturer>,
val stream: String,
val begin: LocalTime,
val end: LocalTime
)
class Auditorium(
val name: String,
val oid: Int,
val floor: Int,
val building: Building
)
class Building(
val name: String,
val gid: Int,
val oid: Int
)
class Discipline(
val name: String,
val oid: Int,
val type: Int
)
class KindOfWork(
val name: String,
val oid: Int,
val uid: String,
val complexity: Int
)
class Lecturer(
val name: String,
val rank: LecturerRank,
val email: String,
val unnId: Int,
val uid: String
)
class User(
val unnId: Int?,
val userId: Int,
val type: Type,
val email: String,
val nameRu: String,
val nameEn: String,
val isMale: Boolean,
val birthday: LocalDate,
val avatar: AvatarSet
)
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
)

View File

@ -1,7 +1,17 @@
/*
* Created by sweetbread
* Copyright (c) 2025. All rights reserved.
*/
package ru.sweetbread.unn.db package ru.sweetbread.unn.db
import androidx.room.Database import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import splitties.arch.room.roomDb
val cacheDb = roomDb<AppDatabase>(name = "cache") {
fallbackToDestructiveMigration(dropAllTables = true)
}
@Database(entities = [ @Database(entities = [
UserDB::class, UserDB::class,

View File

@ -1,3 +1,8 @@
/*
* Created by sweetbread
* Copyright (c) 2025. All rights reserved.
*/
package ru.sweetbread.unn.db package ru.sweetbread.unn.db
import android.util.Log import android.util.Log
@ -9,14 +14,13 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import androidx.room.Query import androidx.room.Query
import ru.sweetbread.unn.Auditorium import ru.sweetbread.unn.api.Auditorium
import ru.sweetbread.unn.Building import ru.sweetbread.unn.api.Building
import ru.sweetbread.unn.Discipline import ru.sweetbread.unn.api.Discipline
import ru.sweetbread.unn.KindOfWork import ru.sweetbread.unn.api.KindOfWork
import ru.sweetbread.unn.Lecturer import ru.sweetbread.unn.api.Lecturer
import ru.sweetbread.unn.LecturerRank import ru.sweetbread.unn.api.LecturerRank
import ru.sweetbread.unn.ScheduleUnit import ru.sweetbread.unn.api.ScheduleUnit
import ru.sweetbread.unn.ui.layout.db
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.LocalTime import java.time.LocalTime
@ -43,7 +47,7 @@ interface BuildingDao {
} }
fun cacheBuilding(building: Building) { fun cacheBuilding(building: Building) {
db.buildingDao().insert( cacheDb.buildingDao().insert(
BuildingDB( BuildingDB(
building.oid, building.oid,
building.name, building.name,
@ -54,13 +58,13 @@ fun cacheBuilding(building: Building) {
} }
fun loadBuilding(oid: Int): Building? { fun loadBuilding(oid: Int): Building? {
return db.buildingDao().get(oid)?.let { return cacheDb.buildingDao().get(oid)?.let {
if (LocalDateTime.parse( if (LocalDateTime.parse(
it.expiredAt, it.expiredAt,
DateTimeFormatter.ISO_DATE_TIME DateTimeFormatter.ISO_DATE_TIME
) > LocalDateTime.now() ) > LocalDateTime.now()
) { ) {
db.buildingDao().delete(it) cacheDb.buildingDao().delete(it)
return null return null
} }
Building( Building(
@ -94,7 +98,7 @@ interface AuditoriumDao {
fun cacheAuditorium(auditorium: Auditorium) { fun cacheAuditorium(auditorium: Auditorium) {
cacheBuilding(auditorium.building) cacheBuilding(auditorium.building)
db.auditoriumDao().insert( cacheDb.auditoriumDao().insert(
AuditoriumDB( AuditoriumDB(
auditorium.oid, auditorium.oid,
auditorium.name, auditorium.name,
@ -106,13 +110,13 @@ fun cacheAuditorium(auditorium: Auditorium) {
} }
fun loadAuditorium(oid: Int): Auditorium? { fun loadAuditorium(oid: Int): Auditorium? {
return db.auditoriumDao().get(oid)?.let { return cacheDb.auditoriumDao().get(oid)?.let {
if (LocalDateTime.parse( if (LocalDateTime.parse(
it.expiredAt, it.expiredAt,
DateTimeFormatter.ISO_DATE_TIME DateTimeFormatter.ISO_DATE_TIME
) > LocalDateTime.now() ) > LocalDateTime.now()
) { ) {
db.auditoriumDao().delete(it) cacheDb.auditoriumDao().delete(it)
return null return null
} }
val building = loadBuilding(it.buildingOid) ?: return null val building = loadBuilding(it.buildingOid) ?: return null
@ -146,7 +150,7 @@ interface DisciplineDao {
} }
fun cacheDiscipline(discipline: Discipline) { fun cacheDiscipline(discipline: Discipline) {
db.disciplineDao().insert( cacheDb.disciplineDao().insert(
DisciplineDB( DisciplineDB(
discipline.oid, discipline.oid,
discipline.name, discipline.name,
@ -157,13 +161,13 @@ fun cacheDiscipline(discipline: Discipline) {
} }
fun loadDiscipline(oid: Int): Discipline? { fun loadDiscipline(oid: Int): Discipline? {
return db.disciplineDao().get(oid)?.let { return cacheDb.disciplineDao().get(oid)?.let {
if (LocalDateTime.parse( if (LocalDateTime.parse(
it.expiredAt, it.expiredAt,
DateTimeFormatter.ISO_DATE_TIME DateTimeFormatter.ISO_DATE_TIME
) > LocalDateTime.now() ) > LocalDateTime.now()
) { ) {
db.disciplineDao().delete(it) cacheDb.disciplineDao().delete(it)
return null return null
} }
@ -197,7 +201,7 @@ interface KindOfWorkDao {
} }
fun cacheKindOfWork(kindOfWork: KindOfWork) { fun cacheKindOfWork(kindOfWork: KindOfWork) {
db.kindOfWorkDao().insert( cacheDb.kindOfWorkDao().insert(
KindOfWorkDB( KindOfWorkDB(
kindOfWork.oid, kindOfWork.oid,
kindOfWork.name, kindOfWork.name,
@ -209,13 +213,13 @@ fun cacheKindOfWork(kindOfWork: KindOfWork) {
} }
fun loadKindOfWork(oid: Int): KindOfWork? { fun loadKindOfWork(oid: Int): KindOfWork? {
return db.kindOfWorkDao().get(oid)?.let { return cacheDb.kindOfWorkDao().get(oid)?.let {
if (LocalDateTime.parse( if (LocalDateTime.parse(
it.expiredAt, it.expiredAt,
DateTimeFormatter.ISO_DATE_TIME DateTimeFormatter.ISO_DATE_TIME
) > LocalDateTime.now() ) > LocalDateTime.now()
) { ) {
db.kindOfWorkDao().delete(it) cacheDb.kindOfWorkDao().delete(it)
return null return null
} }
@ -251,7 +255,7 @@ interface LecturerDao {
} }
fun cacheLecturer(lecturer: Lecturer) { fun cacheLecturer(lecturer: Lecturer) {
db.lecturerDao().insert( cacheDb.lecturerDao().insert(
LecturerDB( LecturerDB(
lecturer.unnId, lecturer.unnId,
lecturer.name, lecturer.name,
@ -264,13 +268,13 @@ fun cacheLecturer(lecturer: Lecturer) {
} }
fun loadLecturer(unnId: Int): Lecturer? { fun loadLecturer(unnId: Int): Lecturer? {
return db.lecturerDao().get(unnId)?.let { return cacheDb.lecturerDao().get(unnId)?.let {
if (LocalDateTime.parse( if (LocalDateTime.parse(
it.expiredAt, it.expiredAt,
DateTimeFormatter.ISO_DATE_TIME DateTimeFormatter.ISO_DATE_TIME
) > LocalDateTime.now() ) > LocalDateTime.now()
) { ) {
db.lecturerDao().delete(it) cacheDb.lecturerDao().delete(it)
return null return null
} }
@ -320,7 +324,7 @@ fun cacheSchedule(item: ScheduleUnit) {
cacheKindOfWork(item.kindOfWork) cacheKindOfWork(item.kindOfWork)
cacheLecturer(item.lecturers[0]) cacheLecturer(item.lecturers[0])
db.scheduleDao().insert( cacheDb.scheduleDao().insert(
ScheduleUnitDB( ScheduleUnitDB(
item.oid, item.oid,
item.date.format(DateTimeFormatter.ISO_DATE), item.date.format(DateTimeFormatter.ISO_DATE),
@ -347,7 +351,7 @@ fun cacheSchedule(items: ArrayList<ScheduleUnit>) {
} }
fun loadSchedule(oid: Int): ScheduleUnit? { fun loadSchedule(oid: Int): ScheduleUnit? {
db.scheduleDao().getSchedule(oid)?.let { cacheDb.scheduleDao().getSchedule(oid)?.let {
Log.d("load", it.oid.toString()) Log.d("load", it.oid.toString())
if (LocalDateTime.parse( if (LocalDateTime.parse(
it.expiredAt, it.expiredAt,
@ -355,7 +359,7 @@ fun loadSchedule(oid: Int): ScheduleUnit? {
) < LocalDateTime.now() ) < LocalDateTime.now()
) { ) {
Log.d("delete", it.oid.toString()) Log.d("delete", it.oid.toString())
db.scheduleDao().delete(it) cacheDb.scheduleDao().delete(it)
return null return null
} }
@ -375,10 +379,10 @@ fun loadSchedule(oid: Int): ScheduleUnit? {
} }
fun loadSchedule(date: LocalDate): ArrayList<ScheduleUnit> { fun loadSchedule(date: LocalDate): ArrayList<ScheduleUnit> {
db.scheduleDao().getSchedule(date.format(DateTimeFormatter.ISO_DATE)) cacheDb.scheduleDao().getSchedule(date.format(DateTimeFormatter.ISO_DATE))
.map { Log.d("meow", "${it.oid}: ${loadSchedule(it.oid)}") } .map { Log.d("meow", "${it.oid}: ${loadSchedule(it.oid)}") }
return ArrayList( return ArrayList(
db.scheduleDao().getSchedule(date.format(DateTimeFormatter.ISO_DATE)) cacheDb.scheduleDao().getSchedule(date.format(DateTimeFormatter.ISO_DATE))
.mapNotNull { loadSchedule(it.oid) } .mapNotNull { loadSchedule(it.oid) }
) )
} }

View File

@ -1,3 +1,8 @@
/*
* Created by sweetbread
* Copyright (c) 2025. All rights reserved.
*/
package ru.sweetbread.unn.db package ru.sweetbread.unn.db
import android.util.Log import android.util.Log
@ -8,10 +13,9 @@ import androidx.room.Entity
import androidx.room.Insert import androidx.room.Insert
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import androidx.room.Query import androidx.room.Query
import ru.sweetbread.unn.AvatarSet import ru.sweetbread.unn.api.AvatarSet
import ru.sweetbread.unn.Type import ru.sweetbread.unn.api.Type
import ru.sweetbread.unn.User import ru.sweetbread.unn.api.User
import ru.sweetbread.unn.ui.layout.db
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
@ -20,7 +24,6 @@ import java.time.format.DateTimeFormatter
data class UserDB( data class UserDB(
@PrimaryKey val userId: Int, @PrimaryKey val userId: Int,
@ColumnInfo val unnId: Int?, @ColumnInfo val unnId: Int?,
@ColumnInfo val bitrixId: Int,
@ColumnInfo val type: Type, @ColumnInfo val type: Type,
@ColumnInfo val email: String, @ColumnInfo val email: String,
@ColumnInfo val nameRu: String, @ColumnInfo val nameRu: String,
@ -35,8 +38,8 @@ data class UserDB(
@Dao @Dao
interface UserDao { interface UserDao {
@Query("SELECT * FROM userDB WHERE bitrixId = :bitrixId LIMIT 1") @Query("SELECT * FROM userDB WHERE userId = :userId LIMIT 1")
fun getUserByBitrix(bitrixId: Int): UserDB? fun getUserById(userId: Int): UserDB?
@Insert @Insert
fun insert(user: UserDB) fun insert(user: UserDB)
@ -48,11 +51,10 @@ interface UserDao {
fun cacheUser(user: User) { fun cacheUser(user: User) {
try { try {
db.userDao().insert( cacheDb.userDao().insert(
UserDB( UserDB(
user.userId, user.userId,
user.unnId, user.unnId,
user.bitrixId,
user.type, user.type,
user.email, user.email,
user.nameRu, user.nameRu,
@ -70,7 +72,7 @@ fun cacheUser(user: User) {
} }
fun loadUserByBitrixId(bitrixId: Int): User? { fun loadUserByBitrixId(bitrixId: Int): User? {
val user = db.userDao().getUserByBitrix(bitrixId) val user = cacheDb.userDao().getUserById(bitrixId)
Log.d("UserDB", user?.nameEn ?: "None") Log.d("UserDB", user?.nameEn ?: "None")
if (user == null) return null if (user == null) return null
if (LocalDateTime.parse( if (LocalDateTime.parse(
@ -78,12 +80,11 @@ fun loadUserByBitrixId(bitrixId: Int): User? {
DateTimeFormatter.ISO_LOCAL_DATE_TIME DateTimeFormatter.ISO_LOCAL_DATE_TIME
) < LocalDateTime.now() ) < LocalDateTime.now()
) { ) {
db.userDao().delete(user) cacheDb.userDao().delete(user)
return null return null
} else { } else {
return User( return User(
user.unnId, user.unnId,
user.bitrixId,
user.userId, user.userId,
user.type, user.type,
user.email, user.email,

View File

@ -55,14 +55,14 @@ import coil.request.ImageRequest
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import ru.sweetbread.unn.AvatarSet
import ru.sweetbread.unn.Post
import ru.sweetbread.unn.R import ru.sweetbread.unn.R
import ru.sweetbread.unn.Type import ru.sweetbread.unn.api.AvatarSet
import ru.sweetbread.unn.User import ru.sweetbread.unn.api.Post
import ru.sweetbread.unn.getBlogposts import ru.sweetbread.unn.api.Type
import ru.sweetbread.unn.getUserByBitrixId import ru.sweetbread.unn.api.User
import ru.sweetbread.unn.portalURL import ru.sweetbread.unn.api.getBlogposts
import ru.sweetbread.unn.api.getUserByBitrixId
import ru.sweetbread.unn.api.portalURL
import ru.sweetbread.unn.ui.theme.UNNTheme import ru.sweetbread.unn.ui.theme.UNNTheme
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
@ -73,8 +73,7 @@ import java.time.format.FormatStyle
val defUser = User( val defUser = User(
null, null,
123, 123,
123, Type.student,
Type.Student,
"cool.email@domain.com", "cool.email@domain.com",
"Джон Сигма Омегович", "Джон Сигма Омегович",
"Jon Sigma Omega", "Jon Sigma Omega",
@ -241,7 +240,9 @@ fun UserItemPreview() {
Modifier Modifier
.width(300.dp) .width(300.dp)
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(8.dp))
.background(MaterialTheme.colorScheme.primaryContainer), defUser, Type.Student.s .background(MaterialTheme.colorScheme.primaryContainer),
defUser,
Type.student.s
) )
} }
} }

View File

@ -55,15 +55,15 @@ import com.kizitonwose.calendar.core.WeekDay
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ru.sweetbread.unn.Auditorium
import ru.sweetbread.unn.Building
import ru.sweetbread.unn.Discipline
import ru.sweetbread.unn.KindOfWork
import ru.sweetbread.unn.Lecturer
import ru.sweetbread.unn.LecturerRank
import ru.sweetbread.unn.R import ru.sweetbread.unn.R
import ru.sweetbread.unn.ScheduleUnit import ru.sweetbread.unn.api.Auditorium
import ru.sweetbread.unn.getScheduleDay 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.api.getScheduleDay
import ru.sweetbread.unn.ui.theme.UNNTheme import ru.sweetbread.unn.ui.theme.UNNTheme
import splitties.resources.appStr import splitties.resources.appStr
import splitties.resources.appStrArray import splitties.resources.appStrArray

View File

@ -44,7 +44,7 @@ import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import ru.sweetbread.unn.R import ru.sweetbread.unn.R
import ru.sweetbread.unn.auth import ru.sweetbread.unn.api.auth
import ru.sweetbread.unn.ui.theme.UNNTheme import ru.sweetbread.unn.ui.theme.UNNTheme
import splitties.activities.start import splitties.activities.start
import splitties.preferences.Preferences import splitties.preferences.Preferences

View File

@ -6,7 +6,6 @@
package ru.sweetbread.unn.ui.layout package ru.sweetbread.unn.ui.layout
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.background import androidx.compose.foundation.background
@ -60,50 +59,16 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import io.ktor.client.HttpClient
import io.ktor.client.engine.android.Android
import io.ktor.client.plugins.HttpRequestRetry
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.cache.HttpCache
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ru.sweetbread.unn.ME
import ru.sweetbread.unn.R import ru.sweetbread.unn.R
import ru.sweetbread.unn.db.AppDatabase import ru.sweetbread.unn.api.ME
import ru.sweetbread.unn.portalURL import ru.sweetbread.unn.api.portalURL
import ru.sweetbread.unn.ui.composes.Blogposts import ru.sweetbread.unn.ui.composes.Blogposts
import ru.sweetbread.unn.ui.composes.Schedule import ru.sweetbread.unn.ui.composes.Schedule
import ru.sweetbread.unn.ui.theme.UNNTheme import ru.sweetbread.unn.ui.theme.UNNTheme
import splitties.arch.room.roomDb
import splitties.resources.appStr import splitties.resources.appStr
import splitties.toast.toast import splitties.toast.toast
val client = HttpClient(Android) {
install(HttpCache)
install(Logging) {
logger = object : Logger {
override fun log(message: String) {
Log.i("Ktor", message)
}
}
level = LogLevel.ALL
}
install(HttpTimeout) {
connectTimeoutMillis = 5000
}
install(HttpRequestRetry) {
retryOnException(maxRetries = 3, retryOnTimeout = true)
exponentialDelay()
modifyRequest { request ->
request.headers.append("x-retry-count", retryCount.toString())
}
}
}
val db = roomDb<AppDatabase>(name = "database")
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)