New TopBar

This commit is contained in:
2026-04-19 02:48:23 +03:00
parent 0711ce41d3
commit dd30db2130
@@ -5,13 +5,22 @@
package ru.risdeveau.pixeldragon.ui.activity
import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -19,27 +28,42 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
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.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import de.connect2x.trixnity.client.CryptoDriverModule
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import de.connect2x.trixnity.client.MatrixClient
import de.connect2x.trixnity.client.create
import de.connect2x.trixnity.client.cryptodriver.vodozemac.vodozemac
import de.connect2x.trixnity.client.flattenValues
import de.connect2x.trixnity.client.room
import de.connect2x.trixnity.client.store.type
import de.connect2x.trixnity.clientserverapi.client.SyncState
import de.connect2x.trixnity.core.model.events.m.room.CreateEventContent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import ru.risdeveau.pixeldragon.R
import ru.risdeveau.pixeldragon.client
import ru.risdeveau.pixeldragon.ui.item.MXCImage
import ru.risdeveau.pixeldragon.ui.layout.Room
import ru.risdeveau.pixeldragon.ui.layout.RoomList
import ru.risdeveau.pixeldragon.ui.theme.PixelDragonTheme
@@ -48,45 +72,41 @@ import ru.risdeveau.pixeldragon.util.getRoomStore
import splitties.activities.start
import splitties.init.appCtx
import splitties.resources.str
import de.connect2x.trixnity.client.store.Room as TrixnityRoom
class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
PixelDragonTheme {
var isClientReady by remember { mutableStateOf(false) }
val syncState by remember { mutableStateOf(SyncState.STOPPED) }
var matrixClient by remember { mutableStateOf(client) }
val currentClient = matrixClient
if (!isClientReady) {
CircularProgressIndicator()
if (currentClient == null) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator()
}
} else {
val navController = rememberNavController()
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
CenterAlignedTopAppBar(
colors = topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary,
),
title = {
when (syncState) {
SyncState.STARTED -> Text("Syncing...")
SyncState.INITIAL_SYNC -> Text("Initial sync...")
SyncState.STOPPED,
SyncState.RUNNING -> Text(str(R.string.app_name))
SyncState.TIMEOUT -> Text("No network connection")
SyncState.ERROR -> Text("Error syncing")
}
}
val syncState by currentClient.api.sync.currentSyncState
.collectAsState(initial = SyncState.STOPPED)
PixelDragonTopBar(
navController = navController,
matrixClient = currentClient,
syncState = syncState,
)
},
) { innerPadding ->
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "rooms") {
composable("rooms") {
RoomList(Modifier.padding(innerPadding), navController)
@@ -95,14 +115,17 @@ class MainActivity : ComponentActivity() {
"room/{rid}",
arguments = listOf(navArgument("rid") { type = NavType.StringType })
) { navBackStackEntry ->
Room(Modifier
.padding(innerPadding)
.fillMaxSize(), navBackStackEntry.arguments!!.getString("rid")!!)
Room(
Modifier
.padding(innerPadding)
.fillMaxSize(),
navBackStackEntry.arguments!!.getString("rid")!!,
)
}
composable(
"space/{rid}",
arguments = listOf(navArgument("rid") { type = NavType.StringType })
) { navBackStackEntry ->
) {
Text(
modifier = Modifier.padding(innerPadding),
text = "Not implemented"
@@ -127,9 +150,15 @@ class MainActivity : ComponentActivity() {
}
}
Log.i("MainActivity", "Log in as ${client!!.userId}")
client!!.startSync()
isClientReady = true
val readyClient = client ?: run {
start<Login>()
finish()
return@LaunchedEffect
}
matrixClient = readyClient
Log.i("MainActivity", "Log in as ${readyClient.userId}")
readyClient.startSync()
}
}
}
@@ -142,3 +171,127 @@ class MainActivity : ComponentActivity() {
}
}
}
@SuppressLint("FlowOperatorInvokedInComposition")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun PixelDragonTopBar(
navController: NavHostController,
matrixClient: MatrixClient,
syncState: SyncState,
) {
val backStackEntry by navController.currentBackStackEntryAsState()
val route = backStackEntry?.destination?.route
val rid = backStackEntry?.arguments?.getString("rid")
val roomsFlow = remember(matrixClient) {
matrixClient.room.getAll().flattenValues().map { it.toList() }
}
val rooms by roomsFlow.collectAsState(initial = emptyList())
val currentRoom = remember(route, rid, rooms) {
if ((route == "room/{rid}" || route == "space/{rid}") && rid != null) {
rooms.firstOrNull { it.roomId.toString() == rid }
} else {
null
}
}
CenterAlignedTopAppBar(
colors = topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary,
),
title = {
val statusTitle = syncState.toStatusTitle()
when {
statusTitle != null -> Text(statusTitle)
currentRoom != null -> RoomTopBarTitle(currentRoom)
else -> Text(appCtx.str(R.string.app_name))
}
},
)
}
@Composable
private fun RoomTopBarTitle(room: TrixnityRoom) {
val title = remember(room) { room.displayName() }
val isSpace = room.type == CreateEventContent.RoomType.Space
val avatarShape = if (isSpace) RoundedCornerShape(8.dp) else CircleShape
Row(
verticalAlignment = Alignment.CenterVertically,
) {
if (room.avatarUrl != null) {
MXCImage(
mxcUrl = room.avatarUrl!!,
modifier = Modifier
.size(36.dp)
.clip(avatarShape),
contentScale = ContentScale.Crop,
contentDescription = title,
showProgress = false,
)
} else {
RoomAvatarPlaceholder(
title = title,
modifier = Modifier.size(36.dp),
isSpace = isSpace,
)
}
Spacer(Modifier.width(10.dp))
Text(
text = title,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.titleMedium,
)
}
}
@Composable
private fun RoomAvatarPlaceholder(
title: String,
modifier: Modifier = Modifier,
isSpace: Boolean = false,
) {
val shape = if (isSpace) RoundedCornerShape(8.dp) else CircleShape
val initial = title
.trim()
.firstOrNull()
?.uppercaseChar()
?.toString()
?: "?"
Box(
modifier = modifier
.clip(shape)
.background(MaterialTheme.colorScheme.secondaryContainer),
contentAlignment = Alignment.Center,
) {
Text(
text = initial,
color = MaterialTheme.colorScheme.onSecondaryContainer,
style = MaterialTheme.typography.titleMedium,
)
}
}
private fun TrixnityRoom.displayName(): String {
return name?.explicitName
?: name?.heroes?.firstNotNullOfOrNull { it.localpart }
?: roomId.toString()
}
private fun SyncState.toStatusTitle(): String? {
return when (this) {
SyncState.INITIAL_SYNC -> "Initial sync..."
SyncState.STARTED -> "Syncing..."
SyncState.TIMEOUT -> "No network connection"
SyncState.ERROR -> "Error syncing"
SyncState.RUNNING,
SyncState.STOPPED -> null
}
}