feat: show room avatar when is DM

This commit is contained in:
2025-11-04 16:01:14 +03:00
parent 5897d31a51
commit 05fbfbac07
6 changed files with 98 additions and 44 deletions
@@ -1,7 +1,6 @@
/* /*
* Created by sweetbread * Created by sweetbread
* Copyright (c) 2025. All rights reserved. * Copyright (c) 2025. All rights reserved.
* Last modified 03.03.2025, 18:28
*/ */
package ru.risdeveau.pixeldragon.api package ru.risdeveau.pixeldragon.api
@@ -36,12 +35,28 @@ suspend fun getRooms(): List<String> {
suspend fun getRoom(rid: String): Room { suspend fun getRoom(rid: String): Room {
var room = db.roomDoa().getById(rid) var room = db.roomDoa().getById(rid)
val direct = getAccountData(getMe()!!.userId, "m.direct")
var directWith = ""
direct?.let {
for (user in direct.keys()) {
val roomsWithUser = direct.getJSONArray(user)
for (i in 0 until roomsWithUser.length()) {
if (rid == roomsWithUser.getString(i)) {
directWith = user
break
}
}
if (directWith.isNotEmpty()) break
}
}
if (room == null) { if (room == null) {
val name = getState(rid, "m.room.name", "name") val name = getState(rid, "m.room.name", "name")
val type = getState(rid, "m.room.create", "type") ?: "m.room" val type = getState(rid, "m.room.create", "type") ?: "m.room"
val creator = getState(rid, "m.room.create", "creator") val creator = getState(rid, "m.room.create", "creator")
val avatar = getState(rid, "m.room.avatar", "url") val avatar = getState(rid, "m.room.avatar", "url")
room = Room(rid, name, type, creator, null, avatar, null, true) room = Room(rid, name, type, creator, null, avatar, null, true, if (directWith.isNotEmpty()) directWith else null)
db.roomDoa().insert(room) db.roomDoa().insert(room)
} }
@@ -1,7 +1,6 @@
/* /*
* Created by sweetbread * Created by sweetbread
* Copyright (c) 2025. All rights reserved. * Copyright (c) 2025. All rights reserved.
* Last modified 03.03.2025, 15:40
*/ */
package ru.risdeveau.pixeldragon.api package ru.risdeveau.pixeldragon.api
@@ -28,9 +27,11 @@ suspend fun getHomeserver(url: String): String? {
return homeserver return homeserver
} }
fun mxcToUrl(mxc: String): String { fun mxcToUrl(mxc: String): String? {
val pattern = Regex("mxc://([-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6})/([a-zA-Z]+)") val pattern = Regex("mxc://([-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6})/([a-zA-Z0-9]+)")
val match = pattern.find(mxc) val match = pattern.find(mxc)
if ((match?.groupValues[1] == null) or (match?.groupValues[2] == null)) return null
return "$homeserver/_matrix/client/v1/media/download/${match?.groupValues[1]}/${match?.groupValues[2]}" return "$homeserver/_matrix/client/v1/media/download/${match?.groupValues[1]}/${match?.groupValues[2]}"
} }
@@ -25,6 +25,7 @@ import splitties.init.appCtx
import splitties.preferences.edit import splitties.preferences.edit
data class Me (val userId: String, val deviceId: String) data class Me (val userId: String, val deviceId: String)
data class UserProfile (val displayName: String, val avatarUrl: String, val other: JSONObject)
/** /**
* This func is to validate the token * This func is to validate the token
@@ -82,3 +83,18 @@ suspend fun login(server: String, login: String, pass: String): Boolean {
return initCheck() return initCheck()
} }
suspend fun getAccountData(user: String, state: String): JSONObject? {
val r = client.get("$baseUrl/user/$user/account_data/$state") { bearerAuth(token) }
if (r.status != HttpStatusCode.OK) return null
return JSONObject(r.bodyAsText())
}
suspend fun getUserProfile(userId: String): UserProfile? {
val r = client.get("$baseUrl/profile/$userId") { bearerAuth(token) }
if (r.status != HttpStatusCode.OK) return null
val json = JSONObject(r.bodyAsText())
val name = json.optString("displayname", ""); json.remove("displayname")
val avatar = json.optString("avatar_url", ""); json.remove("avatar_url")
return UserProfile(name, avatar, json)
}
@@ -1,7 +1,6 @@
/* /*
* Created by sweetbread * Created by sweetbread
* Copyright (c) 2025. All rights reserved. * Copyright (c) 2025. All rights reserved.
* Last modified 22.02.2025, 19:49
*/ */
package ru.risdeveau.pixeldragon.db package ru.risdeveau.pixeldragon.db
@@ -27,7 +26,8 @@ data class Room(
val createTime: Long?, val createTime: Long?,
val avatarUrl: String?, val avatarUrl: String?,
val members: Int?, val members: Int?,
val joined: Boolean val joined: Boolean,
val direct: String?,
) )
@Dao @Dao
@@ -15,6 +15,7 @@ import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import coil3.compose.AsyncImagePainter import coil3.compose.AsyncImagePainter
@@ -38,40 +39,43 @@ fun MXCImage(
contentScale: ContentScale = ContentScale.Fit, contentScale: ContentScale = ContentScale.Fit,
contentDescription: String = "" contentDescription: String = ""
) { ) {
val loadState = remember { mutableStateOf(ImageLoadState.Loading) } mxcToUrl(mxcUrl)?.let { url ->
val painter = rememberAsyncImagePainter( val loadState = remember { mutableStateOf(ImageLoadState.Loading) }
model = ImageRequest.Builder(appCtx) val painter = rememberAsyncImagePainter(
.data(mxcToUrl(mxcUrl)) model = ImageRequest.Builder(appCtx)
.httpHeaders( .data(url)
NetworkHeaders.Builder() .httpHeaders(
.set("Authorization", "Bearer $token") NetworkHeaders.Builder()
.set("Cache-Control", "max-age=86400") .set("Authorization", "Bearer $token")
.build() .set("Cache-Control", "max-age=86400")
) .build()
.build(), )
onState = { state -> .build(),
loadState.value = when (state) { onState = { state ->
is AsyncImagePainter.State.Loading -> ImageLoadState.Loading loadState.value = when (state) {
is AsyncImagePainter.State.Success -> ImageLoadState.Success is AsyncImagePainter.State.Loading -> ImageLoadState.Loading
else -> ImageLoadState.Error is AsyncImagePainter.State.Success -> ImageLoadState.Success
else -> ImageLoadState.Error
}
} }
}
)
Box(
modifier = modifier.fillMaxSize()
) {
Image(
painter = painter,
contentDescription = contentDescription,
contentScale = contentScale,
modifier = Modifier.fillMaxSize()
) )
when (loadState.value) { Box(
ImageLoadState.Loading -> CircularProgressIndicator() modifier = modifier.fillMaxSize(),
ImageLoadState.Error -> Icon(Icons.Outlined.Warning, "Error") contentAlignment = Alignment.Center
ImageLoadState.Success -> Unit ) {
Image(
painter = painter,
contentDescription = contentDescription,
contentScale = contentScale,
modifier = Modifier.fillMaxSize()
)
when (loadState.value) {
ImageLoadState.Loading -> CircularProgressIndicator()
ImageLoadState.Error -> Icon(Icons.Outlined.Warning, "Error")
ImageLoadState.Success -> Unit
}
} }
} }
} }
@@ -35,8 +35,10 @@ import androidx.navigation.NavController
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import ru.risdeveau.pixeldragon.api.UserProfile
import ru.risdeveau.pixeldragon.api.getRoom import ru.risdeveau.pixeldragon.api.getRoom
import ru.risdeveau.pixeldragon.api.getRooms import ru.risdeveau.pixeldragon.api.getRooms
import ru.risdeveau.pixeldragon.api.getUserProfile
import ru.risdeveau.pixeldragon.db.Room import ru.risdeveau.pixeldragon.db.Room
import ru.risdeveau.pixeldragon.ui.item.MXCImage import ru.risdeveau.pixeldragon.ui.item.MXCImage
@@ -77,6 +79,7 @@ fun RoomList(modifier: Modifier = Modifier, navController: NavController) {
@Composable @Composable
fun RoomItem(modifier: Modifier = Modifier, rid: String, navController: NavController) { fun RoomItem(modifier: Modifier = Modifier, rid: String, navController: NavController) {
var room by remember { mutableStateOf<Room?>(null) } var room by remember { mutableStateOf<Room?>(null) }
var directUser by remember { mutableStateOf<UserProfile?>(null) }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
LaunchedEffect(true) { LaunchedEffect(true) {
@@ -87,17 +90,32 @@ fun RoomItem(modifier: Modifier = Modifier, rid: String, navController: NavContr
} }
} }
LaunchedEffect(room?.direct) {
room?.let {
it.direct?.let { uid ->
scope.launch {
withContext(Dispatchers.IO) {
directUser = getUserProfile(uid)
}
}
}
}
}
if (room != null) { if (room != null) {
val avatarUrl = room!!.avatarUrl ?: (directUser?.avatarUrl ?: "")
Row( Row(
modifier modifier
.padding(8.dp) .padding(8.dp)
.height((52 + 8 * 2).dp) .height((52 + 8 * 2).dp)
.fillMaxWidth() .fillMaxWidth()
.background(color = .background(
if (room!!.type == "m.space") color =
MaterialTheme.colorScheme.tertiary if (room!!.type == "m.space")
else MaterialTheme.colorScheme.tertiary
MaterialTheme.colorScheme.background else
MaterialTheme.colorScheme.background
) )
.clip(RoundedCornerShape(12.dp)) .clip(RoundedCornerShape(12.dp))
.clickable { .clickable {
@@ -109,7 +127,7 @@ fun RoomItem(modifier: Modifier = Modifier, rid: String, navController: NavContr
.padding(8.dp) .padding(8.dp)
) { ) {
MXCImage( MXCImage(
room!!.avatarUrl ?: "", avatarUrl,
modifier = Modifier modifier = Modifier
.padding(end = 4.dp) .padding(end = 4.dp)
.height(52.dp) .height(52.dp)