ref: reformat caching data in Rooms

This commit is contained in:
2025-11-04 20:30:22 +03:00
parent 05fbfbac07
commit ebbc9a4b1f
9 changed files with 246 additions and 87 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ android {
defaultConfig {
applicationId = "ru.risdeveau.pixeldragon"
minSdk = 24
minSdk = 26
targetSdk = 35
versionCode = 1
versionName = "1.0"
@@ -6,15 +6,12 @@
package ru.risdeveau.pixeldragon
import android.util.Log
import androidx.room.Room
import io.ktor.client.HttpClient
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 ru.risdeveau.pixeldragon.api.getMe
import ru.risdeveau.pixeldragon.db.AppDatabase
import splitties.init.appCtx
import splitties.preferences.Preferences
val client = HttpClient {
@@ -51,8 +48,3 @@ suspend fun initCheck(): Boolean {
return getMe() != null
}
val db = Room.databaseBuilder(
appCtx,
AppDatabase::class.java, "database"
).build()
@@ -12,8 +12,8 @@ import io.ktor.http.HttpStatusCode
import org.json.JSONObject
import ru.risdeveau.pixeldragon.baseUrl
import ru.risdeveau.pixeldragon.client
import ru.risdeveau.pixeldragon.db
import ru.risdeveau.pixeldragon.db.Room
import ru.risdeveau.pixeldragon.repo.Room
import ru.risdeveau.pixeldragon.repo.User
import ru.risdeveau.pixeldragon.token
//fun getRooms(): List<Room> {
@@ -34,8 +34,6 @@ suspend fun getRooms(): List<String> {
}
suspend fun getRoom(rid: String): Room {
var room = db.roomDoa().getById(rid)
val direct = getAccountData(getMe()!!.userId, "m.direct")
var directWith = ""
direct?.let {
@@ -51,16 +49,21 @@ suspend fun getRoom(rid: String): Room {
}
}
if (room == null) {
val name = getState(rid, "m.room.name", "name")
val type = getState(rid, "m.room.create", "type") ?: "m.room"
val creator = getState(rid, "m.room.create", "creator")
val avatar = getState(rid, "m.room.avatar", "url")
room = Room(rid, name, type, creator, null, avatar, null, true, if (directWith.isNotEmpty()) directWith else null)
db.roomDoa().insert(room)
}
return room
return Room(
rid,
name,
type,
creator,
null,
avatar,
null,
true, // TODO: insert actual value
if (directWith.isNotEmpty()) User.getById(directWith) else null
)
}
private suspend fun getState(rid: String, state: String, key: String): String? {
+38
View File
@@ -0,0 +1,38 @@
/*
* Created by sweetbread
* Copyright (c) 2025. All rights reserved.
*/
package ru.risdeveau.pixeldragon.db
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverter
import splitties.init.appCtx
import java.time.Instant
@Database(entities = [RoomDB::class, SpaceToRoom::class, UserDB::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun roomDoa(): RoomDao
abstract fun userDoa(): UserDao
}
class Converters {
@TypeConverter
fun fromInstant(value: Instant?): String? {
return value?.toString()
}
@TypeConverter
fun toInstant(value: String?): Instant? {
return value?.let { Instant.parse(it) }
}
}
val cacheDb = Room.databaseBuilder(
appCtx,
AppDatabase::class.java, "cache"
)
.fallbackToDestructiveMigration()
.build()
@@ -6,7 +6,6 @@
package ru.risdeveau.pixeldragon.db
import androidx.room.Dao
import androidx.room.Database
import androidx.room.Delete
import androidx.room.Embedded
import androidx.room.Entity
@@ -15,11 +14,16 @@ import androidx.room.Junction
import androidx.room.PrimaryKey
import androidx.room.Query
import androidx.room.Relation
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import ru.risdeveau.pixeldragon.repo.Room
import ru.risdeveau.pixeldragon.repo.User
import java.time.Instant
@Entity
data class Room(
@PrimaryKey val roomId: String,
@Entity(tableName = "room")
@TypeConverters(Converters::class)
data class RoomDB (
@PrimaryKey val id: String,
val updatedAt: Instant,
val name: String?,
val type: String,
val creatorId: String?,
@@ -27,36 +31,56 @@ data class Room(
val avatarUrl: String?,
val members: Int?,
val joined: Boolean,
val direct: String?,
val direct: String?
)
fun RoomDB.isExpired(cacheDuration: Long = 60 * 60): Boolean {
return Instant.now().minusSeconds(cacheDuration) > updatedAt
}
suspend fun RoomDB.toDomain(): Room = Room(
id = id,
name = name,
type = type,
creatorId = creatorId,
createTime = createTime,
avatarUrl = avatarUrl,
members = members,
joined = joined,
direct = direct?.let { User.getById(it) }
)
fun Room.toEntity(): RoomDB = RoomDB(
id = id,
updatedAt = Instant.now(),
name = name,
type = type,
creatorId = creatorId,
createTime = createTime,
avatarUrl = avatarUrl,
members = members,
joined = joined,
direct = direct?.id
)
@Dao
interface RoomDao {
// @Query("SELECT * FROM room")
// fun getAll(): List<User>
// @Query("SELECT * FROM user WHERE uid IN (:userIds)")
// fun loadAllByIds(userIds: IntArray): List<User>
// @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
// "last_name LIKE :last LIMIT 1")
// fun findByName(first: String, last: String): User
@Query("SELECT * FROM room WHERE roomId LIKE :rid LIMIT 1")
fun getById(rid: String): Room?
@Query("SELECT * FROM room WHERE id LIKE :id LIMIT 1")
fun getById(id: String): RoomDB?
// @Transaction
// @Query("SELECT * FROM room WHERE ")
// fun getSpace(rid: String): Space
@Query("SELECT * FROM room WHERE joined = 1 AND roomId NOT IN (SELECT roomId FROM SpaceToRoom)")
fun getAllJoined(): List<Room>
@Query("SELECT * FROM room WHERE joined = 1 AND id NOT IN (SELECT id FROM SpaceToRoom)")
fun getAllJoined(): List<RoomDB>
@Insert
fun insert(vararg rooms: Room)
fun insert(vararg rooms: RoomDB)
@Delete
fun delete(room: Room)
fun delete(room: RoomDB)
}
@Entity(primaryKeys = ["spaceId", "roomId"])
@@ -66,16 +90,11 @@ data class SpaceToRoom(
)
data class Space(
@Embedded val space: Room,
@Embedded val space: RoomDB,
@Relation(
parentColumn = "spaceId",
entityColumn = "roomId",
associateBy = Junction(SpaceToRoom::class)
)
val children: List<Room>
val children: List<RoomDB>
)
@Database(entities = [Room::class, SpaceToRoom::class, User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun roomDoa(): RoomDao
}
@@ -1,18 +1,59 @@
/*
* Created by sweetbread on 22.02.2025, 15:45
* Created by sweetbread
* Copyright (c) 2025. All rights reserved.
* Last modified 21.02.2025, 13:38
*/
package ru.risdeveau.pixeldragon.db
import androidx.room.ColumnInfo
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Entity
import androidx.room.Insert
import androidx.room.PrimaryKey
import androidx.room.Query
import androidx.room.TypeConverters
import org.json.JSONObject
import ru.risdeveau.pixeldragon.repo.User
import java.time.Instant
@Entity
data class User(
@PrimaryKey val uid: String,
@ColumnInfo(name = "name") val name: String?,
@ColumnInfo(name = "avatar") val avatar: String?
@Entity(tableName = "user")
@TypeConverters(Converters::class)
data class UserDB (
@PrimaryKey val id: String,
val updatedAt: Instant,
val name: String?,
val avatar: String?,
val attrs: String,
)
fun UserDB.isExpired(cacheDuration: Long = 60 * 60): Boolean {
return Instant.now().minusSeconds(cacheDuration) > updatedAt
}
fun UserDB.toDomain(): User = User(
id = id,
name = name,
avatarUrl = avatar,
attrs = JSONObject(attrs),
)
fun User.toEntity(): UserDB = UserDB(
id = id,
updatedAt = Instant.now(),
name = name,
avatar = avatarUrl,
attrs = attrs.toString().trim(),
)
@Dao
interface UserDao {
@Query("SELECT * FROM user WHERE id LIKE :id LIMIT 1")
fun getById(id: String): UserDB?
@Insert
fun insert(vararg users: UserDB)
@Delete
fun delete(user: UserDB)
}
+38
View File
@@ -0,0 +1,38 @@
/*
* Created by sweetbread
* Copyright (c) 2025. All rights reserved.
*/
package ru.risdeveau.pixeldragon.repo
import ru.risdeveau.pixeldragon.api.getRoom
import ru.risdeveau.pixeldragon.db.cacheDb
import ru.risdeveau.pixeldragon.db.isExpired
import ru.risdeveau.pixeldragon.db.toDomain
import ru.risdeveau.pixeldragon.db.toEntity
class Room (
val id: String,
val name: String?,
val type: String,
val creatorId: String?,
val createTime: Long?,
val avatarUrl: String?,
val members: Int?,
val joined: Boolean,
val direct: User?,
) {
companion object {
suspend fun getById(id: String, cached: Boolean = true): Room {
val cachedRoom = cacheDb.roomDoa().getById(id)
if ((cachedRoom == null) or (cachedRoom?.isExpired() == true)) {
val room = getRoom(id)
val cacheRoom = room.toEntity()
cacheDb.roomDoa().insert(cacheRoom)
return room
}
return cachedRoom!!.toDomain()
}
}
}
+43
View File
@@ -0,0 +1,43 @@
/*
* Created by sweetbread
* Copyright (c) 2025. All rights reserved.
*/
package ru.risdeveau.pixeldragon.repo
import android.util.Log
import org.json.JSONObject
import ru.risdeveau.pixeldragon.api.getUserProfile
import ru.risdeveau.pixeldragon.db.cacheDb
import ru.risdeveau.pixeldragon.db.isExpired
import ru.risdeveau.pixeldragon.db.toDomain
import ru.risdeveau.pixeldragon.db.toEntity
class User (
val id: String,
val name: String?,
val avatarUrl: String?,
val attrs: JSONObject
) {
companion object {
suspend fun getById(id: String): User? {
val cachedUser = cacheDb.userDoa().getById(id)
if ((cachedUser == null) or (cachedUser?.isExpired() == true)) {
val userProfile = getUserProfile(id)
if (userProfile == null) {
Log.i("User.getById", "User $id not found")
return null
}
val user = User(
id,
userProfile.displayName,
userProfile.avatarUrl,
userProfile.other
)
cacheDb.userDoa().insert(user.toEntity())
return user
}
return cachedUser!!.toDomain()
}
}
}
@@ -35,11 +35,8 @@ import androidx.navigation.NavController
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import ru.risdeveau.pixeldragon.api.UserProfile
import ru.risdeveau.pixeldragon.api.getRoom
import ru.risdeveau.pixeldragon.api.getRooms
import ru.risdeveau.pixeldragon.api.getUserProfile
import ru.risdeveau.pixeldragon.db.Room
import ru.risdeveau.pixeldragon.repo.Room
import ru.risdeveau.pixeldragon.ui.item.MXCImage
@Composable
@@ -79,31 +76,19 @@ fun RoomList(modifier: Modifier = Modifier, navController: NavController) {
@Composable
fun RoomItem(modifier: Modifier = Modifier, rid: String, navController: NavController) {
var room by remember { mutableStateOf<Room?>(null) }
var directUser by remember { mutableStateOf<UserProfile?>(null) }
val scope = rememberCoroutineScope()
LaunchedEffect(true) {
scope.launch {
withContext(Dispatchers.IO) {
room = getRoom(rid)
}
}
}
LaunchedEffect(room?.direct) {
room?.let {
it.direct?.let { uid ->
scope.launch {
withContext(Dispatchers.IO) {
directUser = getUserProfile(uid)
}
}
room = Room.getById(rid)
}
}
}
if (room != null) {
val avatarUrl = room!!.avatarUrl ?: (directUser?.avatarUrl ?: "")
val room = room!!
val avatarUrl = room.avatarUrl ?: (room.direct?.avatarUrl ?: "")
Row(
modifier
@@ -112,14 +97,14 @@ fun RoomItem(modifier: Modifier = Modifier, rid: String, navController: NavContr
.fillMaxWidth()
.background(
color =
if (room!!.type == "m.space")
if (room.type == "m.space")
MaterialTheme.colorScheme.tertiary
else
MaterialTheme.colorScheme.background
)
.clip(RoundedCornerShape(12.dp))
.clickable {
if (room!!.type == "m.space")
if (room.type == "m.space")
navController.navigate("space/$rid")
else
navController.navigate("room/$rid")
@@ -133,16 +118,16 @@ fun RoomItem(modifier: Modifier = Modifier, rid: String, navController: NavContr
.height(52.dp)
.width(52.dp)
.let {
if (room!!.type == "m.space")
if (room.type == "m.space")
it.clip(RoundedCornerShape(12.dp))
else
it.clip(CircleShape)
}
)
Column {
Text(room!!.type)
Text(room.type)
Text(
room!!.name ?: "Unnamed",
room.name ?: "Unnamed",
maxLines = 1,
color = MaterialTheme.colorScheme.primary,
fontSize = MaterialTheme.typography.titleLarge.fontSize