forked from agzam/vyatsu-schedule
refactor: database multi-period schedule storage
This commit is contained in:
@@ -2,16 +2,20 @@ package agzam.vyatsu.data.local
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
|
||||
@Database(
|
||||
entities = [
|
||||
GroupEntity::class,
|
||||
ScheduleEntity::class,
|
||||
DayEntity::class,
|
||||
LessonEntity::class,
|
||||
PreferenceEntity::class
|
||||
],
|
||||
version = 1,
|
||||
exportSchema = false
|
||||
version = 2,
|
||||
exportSchema = true
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun groupDao(): GroupDao
|
||||
abstract fun scheduleDao(): ScheduleDao
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package agzam.vyatsu.data.local
|
||||
|
||||
import agzam.vyatsu.domain.model.LessonType
|
||||
import androidx.room.TypeConverter
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
|
||||
class Converters {
|
||||
|
||||
@TypeConverter
|
||||
fun localDateToEpochDay(date: LocalDate?): Long? = date?.toEpochDay()
|
||||
|
||||
@TypeConverter
|
||||
fun epochDayToLocalDate(epochDay: Long?): LocalDate? = epochDay?.let(LocalDate::ofEpochDay)
|
||||
|
||||
@TypeConverter
|
||||
fun localTimeToMinutes(time: LocalTime?): Int? = time?.let { it.hour * 60 + it.minute }
|
||||
|
||||
@TypeConverter
|
||||
fun minutesToLocalTime(minutes: Int?): LocalTime? =
|
||||
minutes?.let { LocalTime.of(it / 60, it % 60) }
|
||||
|
||||
@TypeConverter
|
||||
fun lessonTypeToString(type: LessonType): String = type.name
|
||||
|
||||
@TypeConverter
|
||||
fun stringToLessonType(value: String): LessonType =
|
||||
runCatching { LessonType.valueOf(value) }.getOrDefault(LessonType.OTHER)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package agzam.vyatsu.data.local
|
||||
|
||||
import agzam.vyatsu.domain.model.Day
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.time.LocalDate
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class DayDto(
|
||||
val date: String,
|
||||
val slots: List<SlotDto> = emptyList()
|
||||
) {
|
||||
fun toDomain(): Day = Day(
|
||||
date = LocalDate.parse(date),
|
||||
intervals = slots.map { it.toDomain() }
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun fromDomain(d: Day): DayDto = DayDto(
|
||||
date = d.date.toString(),
|
||||
slots = d.intervals.map { SlotDto.fromDomain(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package agzam.vyatsu.data.local
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(
|
||||
tableName = "days",
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = ScheduleEntity::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["scheduleId"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)
|
||||
],
|
||||
indices = [
|
||||
Index("scheduleId"),
|
||||
Index("date")
|
||||
]
|
||||
)
|
||||
data class DayEntity(
|
||||
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
val scheduleId: Long,
|
||||
val date: Long
|
||||
)
|
||||
@@ -8,9 +8,9 @@ data class GroupEntity(
|
||||
@PrimaryKey val id: String,
|
||||
val name: String,
|
||||
val faculty: String,
|
||||
val autumnFirst: String,
|
||||
val autumnSecond: String,
|
||||
val springFirst: String,
|
||||
val springSecond: String,
|
||||
val autumnFirst: Long?,
|
||||
val autumnSecond: Long?,
|
||||
val springFirst: Long?,
|
||||
val springSecond: Long?,
|
||||
val lastSyncedAt: Long
|
||||
)
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package agzam.vyatsu.data.local
|
||||
|
||||
import agzam.vyatsu.domain.model.Lesson
|
||||
import agzam.vyatsu.domain.model.LessonType
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class LessonDto(
|
||||
val raw: String = "",
|
||||
val room: String? = null,
|
||||
val group: String? = null,
|
||||
val subgroup: String? = null,
|
||||
val subject: String? = null,
|
||||
val type: String = "",
|
||||
val teacher: String? = null
|
||||
) {
|
||||
fun toDomain(): Lesson = Lesson(
|
||||
raw = raw,
|
||||
room = room?.takeIf { it.isNotBlank() },
|
||||
group = group?.takeIf { it.isNotBlank() },
|
||||
subgroup = subgroup?.takeIf { it.isNotBlank() },
|
||||
subject = subject?.takeIf { it.isNotBlank() },
|
||||
type = runCatching { LessonType.valueOf(type) }.getOrDefault(LessonType.OTHER),
|
||||
teacher = teacher?.takeIf { it.isNotBlank() }
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun fromDomain(l: Lesson): LessonDto = LessonDto(
|
||||
raw = l.raw,
|
||||
room = l.room,
|
||||
group = l.group,
|
||||
subgroup = l.subgroup,
|
||||
subject = l.subject,
|
||||
type = l.type.name,
|
||||
teacher = l.teacher
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package agzam.vyatsu.data.local
|
||||
|
||||
import agzam.vyatsu.domain.model.LessonType
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(
|
||||
tableName = "lessons",
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = DayEntity::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["dayId"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)
|
||||
],
|
||||
indices = [
|
||||
Index("dayId"),
|
||||
Index("date"),
|
||||
Index("subject"),
|
||||
Index("room")
|
||||
]
|
||||
)
|
||||
data class LessonEntity(
|
||||
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
val dayId: Long,
|
||||
val date: Long,
|
||||
val slotIndex: Int,
|
||||
val start: Int,
|
||||
val end: Int,
|
||||
val lessonIndex: Int,
|
||||
val raw: String,
|
||||
val room: String?,
|
||||
val groupName: String?,
|
||||
val subgroup: String?,
|
||||
val subject: String?,
|
||||
val type: LessonType,
|
||||
val teacher: String?
|
||||
)
|
||||
@@ -1,25 +0,0 @@
|
||||
package agzam.vyatsu.data.local
|
||||
|
||||
import agzam.vyatsu.domain.model.Day
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
|
||||
class ScheduleCodec(moshi: Moshi) {
|
||||
private val type: java.lang.reflect.Type =
|
||||
Types.newParameterizedType(List::class.java, DayDto::class.java)
|
||||
private val adapter = moshi.adapter<List<DayDto>>(type)
|
||||
|
||||
fun toJson(days: List<Day>): String {
|
||||
val dto = days.map { DayDto.fromDomain(it) }
|
||||
return adapter.toJson(dto)
|
||||
}
|
||||
|
||||
fun fromJson(json: String): List<Day> {
|
||||
return try {
|
||||
val dto = adapter.fromJson(json) ?: return emptyList()
|
||||
dto.map { it.toDomain() }
|
||||
} catch (t: Throwable) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,15 +8,68 @@ import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface ScheduleDao {
|
||||
@Query("SELECT * FROM schedules WHERE groupId = :groupId")
|
||||
fun observe(groupId: String): Flow<ScheduleEntity?>
|
||||
|
||||
@Query("SELECT * FROM schedules WHERE groupId = :groupId")
|
||||
suspend fun get(groupId: String): ScheduleEntity?
|
||||
@Query("SELECT * FROM schedules WHERE groupId = :groupId ORDER BY weekStart DESC")
|
||||
fun observeSchedules(groupId: String): Flow<List<ScheduleEntity>>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun upsert(entity: ScheduleEntity)
|
||||
@Query("SELECT * FROM schedules WHERE groupId = :groupId ORDER BY weekStart DESC")
|
||||
suspend fun getSchedules(groupId: String): List<ScheduleEntity>
|
||||
|
||||
@Query("DELETE FROM days WHERE scheduleId = :scheduleId")
|
||||
suspend fun deleteDaysForSchedule(scheduleId: Long)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.ABORT)
|
||||
suspend fun insertSchedule(schedule: ScheduleEntity): Long
|
||||
|
||||
@Query("DELETE FROM schedules WHERE groupId = :groupId AND season = :season AND weekStart = :weekStart")
|
||||
suspend fun deleteSchedule(groupId: String, season: String, weekStart: Long)
|
||||
|
||||
@Query("DELETE FROM schedules WHERE groupId = :groupId")
|
||||
suspend fun delete(groupId: String)
|
||||
suspend fun deleteAll(groupId: String)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
DELETE FROM schedules
|
||||
WHERE groupId = :groupId
|
||||
AND id NOT IN (
|
||||
SELECT id FROM schedules
|
||||
WHERE groupId = :groupId
|
||||
ORDER BY weekStart DESC
|
||||
LIMIT :keep
|
||||
)
|
||||
"""
|
||||
)
|
||||
suspend fun trim(groupId: String, keep: Int)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertDays(days: List<DayEntity>)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertLessons(lessons: List<LessonEntity>)
|
||||
|
||||
@Query(
|
||||
"SELECT days.* FROM days INNER JOIN schedules ON schedules.id = days.scheduleId " +
|
||||
"WHERE schedules.groupId = :groupId ORDER BY days.date"
|
||||
)
|
||||
suspend fun getDaysForGroup(groupId: String): List<DayEntity>
|
||||
|
||||
@Query(
|
||||
"SELECT lessons.* FROM lessons INNER JOIN days ON days.id = lessons.dayId " +
|
||||
"INNER JOIN schedules ON schedules.id = days.scheduleId " +
|
||||
"WHERE schedules.groupId = :groupId ORDER BY lessons.date, lessons.slotIndex, lessons.lessonIndex"
|
||||
)
|
||||
suspend fun getLessonsForGroup(groupId: String): List<LessonEntity>
|
||||
|
||||
@Query(
|
||||
"SELECT days.* FROM days INNER JOIN schedules ON schedules.id = days.scheduleId " +
|
||||
"WHERE schedules.groupId = :groupId ORDER BY days.date"
|
||||
)
|
||||
fun observeDaysForGroup(groupId: String): Flow<List<DayEntity>>
|
||||
|
||||
@Query(
|
||||
"SELECT lessons.* FROM lessons INNER JOIN days ON days.id = lessons.dayId " +
|
||||
"INNER JOIN schedules ON schedules.id = days.scheduleId " +
|
||||
"WHERE schedules.groupId = :groupId ORDER BY lessons.date, lessons.slotIndex, lessons.lessonIndex"
|
||||
)
|
||||
fun observeLessonsForGroup(groupId: String): Flow<List<LessonEntity>>
|
||||
}
|
||||
|
||||
@@ -1,14 +1,31 @@
|
||||
package agzam.vyatsu.data.local
|
||||
|
||||
import agzam.vyatsu.domain.model.Season
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "schedules")
|
||||
@Entity(
|
||||
tableName = "schedules",
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = GroupEntity::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["groupId"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)
|
||||
],
|
||||
indices = [
|
||||
Index("groupId"),
|
||||
Index(value = ["groupId", "season", "weekStart"], unique = true)
|
||||
]
|
||||
)
|
||||
data class ScheduleEntity(
|
||||
@PrimaryKey val groupId: String,
|
||||
val season: String,
|
||||
val dateRangeFirst: String,
|
||||
val dateRangeSecond: String,
|
||||
val weeksJson: String,
|
||||
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
val groupId: String,
|
||||
val season: Season,
|
||||
val weekStart: Long,
|
||||
val weekEnd: Long,
|
||||
val lastSyncedAt: Long
|
||||
)
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package agzam.vyatsu.data.local
|
||||
|
||||
import agzam.vyatsu.domain.model.TimeSlot
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.time.LocalTime
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SlotDto(
|
||||
val start: String,
|
||||
val end: String,
|
||||
val lessons: List<LessonDto> = emptyList()
|
||||
) {
|
||||
fun toDomain(): TimeSlot = TimeSlot(
|
||||
start = LocalTime.parse(start),
|
||||
end = LocalTime.parse(end),
|
||||
lessons = lessons.map { it.toDomain() }
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun fromDomain(s: TimeSlot): SlotDto = SlotDto(
|
||||
start = s.start.toString(),
|
||||
end = s.end.toString(),
|
||||
lessons = s.lessons.map { LessonDto.fromDomain(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user