feat: channel routing

we can now switch between servers and channels, and there is a global drawer to do exactly that
the app is really starting to take shape at this point!
This commit is contained in:
Infi 2022-12-27 04:58:52 +01:00
parent dc3e71083b
commit 2a14f5fdc9
8 changed files with 221 additions and 133 deletions

View File

@ -16,8 +16,7 @@ import chat.revolt.screens.SplashScreen
import chat.revolt.screens.about.AboutScreen import chat.revolt.screens.about.AboutScreen
import chat.revolt.screens.about.AttributionScreen import chat.revolt.screens.about.AttributionScreen
import chat.revolt.screens.about.PlaceholderScreen import chat.revolt.screens.about.PlaceholderScreen
import chat.revolt.screens.chat.ChannelScreen import chat.revolt.screens.chat.ChatRouterScreen
import chat.revolt.screens.chat.HomeScreen
import chat.revolt.screens.login.GreeterScreen import chat.revolt.screens.login.GreeterScreen
import chat.revolt.screens.login.LoginScreen import chat.revolt.screens.login.LoginScreen
import chat.revolt.screens.login.MfaScreen import chat.revolt.screens.login.MfaScreen
@ -92,12 +91,7 @@ fun AppEntrypoint() {
MfaScreen(navController, allowedAuthTypes, mfaTicket) MfaScreen(navController, allowedAuthTypes, mfaTicket)
} }
composable("chat/home") { HomeScreen(navController) } composable("chat") { ChatRouterScreen(navController) }
composable("chat/channel/{channelId}") { backStackEntry ->
val channelId = backStackEntry.arguments?.getString("channelId") ?: ""
ChannelScreen(navController, channelId)
}
composable("about") { AboutScreen(navController) } composable("about") { AboutScreen(navController) }
composable("about/oss") { AttributionScreen(navController) } composable("about/oss") { AttributionScreen(navController) }

View File

@ -80,7 +80,7 @@ fun SplashScreen(navController: NavController, viewModel: SplashScreenViewModel
} }
} }
"home" -> { "home" -> {
navController.navigate("chat/home") { navController.navigate("chat") {
popUpTo("splash") { popUpTo("splash") {
inclusive = true inclusive = true
} }

View File

@ -0,0 +1,132 @@
package chat.revolt.screens.chat
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import chat.revolt.api.REVOLT_FILES
import chat.revolt.api.RevoltAPI
import chat.revolt.components.generic.RemoteImage
import chat.revolt.components.generic.drawableResource
import chat.revolt.screens.chat.views.HomeScreen
import chat.revolt.R
import chat.revolt.screens.chat.views.ChannelScreen
import kotlinx.coroutines.launch
class ChatRouterViewModel : ViewModel() {
private var _currentServer =
mutableStateOf(RevoltAPI.serverCache.values.firstOrNull()?.id ?: "home")
val currentServer: String
get() = _currentServer.value
fun setCurrentServer(serverId: String) {
_currentServer.value = serverId
}
fun goToHome() {
_currentServer.value = "home"
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = viewModel()) {
val channelDrawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
val navController = rememberNavController()
DismissibleNavigationDrawer(drawerState = channelDrawerState, drawerContent = {
ModalDrawerSheet {
Column(Modifier.fillMaxWidth()) {
Row {
Column(Modifier.verticalScroll(rememberScrollState())) {
RemoteImage(
url = drawableResource(R.drawable.ic_launcher_monochrome),
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.clickable { viewModel.goToHome() },
description = "Home",
)
RevoltAPI.serverCache.values.forEach { server ->
server.icon?.let { icon ->
RemoteImage(
url = "$REVOLT_FILES/icons/${icon.id!!}/server.png?max_side=256",
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.clickable { viewModel.setCurrentServer(server.id!!) },
description = "${server.name}"
)
}
}
}
Column(
Modifier
.weight(1f)
) {
if (viewModel.currentServer != "home") {
val server = RevoltAPI.serverCache[viewModel.currentServer]
Text(
text = server?.name ?: "Unknown Server",
fontWeight = FontWeight.Black,
fontSize = 24.sp
)
Column(
Modifier
.weight(1f)
.verticalScroll(rememberScrollState())
) {
server?.channels?.forEach { channelId ->
RevoltAPI.channelCache[channelId]?.let {
Text(
text = it.name ?: "Unnamed Channel",
modifier = Modifier.clickable {
scope.launch { channelDrawerState.close() }
navController.navigate("channel/${it.id}")
}
)
}
}
}
} else {
Text(text = "Home not implemented!")
}
}
}
}
}
}) {
Column(Modifier.fillMaxSize()) {
NavHost(navController = navController, startDestination = "home") {
composable("home") {
HomeScreen(navController = navController)
}
composable("channel/{channelId}") { backStackEntry ->
val channelId = backStackEntry.arguments?.getString("channelId")
if (channelId != null) {
ChannelScreen(navController, channelId = channelId)
}
}
}
}
}
}

View File

@ -1,121 +0,0 @@
package chat.revolt.screens.chat
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController
import chat.revolt.api.RevoltAPI
import chat.revolt.persistence.KVStorage
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import javax.inject.Inject
@HiltViewModel
class HomeScreenViewModel @Inject constructor(
private val kvStorage: KVStorage
) : ViewModel() {
private var _messageContent by mutableStateOf("")
val messageContent: String
get() = _messageContent
fun setMessageContent(value: String) {
_messageContent = value
}
fun logout() {
runBlocking {
kvStorage.remove("sessionToken")
RevoltAPI.logout()
}
}
fun sendMessage() {
viewModelScope.launch {
chat.revolt.api.routes.channel.sendMessage(
"01F7ZSBSFHCAAJQ92ZGTY67HMN", // revolt lounge #general (temporarily hardcoded) FIXME
messageContent
)
}
setMessageContent("")
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen(navController: NavController, viewModel: HomeScreenViewModel = hiltViewModel()) {
val channelDrawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
DismissibleNavigationDrawer(drawerState = channelDrawerState, drawerContent = {
ModalDrawerSheet {
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Revolt Lounge",
style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.Black)
)
Divider()
Column(Modifier.verticalScroll(rememberScrollState())) {
RevoltAPI.channelCache.values
.filter { channel ->
channel.server == "01F7ZSBSFHQ8TA81725KQCSDDP"
}
.forEach { channel ->
NavigationDrawerItem(
selected = false,
label = { Text(text = "#" + channel.name) },
onClick = {
scope.launch {
channelDrawerState.close()
navController.navigate("chat/channel/${channel.id}")
}
},
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
)
}
}
}
}) {
Column() {
Text(
text = "Home (placeholder)",
style = MaterialTheme.typography.displaySmall.copy(
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Left,
fontSize = 24.sp
),
modifier = Modifier
.padding(horizontal = 15.dp, vertical = 15.dp)
.fillMaxWidth(),
)
Button(
onClick = {
viewModel.logout()
navController.navigate("login/greeting") {
popUpTo("chat/home") {
inclusive = true
}
}
},
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 30.dp, top = 5.dp, start = 20.dp, end = 20.dp)
) {
Text("Logout")
}
}
}
}

View File

@ -1,4 +1,4 @@
package chat.revolt.screens.chat package chat.revolt.screens.chat.views
import android.util.Log import android.util.Log
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility

View File

@ -0,0 +1,83 @@
package chat.revolt.screens.chat.views
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController
import chat.revolt.api.RevoltAPI
import chat.revolt.persistence.KVStorage
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import javax.inject.Inject
@HiltViewModel
class HomeScreenViewModel @Inject constructor(
private val kvStorage: KVStorage
) : ViewModel() {
private var _messageContent by mutableStateOf("")
val messageContent: String
get() = _messageContent
fun setMessageContent(value: String) {
_messageContent = value
}
fun logout() {
runBlocking {
kvStorage.remove("sessionToken")
RevoltAPI.logout()
}
}
fun sendMessage() {
viewModelScope.launch {
chat.revolt.api.routes.channel.sendMessage(
"01F7ZSBSFHCAAJQ92ZGTY67HMN", // revolt lounge #general (temporarily hardcoded) FIXME
messageContent
)
}
setMessageContent("")
}
}
@Composable
fun HomeScreen(navController: NavController, viewModel: HomeScreenViewModel = hiltViewModel()) {
Column() {
Text(
text = "Home (placeholder)",
style = MaterialTheme.typography.displaySmall.copy(
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Left,
fontSize = 24.sp
),
modifier = Modifier
.padding(horizontal = 15.dp, vertical = 15.dp)
.fillMaxWidth(),
)
Button(
onClick = {
viewModel.logout()
navController.navigate("login/greeting") {
popUpTo("chat") {
inclusive = true
}
}
},
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 30.dp, top = 5.dp, start = 20.dp, end = 20.dp)
) {
Text("Logout")
}
}
}

View File

@ -116,7 +116,7 @@ fun LoginScreen(
) )
viewModel.navigationComplete() viewModel.navigationComplete()
} else if (viewModel.navigateTo == "home") { } else if (viewModel.navigateTo == "home") {
navController.navigate("chat/home") { navController.navigate("chat") {
popUpTo("login/greeting") { inclusive = true } popUpTo("login/greeting") { inclusive = true }
} }
viewModel.navigationComplete() viewModel.navigationComplete()

View File

@ -127,7 +127,7 @@ fun MfaScreen(
val allowedAuthTypes = allowedAuthTypesCommaSep.split(",") val allowedAuthTypes = allowedAuthTypesCommaSep.split(",")
if (viewModel.navigateToHome) { if (viewModel.navigateToHome) {
navController.navigate("chat/home") { navController.navigate("chat") {
popUpTo("login/greeting") { inclusive = true } popUpTo("login/greeting") { inclusive = true }
} }
viewModel.navigationComplete() viewModel.navigationComplete()