Compare commits
17 Commits
d7d14389fc
...
e10e47d6c0
| Author | SHA1 | Date | |
|---|---|---|---|
| e10e47d6c0 | |||
| 8f6adf0746 | |||
| eae2d4f388 | |||
| b312cc50f1 | |||
| 8b8926f931 | |||
| a35f5196b1 | |||
| cab1ff93a4 | |||
| 769319f308 | |||
| d28baa70d4 | |||
| f29672d1bd | |||
| dd30db2130 | |||
| 0711ce41d3 | |||
| b1357bd4e7 | |||
| 0e107eca0b | |||
| f90a8e3472 | |||
| aa18e5b0a6 | |||
| a21860c21d |
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
* Created by sweetbread
|
||||
* Copyright (c) 2026. All rights reserved.
|
||||
*/
|
||||
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
/*
|
||||
@@ -58,6 +63,7 @@ dependencies {
|
||||
implementation(libs.androidx.ui.graphics)
|
||||
implementation(libs.androidx.ui.tooling.preview)
|
||||
implementation(libs.androidx.material3)
|
||||
implementation(libs.androidx.compose.ui.unit)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
|
||||
@@ -5,41 +5,70 @@
|
||||
|
||||
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.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
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
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.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.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.clientserverapi.model.user.avatarUrl
|
||||
import de.connect2x.trixnity.clientserverapi.model.user.displayName
|
||||
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.client
|
||||
import ru.risdeveau.pixeldragon.ui.item.Avatar
|
||||
import ru.risdeveau.pixeldragon.ui.layout.Room
|
||||
import ru.risdeveau.pixeldragon.ui.layout.RoomList
|
||||
import ru.risdeveau.pixeldragon.ui.theme.PixelDragonTheme
|
||||
@@ -47,11 +76,10 @@ import ru.risdeveau.pixeldragon.util.getMediaStore
|
||||
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)
|
||||
|
||||
@@ -59,34 +87,28 @@ class MainActivity : ComponentActivity() {
|
||||
setContent {
|
||||
PixelDragonTheme {
|
||||
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()
|
||||
}
|
||||
} else {
|
||||
val navController = rememberNavController()
|
||||
val syncState by client!!.api.sync.currentSyncState
|
||||
.collectAsState(initial = SyncState.STOPPED)
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
PixelDragonTopBar(
|
||||
navController = navController,
|
||||
syncState = syncState,
|
||||
)
|
||||
},
|
||||
) { innerPadding ->
|
||||
val navController = rememberNavController()
|
||||
|
||||
NavHost(navController = navController, startDestination = "rooms") {
|
||||
composable("rooms") {
|
||||
RoomList(Modifier.padding(innerPadding), navController)
|
||||
@@ -102,7 +124,7 @@ class MainActivity : ComponentActivity() {
|
||||
composable(
|
||||
"space/{rid}",
|
||||
arguments = listOf(navArgument("rid") { type = NavType.StringType })
|
||||
) { navBackStackEntry ->
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(innerPadding),
|
||||
text = "Not implemented"
|
||||
@@ -142,3 +164,156 @@ 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))
|
||||
},
|
||||
actions = {
|
||||
val client = client!!
|
||||
|
||||
var userName by remember { mutableStateOf("?") }
|
||||
var userAvatar: String? by remember { mutableStateOf(null) }
|
||||
|
||||
LaunchedEffect(client, syncState) {
|
||||
val profile = client.api.user
|
||||
.getProfile(client.userId)
|
||||
.getOrNull()
|
||||
|
||||
userName = profile?.displayName ?: "?"
|
||||
userAvatar = profile?.avatarUrl
|
||||
}
|
||||
|
||||
Avatar(
|
||||
Modifier.size(32.dp).clip(CircleShape),
|
||||
userAvatar,
|
||||
userName,
|
||||
MaterialTheme.colorScheme.background
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@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,
|
||||
) {
|
||||
Avatar(
|
||||
Modifier
|
||||
.size(36.dp)
|
||||
.clip(avatarShape),
|
||||
room.avatarUrl,
|
||||
title
|
||||
)
|
||||
|
||||
Spacer(Modifier.width(10.dp))
|
||||
|
||||
Text(
|
||||
text = title,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Created by sweetbread
|
||||
* Copyright (c) 2026. All rights reserved.
|
||||
*/
|
||||
|
||||
package ru.risdeveau.pixeldragon.ui.item
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
|
||||
@Composable
|
||||
fun Avatar(
|
||||
modifier: Modifier,
|
||||
url: String?,
|
||||
fallbackName: String,
|
||||
fallbackColor: Color = MaterialTheme.colorScheme.secondaryContainer,
|
||||
) {
|
||||
if (url != null) {
|
||||
MXCImage(
|
||||
mxcUrl = url,
|
||||
modifier = modifier,
|
||||
contentScale = ContentScale.Crop,
|
||||
contentDescription = fallbackName,
|
||||
showProgress = false,
|
||||
)
|
||||
} else {
|
||||
AvatarPlaceholder(
|
||||
modifier,
|
||||
fallbackName,
|
||||
fallbackColor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AvatarPlaceholder(
|
||||
modifier: Modifier = Modifier,
|
||||
fallbackName: String,
|
||||
fallbackColor: Color,
|
||||
) {
|
||||
val initial = fallbackName
|
||||
.trim()
|
||||
.firstOrNull()
|
||||
?.uppercaseChar()
|
||||
?.toString()
|
||||
?: "?"
|
||||
|
||||
Box(
|
||||
modifier = modifier.background(fallbackColor),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = initial,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,7 @@ navigationCompose = "2.9.7"
|
||||
room = "2.6.1"
|
||||
splittiesFunPackAndroidBase = "3.0.0"
|
||||
trixnityClient = "5.2.0"
|
||||
uiUnit = "1.10.6"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
@@ -53,6 +54,7 @@ trixnity-client = { module = "de.connect2x.trixnity:trixnity-client", version.re
|
||||
trixnity-client-media-okio = { module = "de.connect2x.trixnity:trixnity-client-media-okio", version.ref = "trixnityClient" }
|
||||
trixnity-client-repository-room = { module = "de.connect2x.trixnity:trixnity-client-repository-room", version.ref = "trixnityClient" }
|
||||
trixnity-client-cryptodriver-vodozemac = { module = "de.connect2x.trixnity:trixnity-client-cryptodriver-vodozemac", version.ref = "trixnityClient" }
|
||||
androidx-compose-ui-unit = { group = "androidx.compose.ui", name = "ui-unit", version.ref = "uiUnit" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
|
||||
Reference in New Issue
Block a user