Compare commits
3
Commits
a775c2799c
...
568ca32668
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
568ca32668
|
||
|
|
9b90b655ab
|
||
|
|
12dc82e5f4
|
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2025 Gleb Zaharov. License: GPLv3 (see LICENSE).
|
// Copyright (c) 2026 Gleb Zaharov. License: GPLv3 (see LICENSE).
|
||||||
|
|
||||||
package ru.sweetbread.unn.api
|
package ru.sweetbread.unn.api
|
||||||
|
|
||||||
@@ -66,11 +66,10 @@ private suspend fun getMyself(login: String) = coroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getScheduleDay(
|
suspend fun getScheduleDay(
|
||||||
|
date: LocalDate,
|
||||||
type: Type = ME.type,
|
type: Type = ME.type,
|
||||||
id: Int = ME.unnId!!,
|
id: Int = ME.unnId!!
|
||||||
date: LocalDate
|
): List<ScheduleUnit> {
|
||||||
): ArrayList<ScheduleUnit> {
|
|
||||||
|
|
||||||
if ((type == ME.type) and (id == ME.unnId!!)) {
|
if ((type == ME.type) and (id == ME.unnId!!)) {
|
||||||
val schedule = withContext(Dispatchers.IO) { loadSchedule(date) }
|
val schedule = withContext(Dispatchers.IO) { loadSchedule(date) }
|
||||||
Log.d("Schedule", schedule.joinToString())
|
Log.d("Schedule", schedule.joinToString())
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2025 Gleb Zaharov. License: GPLv3 (see LICENSE).
|
// Copyright (c) 2026 Gleb Zaharov. License: GPLv3 (see LICENSE).
|
||||||
|
|
||||||
package ru.sweetbread.unn.api
|
package ru.sweetbread.unn.api
|
||||||
|
|
||||||
@@ -116,6 +116,11 @@ suspend fun getUser(userId: Int? = null): User {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch schedule from Uni API
|
||||||
|
*
|
||||||
|
* @return [ArrayList] of all [ScheduleUnit]s between [start] and [finish]
|
||||||
|
*/
|
||||||
suspend fun downloadSchedule(
|
suspend fun downloadSchedule(
|
||||||
type: Type = ME.type,
|
type: Type = ME.type,
|
||||||
id: Int = ME.unnId!!,
|
id: Int = ME.unnId!!,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2025 Gleb Zaharov. License: GPLv3 (see LICENSE).
|
// Copyright (c) 2026 Gleb Zaharov. License: GPLv3 (see LICENSE).
|
||||||
|
|
||||||
package ru.sweetbread.unn.api
|
package ru.sweetbread.unn.api
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ class ScheduleUnit(
|
|||||||
val date: LocalDate,
|
val date: LocalDate,
|
||||||
val discipline: Discipline,
|
val discipline: Discipline,
|
||||||
val kindOfWork: KindOfWork,
|
val kindOfWork: KindOfWork,
|
||||||
val lecturers: ArrayList<Lecturer>,
|
val lecturers: List<Lecturer>,
|
||||||
val stream: String,
|
val stream: String,
|
||||||
val begin: LocalTime,
|
val begin: LocalTime,
|
||||||
val end: LocalTime
|
val end: LocalTime
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2025 Gleb Zaharov. License: GPLv3 (see LICENSE).
|
// Copyright (c) 2026 Gleb Zaharov. License: GPLv3 (see LICENSE).
|
||||||
|
|
||||||
package ru.sweetbread.unn.db
|
package ru.sweetbread.unn.db
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ val cacheDb = roomDb<AppDatabase>(name = "cache") {
|
|||||||
DisciplineDB::class,
|
DisciplineDB::class,
|
||||||
KindOfWorkDB::class,
|
KindOfWorkDB::class,
|
||||||
LecturerDB::class,
|
LecturerDB::class,
|
||||||
ScheduleUnitDB::class], version = 1)
|
ScheduleUnitDB::class], version = 2)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
abstract fun userDao(): UserDao
|
abstract fun userDao(): UserDao
|
||||||
abstract fun scheduleDao(): ScheduleItemDao
|
abstract fun scheduleDao(): ScheduleItemDao
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
// Copyright (c) 2025 Gleb Zaharov. License: GPLv3 (see LICENSE).
|
// Copyright (c) 2026 Gleb Zaharov. License: GPLv3 (see LICENSE).
|
||||||
|
|
||||||
package ru.sweetbread.unn.db
|
package ru.sweetbread.unn.db
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Dao
|
import androidx.room.Dao
|
||||||
import androidx.room.Delete
|
import androidx.room.Delete
|
||||||
@@ -11,6 +10,7 @@ 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 kotlinx.coroutines.flow.Flow
|
||||||
import ru.sweetbread.unn.api.Auditorium
|
import ru.sweetbread.unn.api.Auditorium
|
||||||
import ru.sweetbread.unn.api.Building
|
import ru.sweetbread.unn.api.Building
|
||||||
import ru.sweetbread.unn.api.Discipline
|
import ru.sweetbread.unn.api.Discipline
|
||||||
@@ -28,7 +28,7 @@ data class BuildingDB(
|
|||||||
@PrimaryKey val oid: Int,
|
@PrimaryKey val oid: Int,
|
||||||
@ColumnInfo val name: String,
|
@ColumnInfo val name: String,
|
||||||
@ColumnInfo val gid: Int,
|
@ColumnInfo val gid: Int,
|
||||||
@ColumnInfo val expiredAt: String
|
@ColumnInfo val updatedAt: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
@@ -37,40 +37,15 @@ interface BuildingDao {
|
|||||||
fun get(oid: Int): BuildingDB?
|
fun get(oid: Int): BuildingDB?
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
fun insert(building: BuildingDB)
|
suspend fun insert(buildings: List<BuildingDB>)
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
fun delete(building: BuildingDB)
|
fun delete(building: BuildingDB)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cacheBuilding(building: Building) {
|
fun BuildingDB.toModel() = Building(name, gid, oid)
|
||||||
cacheDb.buildingDao().insert(
|
fun Building.toEntity() = BuildingDB(oid, name, gid, LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME))
|
||||||
BuildingDB(
|
|
||||||
building.oid,
|
|
||||||
building.name,
|
|
||||||
building.gid,
|
|
||||||
LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadBuilding(oid: Int): Building? {
|
|
||||||
return cacheDb.buildingDao().get(oid)?.let {
|
|
||||||
if (LocalDateTime.parse(
|
|
||||||
it.expiredAt,
|
|
||||||
DateTimeFormatter.ISO_DATE_TIME
|
|
||||||
) > LocalDateTime.now()
|
|
||||||
) {
|
|
||||||
cacheDb.buildingDao().delete(it)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
Building(
|
|
||||||
it.name,
|
|
||||||
it.gid,
|
|
||||||
it.oid
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
data class AuditoriumDB(
|
data class AuditoriumDB(
|
||||||
@@ -78,7 +53,7 @@ data class AuditoriumDB(
|
|||||||
@ColumnInfo val name: String,
|
@ColumnInfo val name: String,
|
||||||
@ColumnInfo val floor: Int?,
|
@ColumnInfo val floor: Int?,
|
||||||
@ColumnInfo val buildingOid: Int,
|
@ColumnInfo val buildingOid: Int,
|
||||||
@ColumnInfo val expiredAt: String
|
@ColumnInfo val updatedAt: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
@@ -87,43 +62,36 @@ interface AuditoriumDao {
|
|||||||
fun get(oid: Int): AuditoriumDB?
|
fun get(oid: Int): AuditoriumDB?
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
fun insert(auditorium: AuditoriumDB)
|
suspend fun insert(auditoriums: List<AuditoriumDB>)
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
fun delete(auditorium: AuditoriumDB)
|
fun delete(auditorium: AuditoriumDB)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cacheAuditorium(auditorium: Auditorium) {
|
fun AuditoriumDB.toModel(): Auditorium? {
|
||||||
cacheBuilding(auditorium.building)
|
return Auditorium(
|
||||||
cacheDb.auditoriumDao().insert(
|
name,
|
||||||
AuditoriumDB(
|
oid,
|
||||||
auditorium.oid,
|
floor ?: 0,
|
||||||
auditorium.name,
|
cacheDb.buildingDao().get(buildingOid)?.toModel() ?: return null
|
||||||
auditorium.floor,
|
|
||||||
auditorium.building.oid,
|
|
||||||
LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
fun Auditorium.toEntity() = AuditoriumDB(
|
||||||
|
oid,
|
||||||
|
name,
|
||||||
|
floor,
|
||||||
|
building.oid,
|
||||||
|
LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)
|
||||||
|
)
|
||||||
|
|
||||||
fun loadAuditorium(oid: Int): Auditorium? {
|
suspend fun cacheAuditorium(auditoriums: List<Auditorium>) {
|
||||||
return cacheDb.auditoriumDao().get(oid)?.let {
|
cacheDb.buildingDao().insert(
|
||||||
if (LocalDateTime.parse(
|
auditoriums.map { it.building }.map { it.toEntity() }
|
||||||
it.expiredAt,
|
)
|
||||||
DateTimeFormatter.ISO_DATE_TIME
|
|
||||||
) > LocalDateTime.now()
|
cacheDb.auditoriumDao().insert(
|
||||||
) {
|
auditoriums.map { it.toEntity() }
|
||||||
cacheDb.auditoriumDao().delete(it)
|
)
|
||||||
return null
|
|
||||||
}
|
|
||||||
val building = loadBuilding(it.buildingOid) ?: return null
|
|
||||||
return Auditorium(
|
|
||||||
it.name,
|
|
||||||
it.oid,
|
|
||||||
it.floor ?: 0,
|
|
||||||
building
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@@ -131,7 +99,7 @@ data class DisciplineDB(
|
|||||||
@PrimaryKey val oid: Int,
|
@PrimaryKey val oid: Int,
|
||||||
@ColumnInfo val name: String,
|
@ColumnInfo val name: String,
|
||||||
@ColumnInfo val type: Int,
|
@ColumnInfo val type: Int,
|
||||||
@ColumnInfo val expiredAt: String
|
@ColumnInfo val updatedAt: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
@@ -140,41 +108,15 @@ interface DisciplineDao {
|
|||||||
fun get(oid: Int): DisciplineDB?
|
fun get(oid: Int): DisciplineDB?
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
fun insert(discipline: DisciplineDB)
|
suspend fun insert(disciplines: List<DisciplineDB>)
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
fun delete(discipline: DisciplineDB)
|
fun delete(discipline: DisciplineDB)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cacheDiscipline(discipline: Discipline) {
|
fun DisciplineDB.toModel() = Discipline(name, oid, type)
|
||||||
cacheDb.disciplineDao().insert(
|
fun Discipline.toEntity() = DisciplineDB(oid, name, type, LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME))
|
||||||
DisciplineDB(
|
|
||||||
discipline.oid,
|
|
||||||
discipline.name,
|
|
||||||
discipline.type,
|
|
||||||
LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadDiscipline(oid: Int): Discipline? {
|
|
||||||
return cacheDb.disciplineDao().get(oid)?.let {
|
|
||||||
if (LocalDateTime.parse(
|
|
||||||
it.expiredAt,
|
|
||||||
DateTimeFormatter.ISO_DATE_TIME
|
|
||||||
) > LocalDateTime.now()
|
|
||||||
) {
|
|
||||||
cacheDb.disciplineDao().delete(it)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return Discipline(
|
|
||||||
it.name,
|
|
||||||
it.oid,
|
|
||||||
it.type
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
data class KindOfWorkDB(
|
data class KindOfWorkDB(
|
||||||
@@ -182,7 +124,7 @@ data class KindOfWorkDB(
|
|||||||
@ColumnInfo val name: String,
|
@ColumnInfo val name: String,
|
||||||
@ColumnInfo val uid: String,
|
@ColumnInfo val uid: String,
|
||||||
@ColumnInfo val complexity: Int,
|
@ColumnInfo val complexity: Int,
|
||||||
@ColumnInfo val expiredAt: String
|
@ColumnInfo val updatedAt: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
@@ -191,43 +133,21 @@ interface KindOfWorkDao {
|
|||||||
fun get(oid: Int): KindOfWorkDB?
|
fun get(oid: Int): KindOfWorkDB?
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
fun insert(kindOfWork: KindOfWorkDB)
|
suspend fun insert(kindOfWork: List<KindOfWorkDB>)
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
fun delete(kindOfWork: KindOfWorkDB)
|
fun delete(kindOfWork: KindOfWorkDB)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cacheKindOfWork(kindOfWork: KindOfWork) {
|
fun KindOfWorkDB.toModel() = KindOfWork(name, oid, uid, complexity)
|
||||||
cacheDb.kindOfWorkDao().insert(
|
fun KindOfWork.toEntity() = KindOfWorkDB(
|
||||||
KindOfWorkDB(
|
oid,
|
||||||
kindOfWork.oid,
|
name,
|
||||||
kindOfWork.name,
|
uid,
|
||||||
kindOfWork.uid,
|
complexity,
|
||||||
kindOfWork.complexity,
|
LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)
|
||||||
LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME)
|
)
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadKindOfWork(oid: Int): KindOfWork? {
|
|
||||||
return cacheDb.kindOfWorkDao().get(oid)?.let {
|
|
||||||
if (LocalDateTime.parse(
|
|
||||||
it.expiredAt,
|
|
||||||
DateTimeFormatter.ISO_DATE_TIME
|
|
||||||
) > LocalDateTime.now()
|
|
||||||
) {
|
|
||||||
cacheDb.kindOfWorkDao().delete(it)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return KindOfWork(
|
|
||||||
it.name,
|
|
||||||
it.oid,
|
|
||||||
it.uid,
|
|
||||||
it.complexity
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
data class LecturerDB(
|
data class LecturerDB(
|
||||||
@@ -236,7 +156,7 @@ data class LecturerDB(
|
|||||||
@ColumnInfo val rank: LecturerRank,
|
@ColumnInfo val rank: LecturerRank,
|
||||||
@ColumnInfo val email: String,
|
@ColumnInfo val email: String,
|
||||||
@ColumnInfo val uid: String,
|
@ColumnInfo val uid: String,
|
||||||
@ColumnInfo val expiredAt: String
|
@ColumnInfo val updatedAt: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
@@ -245,45 +165,22 @@ interface LecturerDao {
|
|||||||
fun get(unnId: Int): LecturerDB?
|
fun get(unnId: Int): LecturerDB?
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
fun insert(lecturer: LecturerDB)
|
suspend fun insert(lecturers: List<LecturerDB>)
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
fun delete(lecturer: LecturerDB)
|
fun delete(lecturer: LecturerDB)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cacheLecturer(lecturer: Lecturer) {
|
fun LecturerDB.toModel() = Lecturer(name, rank, email, unnId, uid)
|
||||||
cacheDb.lecturerDao().insert(
|
fun Lecturer.toEntity() = LecturerDB(
|
||||||
LecturerDB(
|
unnId,
|
||||||
lecturer.unnId,
|
name,
|
||||||
lecturer.name,
|
rank,
|
||||||
lecturer.rank,
|
email,
|
||||||
lecturer.email,
|
uid,
|
||||||
lecturer.uid,
|
LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)
|
||||||
LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME)
|
)
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadLecturer(unnId: Int): Lecturer? {
|
|
||||||
return cacheDb.lecturerDao().get(unnId)?.let {
|
|
||||||
if (LocalDateTime.parse(
|
|
||||||
it.expiredAt,
|
|
||||||
DateTimeFormatter.ISO_DATE_TIME
|
|
||||||
) > LocalDateTime.now()
|
|
||||||
) {
|
|
||||||
cacheDb.lecturerDao().delete(it)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return Lecturer(
|
|
||||||
it.name,
|
|
||||||
it.rank,
|
|
||||||
it.email,
|
|
||||||
it.unnId,
|
|
||||||
it.uid
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
data class ScheduleUnitDB(
|
data class ScheduleUnitDB(
|
||||||
@@ -296,9 +193,9 @@ data class ScheduleUnitDB(
|
|||||||
@ColumnInfo val auditoriumOid: Int,
|
@ColumnInfo val auditoriumOid: Int,
|
||||||
@ColumnInfo val disciplineOid: Int,
|
@ColumnInfo val disciplineOid: Int,
|
||||||
@ColumnInfo val kindOfWorkOid: Int,
|
@ColumnInfo val kindOfWorkOid: Int,
|
||||||
@ColumnInfo val lecturerId: Int, // TODO: many-to-many
|
@ColumnInfo val lecturersId: String,
|
||||||
|
|
||||||
@ColumnInfo val expiredAt: String
|
@ColumnInfo val updatedAt: String
|
||||||
)
|
)
|
||||||
@Dao
|
@Dao
|
||||||
interface ScheduleItemDao {
|
interface ScheduleItemDao {
|
||||||
@@ -308,78 +205,52 @@ interface ScheduleItemDao {
|
|||||||
@Query("SELECT * FROM scheduleUnitDB WHERE date = :date ORDER BY `begin`")
|
@Query("SELECT * FROM scheduleUnitDB WHERE date = :date ORDER BY `begin`")
|
||||||
fun getSchedule(date: String): List<ScheduleUnitDB>
|
fun getSchedule(date: String): List<ScheduleUnitDB>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM scheduleUnitDB WHERE date = :date ORDER BY `begin`")
|
||||||
|
fun getScheduleFlow(date: String): Flow<List<ScheduleUnitDB>>
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
fun insert(user: ScheduleUnitDB)
|
suspend fun insert(units: List<ScheduleUnitDB>)
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
fun delete(user: ScheduleUnitDB)
|
fun delete(unit: ScheduleUnitDB)
|
||||||
|
|
||||||
|
@Query("DELETE FROM scheduleUnitDB WHERE date = :date")
|
||||||
|
fun deleteByDate(date: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cacheSchedule(item: ScheduleUnit) {
|
fun ScheduleUnitDB.toModel(): ScheduleUnit? {
|
||||||
cacheAuditorium(item.auditorium)
|
return ScheduleUnit(
|
||||||
cacheDiscipline(item.discipline)
|
oid,
|
||||||
cacheKindOfWork(item.kindOfWork)
|
cacheDb.auditoriumDao().get(auditoriumOid)?.toModel() ?: return null,
|
||||||
cacheLecturer(item.lecturers[0])
|
LocalDate.parse(date, DateTimeFormatter.ISO_DATE),
|
||||||
|
cacheDb.disciplineDao().get(disciplineOid)?.toModel() ?: return null,
|
||||||
cacheDb.scheduleDao().insert(
|
cacheDb.kindOfWorkDao().get(kindOfWorkOid)?.toModel() ?: return null,
|
||||||
ScheduleUnitDB(
|
lecturersId.split(",").map { cacheDb.lecturerDao().get(it.toInt())?.toModel() ?: return null },
|
||||||
item.oid,
|
stream,
|
||||||
item.date.format(DateTimeFormatter.ISO_DATE),
|
LocalTime.parse(begin, DateTimeFormatter.ISO_TIME),
|
||||||
item.stream,
|
LocalTime.parse(end, DateTimeFormatter.ISO_TIME)
|
||||||
item.begin.format(DateTimeFormatter.ISO_TIME),
|
|
||||||
item.end.format(DateTimeFormatter.ISO_TIME),
|
|
||||||
item.auditorium.oid,
|
|
||||||
item.discipline.oid,
|
|
||||||
item.kindOfWork.oid,
|
|
||||||
item.lecturers[0].unnId,
|
|
||||||
when {
|
|
||||||
(LocalDate.now() > item.date) -> LocalDateTime.now().plusWeeks(2)
|
|
||||||
(LocalDate.now().plusWeeks(1) > item.date) -> LocalDateTime.now().plusDays(1)
|
|
||||||
(LocalDate.now().plusWeeks(2) > item.date) -> LocalDateTime.now().plusWeeks(1)
|
|
||||||
else -> LocalDateTime.now().plusWeeks(2)
|
|
||||||
}.format(DateTimeFormatter.ISO_DATE_TIME)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
fun ScheduleUnit.toEntity() = ScheduleUnitDB(
|
||||||
|
oid,
|
||||||
|
date.format(DateTimeFormatter.ISO_DATE),
|
||||||
|
stream,
|
||||||
|
begin.format(DateTimeFormatter.ISO_TIME),
|
||||||
|
end.format(DateTimeFormatter.ISO_TIME),
|
||||||
|
auditorium.oid,
|
||||||
|
discipline.oid,
|
||||||
|
kindOfWork.oid,
|
||||||
|
lecturers.map { it.unnId }.joinToString(","),
|
||||||
|
LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)
|
||||||
|
)
|
||||||
|
|
||||||
fun cacheSchedule(items: ArrayList<ScheduleUnit>) {
|
suspend fun cacheSchedule(units: List<ScheduleUnit>) {
|
||||||
for (item in items)
|
cacheAuditorium(units.map { it.auditorium })
|
||||||
cacheSchedule(item)
|
cacheDb.disciplineDao().insert(units.map { it.discipline.toEntity() })
|
||||||
|
cacheDb.kindOfWorkDao().insert(units.map { it.kindOfWork.toEntity() })
|
||||||
|
cacheDb.lecturerDao().insert(units.flatMap { it.lecturers }.map { it.toEntity() })
|
||||||
|
cacheDb.scheduleDao().insert(units.map { it.toEntity() })
|
||||||
}
|
}
|
||||||
|
fun loadSchedule(date: LocalDate): List<ScheduleUnit> =
|
||||||
fun loadSchedule(oid: Int): ScheduleUnit? {
|
|
||||||
cacheDb.scheduleDao().getSchedule(oid)?.let {
|
|
||||||
Log.d("load", it.oid.toString())
|
|
||||||
if (LocalDateTime.parse(
|
|
||||||
it.expiredAt,
|
|
||||||
DateTimeFormatter.ISO_DATE_TIME
|
|
||||||
) < LocalDateTime.now()
|
|
||||||
) {
|
|
||||||
Log.d("delete", it.oid.toString())
|
|
||||||
cacheDb.scheduleDao().delete(it)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return ScheduleUnit(
|
|
||||||
it.oid,
|
|
||||||
loadAuditorium(it.auditoriumOid) ?: return null,
|
|
||||||
LocalDate.parse(it.date, DateTimeFormatter.ISO_DATE),
|
|
||||||
loadDiscipline(it.disciplineOid) ?: return null,
|
|
||||||
loadKindOfWork(it.kindOfWorkOid) ?: return null,
|
|
||||||
arrayListOf(loadLecturer(it.lecturerId) ?: return null),
|
|
||||||
it.stream,
|
|
||||||
LocalTime.parse(it.begin, DateTimeFormatter.ISO_TIME),
|
|
||||||
LocalTime.parse(it.end, DateTimeFormatter.ISO_TIME)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadSchedule(date: LocalDate): ArrayList<ScheduleUnit> {
|
|
||||||
cacheDb.scheduleDao().getSchedule(date.format(DateTimeFormatter.ISO_DATE))
|
cacheDb.scheduleDao().getSchedule(date.format(DateTimeFormatter.ISO_DATE))
|
||||||
.map { Log.d("meow", "${it.oid}: ${loadSchedule(it.oid)}") }
|
.mapNotNull { it.toModel() }
|
||||||
return ArrayList(
|
|
||||||
cacheDb.scheduleDao().getSchedule(date.format(DateTimeFormatter.ISO_DATE))
|
|
||||||
.mapNotNull { loadSchedule(it.oid) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
package ru.sweetbread.unn.ui.composes
|
package ru.sweetbread.unn.ui.composes
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.animateColorAsState
|
import androidx.compose.animation.animateColorAsState
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
@@ -24,12 +23,11 @@ import androidx.compose.material3.Surface
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -43,9 +41,22 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import ru.sweetbread.unn.R
|
import ru.sweetbread.unn.R
|
||||||
import ru.sweetbread.unn.api.Auditorium
|
import ru.sweetbread.unn.api.Auditorium
|
||||||
import ru.sweetbread.unn.api.Building
|
import ru.sweetbread.unn.api.Building
|
||||||
@@ -55,6 +66,8 @@ import ru.sweetbread.unn.api.Lecturer
|
|||||||
import ru.sweetbread.unn.api.LecturerRank
|
import ru.sweetbread.unn.api.LecturerRank
|
||||||
import ru.sweetbread.unn.api.ScheduleUnit
|
import ru.sweetbread.unn.api.ScheduleUnit
|
||||||
import ru.sweetbread.unn.api.getScheduleDay
|
import ru.sweetbread.unn.api.getScheduleDay
|
||||||
|
import ru.sweetbread.unn.db.cacheDb
|
||||||
|
import ru.sweetbread.unn.db.toModel
|
||||||
import ru.sweetbread.unn.ui.theme.UNNTheme
|
import ru.sweetbread.unn.ui.theme.UNNTheme
|
||||||
import splitties.resources.appStr
|
import splitties.resources.appStr
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
@@ -64,58 +77,105 @@ import java.time.ZoneId
|
|||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
|
||||||
|
|
||||||
|
class ScheduleRepository {
|
||||||
|
fun getFromDb(date: LocalDate): List<ScheduleUnit> =
|
||||||
|
cacheDb.scheduleDao()
|
||||||
|
.getSchedule(date.format(DateTimeFormatter.ISO_DATE))
|
||||||
|
.mapNotNull { it.toModel() }
|
||||||
|
suspend fun getFromApi(date: LocalDate): List<ScheduleUnit> = getScheduleDay(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class ScheduleState {
|
||||||
|
object Loading : ScheduleState()
|
||||||
|
data class Success(val data: List<ScheduleUnit>) : ScheduleState()
|
||||||
|
data class Error(val message: String) : ScheduleState()
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScheduleViewModel : ViewModel() {
|
||||||
|
private val repository = ScheduleRepository()
|
||||||
|
|
||||||
|
private val _date = MutableStateFlow(LocalDate.now())
|
||||||
|
val date: StateFlow<LocalDate> = _date.asStateFlow()
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
val scheduleState: StateFlow<ScheduleState> = _date
|
||||||
|
.flatMapLatest { date ->
|
||||||
|
flow {
|
||||||
|
emit(ScheduleState.Loading)
|
||||||
|
val cached = withContext(Dispatchers.IO) {
|
||||||
|
repository.getFromDb(date)
|
||||||
|
}
|
||||||
|
emit(ScheduleState.Success(cached))
|
||||||
|
|
||||||
|
try {
|
||||||
|
val fresh = repository.getFromApi(date)
|
||||||
|
if (fresh != cached) {
|
||||||
|
emit(ScheduleState.Success(fresh))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
emit(ScheduleState.Error("Ошибка загрузки: ${e.message}"))
|
||||||
|
}
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
}
|
||||||
|
.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(5000),
|
||||||
|
initialValue = ScheduleState.Loading
|
||||||
|
)
|
||||||
|
|
||||||
|
fun updateDate(newDate: LocalDate) {
|
||||||
|
_date.update { newDate }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Schedule() {
|
fun Schedule() {
|
||||||
var selectedDate by remember { mutableStateOf(LocalDate.now()) }
|
val viewModel: ScheduleViewModel = viewModel()
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
BoundedWeekPicker(
|
BoundedWeekPicker(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 8.dp),
|
.padding(vertical = 8.dp),
|
||||||
selectedDate = selectedDate,
|
selectedDate = viewModel.date.collectAsState().value,
|
||||||
onDateSelected = { selectedDate = it }
|
onDateSelected = { viewModel.updateDate(it) }
|
||||||
)
|
)
|
||||||
|
|
||||||
ScheduleDay(date = selectedDate)
|
ScheduleDay(viewModel = viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ScheduleDay(modifier: Modifier = Modifier, date: LocalDate) {
|
fun ScheduleDay(modifier: Modifier = Modifier, viewModel: ScheduleViewModel) {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
var loadedDate by remember { mutableStateOf(LocalDate.MIN) }
|
|
||||||
val lessons = remember { mutableListOf<ScheduleUnit>() }
|
|
||||||
var expanded by remember { mutableIntStateOf(0) }
|
var expanded by remember { mutableIntStateOf(0) }
|
||||||
|
val state by viewModel.scheduleState.collectAsState()
|
||||||
|
|
||||||
if (loadedDate == date) {
|
when (state) {
|
||||||
Log.d("Loaded", "${date.format(DateTimeFormatter.ISO_DATE)} ${lessons.size}")
|
is ScheduleState.Success -> {
|
||||||
LazyColumn (modifier) {
|
val lessons = (state as ScheduleState.Success).data
|
||||||
items(lessons) {
|
LazyColumn(modifier) {
|
||||||
ScheduleItem(unit = it, modifier = Modifier.clickable {
|
items(lessons) {
|
||||||
expanded = if (it.oid == expanded) 0
|
ScheduleItem(unit = it, modifier = Modifier.clickable {
|
||||||
else it.oid
|
expanded = if (it.oid == expanded) 0
|
||||||
}, expanded = expanded == it.oid)
|
else it.oid
|
||||||
}
|
}, expanded = expanded == it.oid)
|
||||||
|
}
|
||||||
|
|
||||||
if (lessons.isEmpty())
|
if (lessons.isEmpty())
|
||||||
item { Text(appStr(R.string.noData)) }
|
item { Text(appStr(R.string.noData)) }
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LinearProgressIndicator(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(8.dp),
|
|
||||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
|
||||||
trackColor = MaterialTheme.colorScheme.secondary,
|
|
||||||
)
|
|
||||||
LaunchedEffect(date != loadedDate) {
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
lessons.clear()
|
|
||||||
lessons.addAll(getScheduleDay(date = date))
|
|
||||||
loadedDate = date
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is ScheduleState.Loading ->
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp),
|
||||||
|
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
trackColor = MaterialTheme.colorScheme.secondary,
|
||||||
|
)
|
||||||
|
is ScheduleState.Error ->
|
||||||
|
Text((state as ScheduleState.Error).message, color = MaterialTheme.colorScheme.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -3,6 +3,6 @@
|
|||||||
#Sat Mar 16 18:30:45 MSK 2024
|
#Sat Mar 16 18:30:45 MSK 2024
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
Reference in New Issue
Block a user