New TopBar
This commit is contained in:
@@ -5,41 +5,70 @@
|
|||||||
|
|
||||||
package ru.risdeveau.pixeldragon.ui.activity
|
package ru.risdeveau.pixeldragon.ui.activity
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
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.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
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.CenterAlignedTopAppBar
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
|
import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.NavType
|
import androidx.navigation.NavType
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
import de.connect2x.trixnity.client.CryptoDriverModule
|
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.MatrixClient
|
||||||
import de.connect2x.trixnity.client.create
|
import de.connect2x.trixnity.client.create
|
||||||
import de.connect2x.trixnity.client.cryptodriver.vodozemac.vodozemac
|
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.clientserverapi.client.SyncState
|
||||||
|
import de.connect2x.trixnity.core.model.events.m.room.CreateEventContent
|
||||||
|
import io.github.rabehx.iconsax.Iconsax
|
||||||
|
import io.github.rabehx.iconsax.automirrored.outline.ArrowLeft2
|
||||||
|
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.R
|
||||||
import ru.risdeveau.pixeldragon.client
|
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.Room
|
||||||
import ru.risdeveau.pixeldragon.ui.layout.RoomList
|
import ru.risdeveau.pixeldragon.ui.layout.RoomList
|
||||||
import ru.risdeveau.pixeldragon.ui.theme.PixelDragonTheme
|
import ru.risdeveau.pixeldragon.ui.theme.PixelDragonTheme
|
||||||
@@ -47,11 +76,10 @@ import ru.risdeveau.pixeldragon.util.getMediaStore
|
|||||||
import ru.risdeveau.pixeldragon.util.getRoomStore
|
import ru.risdeveau.pixeldragon.util.getRoomStore
|
||||||
import splitties.activities.start
|
import splitties.activities.start
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import splitties.resources.str
|
import de.connect2x.trixnity.client.store.Room as TrixnityRoom
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
@@ -59,34 +87,28 @@ class MainActivity : ComponentActivity() {
|
|||||||
setContent {
|
setContent {
|
||||||
PixelDragonTheme {
|
PixelDragonTheme {
|
||||||
var isClientReady by remember { mutableStateOf(false) }
|
var isClientReady by remember { mutableStateOf(false) }
|
||||||
val syncState by remember { mutableStateOf(SyncState.STOPPED) }
|
|
||||||
|
|
||||||
if (!isClientReady) {
|
if (!isClientReady || client == null) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
CircularProgressIndicator()
|
CircularProgressIndicator()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
val navController = rememberNavController()
|
||||||
|
val syncState by client!!.api.sync.currentSyncState
|
||||||
|
.collectAsState(initial = SyncState.STOPPED)
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
topBar = {
|
topBar = {
|
||||||
CenterAlignedTopAppBar(
|
PixelDragonTopBar(
|
||||||
colors = topAppBarColors(
|
navController = navController,
|
||||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
syncState = syncState,
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
val navController = rememberNavController()
|
|
||||||
|
|
||||||
NavHost(navController = navController, startDestination = "rooms") {
|
NavHost(navController = navController, startDestination = "rooms") {
|
||||||
composable("rooms") {
|
composable("rooms") {
|
||||||
RoomList(Modifier.padding(innerPadding), navController)
|
RoomList(Modifier.padding(innerPadding), navController)
|
||||||
@@ -102,7 +124,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
composable(
|
composable(
|
||||||
"space/{rid}",
|
"space/{rid}",
|
||||||
arguments = listOf(navArgument("rid") { type = NavType.StringType })
|
arguments = listOf(navArgument("rid") { type = NavType.StringType })
|
||||||
) { navBackStackEntry ->
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(innerPadding),
|
modifier = Modifier.padding(innerPadding),
|
||||||
text = "Not implemented"
|
text = "Not implemented"
|
||||||
@@ -142,3 +164,172 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("FlowOperatorInvokedInComposition")
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun PixelDragonTopBar(
|
||||||
|
navController: NavHostController,
|
||||||
|
syncState: SyncState,
|
||||||
|
) {
|
||||||
|
val backStackEntry by navController.currentBackStackEntryAsState()
|
||||||
|
val route = backStackEntry?.destination?.route
|
||||||
|
val rid = backStackEntry?.arguments?.getString("rid")
|
||||||
|
val isRoomLikeScreen = route == "room/{rid}" || route == "space/{rid}"
|
||||||
|
|
||||||
|
val roomsFlow = remember(client) {
|
||||||
|
client!!.room.getAll().flattenValues().map { it.toList() }
|
||||||
|
}
|
||||||
|
val rooms by roomsFlow.collectAsState(initial = emptyList())
|
||||||
|
|
||||||
|
val currentRoom = remember(isRoomLikeScreen, rid, rooms) {
|
||||||
|
if (isRoomLikeScreen && rid != null) {
|
||||||
|
rooms.firstOrNull { it.roomId.toString() == rid }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRoomLikeScreen) {
|
||||||
|
RoomTopBar(
|
||||||
|
room = currentRoom,
|
||||||
|
fallbackTitle = rid ?: stringResource(R.string.app_name),
|
||||||
|
onBack = { navController.popBackStack() },
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
HomeTopBar(syncState = syncState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun HomeTopBar(syncState: SyncState) {
|
||||||
|
CenterAlignedTopAppBar(
|
||||||
|
colors = topAppBarColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
titleContentColor = MaterialTheme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
title = {
|
||||||
|
Text(syncState.toStatusTitle() ?: stringResource(R.string.app_name))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun RoomTopBar(
|
||||||
|
room: TrixnityRoom?,
|
||||||
|
fallbackTitle: String,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
CenterAlignedTopAppBar(
|
||||||
|
colors = topAppBarColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface.copy(.5f),
|
||||||
|
titleContentColor = MaterialTheme.colorScheme.onSurface,
|
||||||
|
navigationIconContentColor = MaterialTheme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = onBack) {
|
||||||
|
Icon(
|
||||||
|
Iconsax.AutoMirrored.Outline.ArrowLeft2,
|
||||||
|
"To home",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
if (room != null) {
|
||||||
|
RoomTopBarTitle(room)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = fallbackTitle,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user