wip: Migrate to Trixnity

This commit is contained in:
2026-02-21 21:21:57 +03:00
parent c7b5f20c06
commit dd3b31d0b2
5 changed files with 269 additions and 36 deletions
@@ -12,9 +12,6 @@ import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import net.folivo.trixnity.client.MatrixClient
//import ru.risdeveau.pixeldragon.api.MatrixSyncService
//import ru.risdeveau.pixeldragon.api.getMe
import splitties.preferences.Preferences
val webClient = HttpClient {
install(Logging) {
@@ -29,25 +26,4 @@ val webClient = HttpClient {
install(HttpCache)
}
var client: MatrixClient? = null
object AccountData : Preferences("system_parameters") {
var token by stringOrNullPref("token", null)
var userId by stringOrNullPref("user_id", null)
var homeserver by stringOrNullPref("homeserver", null)
var syncLastBatch by stringOrNullPref("next_batch", null)
var filter by stringOrNullPref("filter", null)
}
//val syncService = MatrixSyncService()
//suspend fun initCheck(): Boolean {
// Log.d("initCheck", "checking...")
//
// token = AccountData.token ?: return false
// homeserver = AccountData.homeserver ?: return false
//
// baseUrl = "$homeserver/_matrix/client/v3"
//
// return getMe() != null
//}
var client: MatrixClient? = null
@@ -30,9 +30,13 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import net.folivo.trixnity.client.MatrixClient
import net.folivo.trixnity.client.fromStore
import ru.risdeveau.pixeldragon.client
import ru.risdeveau.pixeldragon.ui.layout.RoomList
import ru.risdeveau.pixeldragon.ui.theme.PixelDragonTheme
import ru.risdeveau.pixeldragon.util.getMediaStore
import ru.risdeveau.pixeldragon.util.getRoomStore
@@ -70,9 +74,10 @@ class MainActivity : ComponentActivity() {
) { innerPadding ->
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "none") {
composable("none") { }
composable("rooms") { /*RoomList(Modifier.padding(innerPadding), navController)*/ }
NavHost(navController = navController, startDestination = "rooms") {
composable("rooms") {
RoomList(Modifier.padding(innerPadding), navController)
}
composable(
"room/{rid}",
arguments = listOf(navArgument("rid") { type = NavType.StringType })
@@ -109,9 +114,17 @@ class MainActivity : ComponentActivity() {
}
Log.i("MainActivity", "Log in as ${client!!.userId}")
client!!.startSync()
isClientReady = true
}
}
}
}
override fun onDestroy() {
super.onDestroy()
CoroutineScope(Dispatchers.Main).launch {
client?.stopSync()
}
}
}
@@ -1,4 +1,129 @@
/*
* Created by sweetbread
* Copyright (c) 2026. All rights reserved.
*/
*/
package ru.risdeveau.pixeldragon.ui.item
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Warning
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import coil3.compose.rememberAsyncImagePainter
import coil3.request.ImageRequest
import kotlinx.coroutines.flow.MutableStateFlow
import net.folivo.trixnity.client.media
import net.folivo.trixnity.clientserverapi.model.media.FileTransferProgress
import ru.risdeveau.pixeldragon.client
enum class ImageLoadState {
Loading, Success, Error
}
@Composable
fun MXCImage(
mxcUrl: String,
modifier: Modifier = Modifier,
contentScale: ContentScale = ContentScale.Fit,
contentDescription: String = "",
showProgress: Boolean = true
) {
val context = LocalContext.current
var imageLoadState by remember { mutableStateOf(ImageLoadState.Loading) }
var imageBytes by remember { mutableStateOf<ByteArray?>(null) }
val progressFlow = remember { MutableStateFlow<FileTransferProgress?>(null) }
LaunchedEffect(mxcUrl) {
if (mxcUrl.isBlank()) {
imageLoadState = ImageLoadState.Error
return@LaunchedEffect
}
imageLoadState = ImageLoadState.Loading
progressFlow.value = null
val result = client!!.media.getMedia(
uri = mxcUrl,
progress = progressFlow.takeIf { showProgress },
saveToCache = true
)
imageLoadState = result.fold(
onSuccess = { media ->
val bytes = media.toByteArray()
if (bytes != null) {
imageBytes = bytes
ImageLoadState.Success
} else {
ImageLoadState.Error
}
},
onFailure = {
ImageLoadState.Error
}
)
}
val progress by progressFlow.collectAsState()
val showProgressIndicator = showProgress &&
imageLoadState == ImageLoadState.Loading &&
progress != null
val painter = rememberAsyncImagePainter(
model = ImageRequest.Builder(context)
.data(imageBytes)
.build()
)
Box(modifier = modifier, contentAlignment = Alignment.Center) {
if (imageBytes != null) {
Image(
painter = painter,
contentDescription = contentDescription,
contentScale = contentScale,
modifier = Modifier.fillMaxSize()
)
}
when {
showProgressIndicator -> {
progress?.let { p ->
val percent = p.total?.let { p.transferred.toFloat() / it }
if (percent == null) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
} else {
CircularProgressIndicator(
progress = percent,
modifier = Modifier.align(Alignment.Center)
)
}
} ?: CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
imageLoadState == ImageLoadState.Loading -> {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
imageLoadState == ImageLoadState.Error -> {
Icon(
Icons.Outlined.Warning,
contentDescription = "Error",
modifier = Modifier.align(Alignment.Center)
)
}
}
}
}
@@ -1,4 +1,128 @@
/*
* Created by sweetbread
* Copyright (c) 2026. All rights reserved.
*/
*/
package ru.risdeveau.pixeldragon.ui.layout
import android.annotation.SuppressLint
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import kotlinx.coroutines.flow.map
import net.folivo.trixnity.client.flattenValues
import net.folivo.trixnity.client.room
import net.folivo.trixnity.client.store.Room
import net.folivo.trixnity.client.store.type
import net.folivo.trixnity.core.model.events.m.room.CreateEventContent
import ru.risdeveau.pixeldragon.client
import ru.risdeveau.pixeldragon.ui.item.MXCImage
@SuppressLint("FlowOperatorInvokedInComposition")
@Composable
fun RoomList(modifier: Modifier = Modifier, navController: NavController) {
val rooms by client!!.room.getAll().flattenValues().map { it.toList() }.collectAsState(initial = emptyList())
val listState = rememberLazyListState()
// if (itemState.scrollToTop) {
// LaunchedEffect(coroutineScope) {
// Log.e("TAG", "TopCoinsScreen: scrollToTop" )
// listState.scrollToItem(0)
// }
// }
// LaunchedEffect(Unit) {
// list = withContext(Dispatchers.IO) { Room.getJoined() }
// }
LazyColumn(modifier = modifier, state = listState) {
items(rooms) { room ->
RoomItem(room = room, navController = navController )
}
item {
if (rooms.isEmpty()) {
Text("You have no rooms")
}
}
}
}
@Composable
fun RoomItem(modifier: Modifier = Modifier, room: Room, navController: NavController) {
val name = room.name
val isSpace = room.type == CreateEventContent.RoomType.Space
Row(
modifier
.padding(8.dp)
.height((52 + 8 * 2).dp)
.fillMaxWidth()
.background(
color =
if (isSpace)
MaterialTheme.colorScheme.tertiary
else
MaterialTheme.colorScheme.background
)
.clip(RoundedCornerShape(12.dp))
.clickable {
if (isSpace)
navController.navigate("space/${room.roomId}")
else
navController.navigate("room/${room.roomId}")
}
.padding(8.dp)
) {
Log.v("RoomItem", room.avatarUrl.toString())
room.avatarUrl?.let { mxc ->
MXCImage(
mxc,
modifier = Modifier
.padding(end = 4.dp)
.height(52.dp)
.width(52.dp)
.let {
if (isSpace)
it.clip(RoundedCornerShape(12.dp))
else
it.clip(CircleShape)
},
ContentScale.Crop
)
}
Column {
(name?.explicitName ?: name?.heroes?.firstNotNullOf {it.localpart})?.let {
Text(
it,
maxLines = 1,
color = MaterialTheme.colorScheme.primary,
fontSize = MaterialTheme.typography.titleLarge.fontSize
)
}
}
}
}