feat: save last channel, show when no channel/s, less NPEs
This commit is contained in:
parent
7bf4d3aadf
commit
40a9cdf344
|
|
@ -19,7 +19,7 @@ object ULID {
|
||||||
0x59.toChar(), 0x5a.toChar()
|
0x59.toChar(), 0x5a.toChar()
|
||||||
)
|
)
|
||||||
|
|
||||||
fun makeSpecial(timestamp: Long, entropy: ByteArray): String {
|
fun makeSpecial(timestamp: Long, entropy: ByteArray = fetchEntropy()): String {
|
||||||
if (timestamp < minTimestamp || timestamp > maxTimestamp) {
|
if (timestamp < minTimestamp || timestamp > maxTimestamp) {
|
||||||
throw IllegalArgumentException("timestamp out of range: $timestamp")
|
throw IllegalArgumentException("timestamp out of range: $timestamp")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,17 @@ import chat.revolt.api.RevoltAPI
|
||||||
import chat.revolt.api.RevoltHttp
|
import chat.revolt.api.RevoltHttp
|
||||||
import chat.revolt.api.RevoltJson
|
import chat.revolt.api.RevoltJson
|
||||||
import chat.revolt.api.internals.ULID
|
import chat.revolt.api.internals.ULID
|
||||||
|
import chat.revolt.api.schemas.Channel
|
||||||
import chat.revolt.api.schemas.Message
|
import chat.revolt.api.schemas.Message
|
||||||
import chat.revolt.api.schemas.MessagesInChannel
|
import chat.revolt.api.schemas.MessagesInChannel
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.get
|
||||||
import io.ktor.client.statement.*
|
import io.ktor.client.request.parameter
|
||||||
import io.ktor.http.*
|
import io.ktor.client.request.post
|
||||||
|
import io.ktor.client.request.put
|
||||||
|
import io.ktor.client.request.setBody
|
||||||
|
import io.ktor.client.statement.bodyAsText
|
||||||
|
import io.ktor.http.ContentType
|
||||||
|
import io.ktor.http.contentType
|
||||||
import kotlinx.serialization.builtins.ListSerializer
|
import kotlinx.serialization.builtins.ListSerializer
|
||||||
|
|
||||||
suspend fun fetchMessagesFromChannel(
|
suspend fun fetchMessagesFromChannel(
|
||||||
|
|
@ -95,4 +101,16 @@ suspend fun ackChannel(channelId: String, messageId: String = ULID.makeNext()) {
|
||||||
RevoltHttp.put("/channels/$channelId/ack/$messageId") {
|
RevoltHttp.put("/channels/$channelId/ack/$messageId") {
|
||||||
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun fetchSingleChannel(channelId: String): Channel {
|
||||||
|
val response = RevoltHttp.get("/channels/$channelId") {
|
||||||
|
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
||||||
|
}
|
||||||
|
.bodyAsText()
|
||||||
|
|
||||||
|
return RevoltJson.decodeFromString(
|
||||||
|
Channel.serializer(),
|
||||||
|
response
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -5,8 +5,8 @@ import chat.revolt.api.RevoltError
|
||||||
import chat.revolt.api.RevoltHttp
|
import chat.revolt.api.RevoltHttp
|
||||||
import chat.revolt.api.RevoltJson
|
import chat.revolt.api.RevoltJson
|
||||||
import chat.revolt.api.schemas.User
|
import chat.revolt.api.schemas.User
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.get
|
||||||
import io.ktor.client.statement.*
|
import io.ktor.client.statement.bodyAsText
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
|
|
||||||
suspend fun fetchSelf(): User {
|
suspend fun fetchSelf(): User {
|
||||||
|
|
@ -50,7 +50,9 @@ suspend fun fetchUser(id: String): User {
|
||||||
|
|
||||||
val user = RevoltJson.decodeFromString(User.serializer(), response)
|
val user = RevoltJson.decodeFromString(User.serializer(), response)
|
||||||
|
|
||||||
RevoltAPI.userCache[user.id!!] = user
|
user.id?.let {
|
||||||
|
RevoltAPI.userCache[it] = user
|
||||||
|
}
|
||||||
|
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
@ -95,7 +96,7 @@ fun Message(
|
||||||
if (message.tail == false) {
|
if (message.tail == false) {
|
||||||
UserAvatar(
|
UserAvatar(
|
||||||
username = author.username ?: "",
|
username = author.username ?: "",
|
||||||
userId = author.id!!,
|
userId = author.id ?: message.id ?: ULID.makeSpecial(0),
|
||||||
avatar = author.avatar,
|
avatar = author.avatar,
|
||||||
rawUrl = message.masquerade?.avatar?.let { asJanuaryProxyUrl(it) }
|
rawUrl = message.masquerade?.avatar?.let { asJanuaryProxyUrl(it) }
|
||||||
)
|
)
|
||||||
|
|
@ -107,7 +108,9 @@ fun Message(
|
||||||
if (message.tail == false) {
|
if (message.tail == false) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text(
|
Text(
|
||||||
text = message.masquerade?.name ?: author.username ?: "",
|
text = message.masquerade?.name
|
||||||
|
?: author.username
|
||||||
|
?: stringResource(id = R.string.unknown),
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = if (message.masquerade?.colour != null) {
|
color = if (message.masquerade?.colour != null) {
|
||||||
WebCompat.parseColour(message.masquerade.colour)
|
WebCompat.parseColour(message.masquerade.colour)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package chat.revolt.components.screens.chat.drawer.channel
|
package chat.revolt.components.screens.chat.drawer.channel
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
|
@ -10,15 +12,14 @@ import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
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.draw.clip
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.navigation.NavController
|
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
|
||||||
import chat.revolt.R
|
import chat.revolt.R
|
||||||
import chat.revolt.api.RevoltAPI
|
import chat.revolt.api.RevoltAPI
|
||||||
import chat.revolt.api.schemas.ChannelType
|
import chat.revolt.api.schemas.ChannelType
|
||||||
|
|
@ -29,17 +30,19 @@ import kotlinx.coroutines.launch
|
||||||
@Composable
|
@Composable
|
||||||
fun RowScope.ChannelList(
|
fun RowScope.ChannelList(
|
||||||
serverId: String,
|
serverId: String,
|
||||||
navController: NavController,
|
drawerState: DoubleDrawerState,
|
||||||
drawerState: DoubleDrawerState
|
currentChannel: String?,
|
||||||
|
onChannelClick: (String) -> Unit,
|
||||||
|
onChannelLongClick: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
tonalElevation = 1.dp,
|
tonalElevation = 1.dp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 4.dp, top = 8.dp, bottom = 8.dp)
|
.padding(start = 4.dp, top = 8.dp, bottom = 8.dp)
|
||||||
.clip(RoundedCornerShape(16.dp))
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
@ -57,9 +60,7 @@ fun RowScope.ChannelList(
|
||||||
name = channel.name
|
name = channel.name
|
||||||
?: "GDM #${channel.id}",
|
?: "GDM #${channel.id}",
|
||||||
channelType = ChannelType.Group,
|
channelType = ChannelType.Group,
|
||||||
selected = (channel.id == navBackStackEntry?.arguments?.getString(
|
selected = currentChannel == channel.id,
|
||||||
"channelId"
|
|
||||||
)),
|
|
||||||
hasUnread = channel.lastMessageID?.let { lastMessageID ->
|
hasUnread = channel.lastMessageID?.let { lastMessageID ->
|
||||||
RevoltAPI.unreads.hasUnread(
|
RevoltAPI.unreads.hasUnread(
|
||||||
channel.id!!,
|
channel.id!!,
|
||||||
|
|
@ -67,15 +68,11 @@ fun RowScope.ChannelList(
|
||||||
)
|
)
|
||||||
} ?: false,
|
} ?: false,
|
||||||
onClick = {
|
onClick = {
|
||||||
navController.navigate("channel/${channel.id}") {
|
onChannelClick(channel.id ?: return@DrawerChannel)
|
||||||
navController.graph.startDestinationRoute?.let { route ->
|
|
||||||
popUpTo(route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
coroutineScope.launch { drawerState.focusCenter() }
|
coroutineScope.launch { drawerState.focusCenter() }
|
||||||
},
|
},
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
navController.navigate("channel/${channel.id}/info")
|
onChannelLongClick(channel.id ?: return@DrawerChannel)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -91,37 +88,52 @@ fun RowScope.ChannelList(
|
||||||
modifier = Modifier.padding(16.dp)
|
modifier = Modifier.padding(16.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(
|
if (server?.channels?.isEmpty() == true) {
|
||||||
Modifier
|
Column(
|
||||||
.weight(1f)
|
Modifier.weight(1f),
|
||||||
.verticalScroll(rememberScrollState())
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
verticalArrangement = Arrangement.Center
|
||||||
server?.channels?.forEach { channelId ->
|
) {
|
||||||
RevoltAPI.channelCache[channelId]?.let { ch ->
|
Text(
|
||||||
DrawerChannel(
|
text = stringResource(R.string.no_channels_heading),
|
||||||
name = ch.name!!,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
channelType = ch.channelType!!,
|
textAlign = TextAlign.Center,
|
||||||
selected = navBackStackEntry?.arguments?.getString(
|
fontSize = 24.sp,
|
||||||
"channelId"
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
) == ch.id,
|
)
|
||||||
hasUnread = ch.lastMessageID?.let { lastMessageID ->
|
Text(
|
||||||
RevoltAPI.unreads.hasUnread(
|
text = stringResource(R.string.no_channels_body),
|
||||||
ch.id!!,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
lastMessageID
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
} ?: true,
|
}
|
||||||
onClick = {
|
} else {
|
||||||
coroutineScope.launch { drawerState.focusCenter() }
|
Column(
|
||||||
navController.navigate("channel/${ch.id}") {
|
Modifier
|
||||||
navController.graph.startDestinationRoute?.let { route ->
|
.weight(1f)
|
||||||
popUpTo(route)
|
.verticalScroll(rememberScrollState())
|
||||||
}
|
) {
|
||||||
|
server?.channels?.forEach { channelId ->
|
||||||
|
RevoltAPI.channelCache[channelId]?.let { ch ->
|
||||||
|
DrawerChannel(
|
||||||
|
name = ch.name!!,
|
||||||
|
channelType = ch.channelType!!,
|
||||||
|
selected = currentChannel == ch.id,
|
||||||
|
hasUnread = ch.lastMessageID?.let { lastMessageID ->
|
||||||
|
RevoltAPI.unreads.hasUnread(
|
||||||
|
ch.id!!,
|
||||||
|
lastMessageID
|
||||||
|
)
|
||||||
|
} ?: true,
|
||||||
|
onClick = {
|
||||||
|
onChannelClick(ch.id ?: return@DrawerChannel)
|
||||||
|
coroutineScope.launch { drawerState.focusCenter() }
|
||||||
|
},
|
||||||
|
onLongClick = {
|
||||||
|
onChannelLongClick(ch.id ?: return@DrawerChannel)
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
onLongClick = {
|
}
|
||||||
navController.navigate("channel/${ch.id}/menu")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,9 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
|
|
@ -55,6 +56,7 @@ import chat.revolt.components.screens.chat.drawer.server.DrawerServer
|
||||||
import chat.revolt.components.screens.chat.drawer.server.DrawerServerlikeIcon
|
import chat.revolt.components.screens.chat.drawer.server.DrawerServerlikeIcon
|
||||||
import chat.revolt.components.screens.chat.drawer.server.ServerDrawerSeparator
|
import chat.revolt.components.screens.chat.drawer.server.ServerDrawerSeparator
|
||||||
import chat.revolt.components.screens.chat.rememberDoubleDrawerState
|
import chat.revolt.components.screens.chat.rememberDoubleDrawerState
|
||||||
|
import chat.revolt.persistence.KVStorage
|
||||||
import chat.revolt.screens.chat.dialogs.safety.ReportMessageDialog
|
import chat.revolt.screens.chat.dialogs.safety.ReportMessageDialog
|
||||||
import chat.revolt.screens.chat.sheets.ChannelContextSheet
|
import chat.revolt.screens.chat.sheets.ChannelContextSheet
|
||||||
import chat.revolt.screens.chat.sheets.ChannelInfoSheet
|
import chat.revolt.screens.chat.sheets.ChannelInfoSheet
|
||||||
|
|
@ -62,35 +64,80 @@ import chat.revolt.screens.chat.sheets.MessageContextSheet
|
||||||
import chat.revolt.screens.chat.sheets.StatusSheet
|
import chat.revolt.screens.chat.sheets.StatusSheet
|
||||||
import chat.revolt.screens.chat.views.ChannelScreen
|
import chat.revolt.screens.chat.views.ChannelScreen
|
||||||
import chat.revolt.screens.chat.views.HomeScreen
|
import chat.revolt.screens.chat.views.HomeScreen
|
||||||
|
import chat.revolt.screens.chat.views.NoCurrentChannelScreen
|
||||||
import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
|
import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
|
||||||
import com.google.accompanist.navigation.material.ModalBottomSheetLayout
|
import com.google.accompanist.navigation.material.ModalBottomSheetLayout
|
||||||
import com.google.accompanist.navigation.material.bottomSheet
|
import com.google.accompanist.navigation.material.bottomSheet
|
||||||
import com.google.accompanist.navigation.material.rememberBottomSheetNavigator
|
import com.google.accompanist.navigation.material.rememberBottomSheetNavigator
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ChatRouterViewModel : ViewModel() {
|
@HiltViewModel
|
||||||
|
class ChatRouterViewModel @Inject constructor(
|
||||||
|
private val kvStorage: KVStorage
|
||||||
|
) : ViewModel() {
|
||||||
private var _currentServer = mutableStateOf("home")
|
private var _currentServer = mutableStateOf("home")
|
||||||
val currentServer: String
|
val currentServer: String
|
||||||
get() = _currentServer.value
|
get() = _currentServer.value
|
||||||
|
|
||||||
fun setCurrentServer(serverId: String) {
|
private var _currentChannel = mutableStateOf<String?>(null)
|
||||||
|
val currentChannel: String?
|
||||||
|
get() = _currentChannel.value
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_currentServer.value = kvStorage.get("currentServer") ?: "home"
|
||||||
|
_currentChannel.value = kvStorage.get("currentChannel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setCurrentServer(serverId: String, save: Boolean = true) {
|
||||||
_currentServer.value = serverId
|
_currentServer.value = serverId
|
||||||
|
|
||||||
|
if (save) viewModelScope.launch {
|
||||||
|
kvStorage.set("currentServer", serverId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setCurrentChannel(channelId: String) {
|
||||||
|
_currentChannel.value = channelId
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
kvStorage.set("currentChannel", channelId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun navigateToServer(serverId: String, navController: NavController) {
|
fun navigateToServer(serverId: String, navController: NavController) {
|
||||||
setCurrentServer(serverId)
|
|
||||||
|
|
||||||
if (serverId == "home") {
|
if (serverId == "home") {
|
||||||
navController.navigate("home") {
|
navController.navigate("home") {
|
||||||
navController.graph.startDestinationRoute?.let { route ->
|
navController.graph.startDestinationRoute?.let { route ->
|
||||||
popUpTo(route)
|
popUpTo(route)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setCurrentServer("home")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val channelId = RevoltAPI.serverCache[serverId]?.channels?.firstOrNull()
|
val channelId = RevoltAPI.serverCache[serverId]?.channels?.firstOrNull()
|
||||||
|
|
||||||
|
setCurrentServer(serverId, channelId != null)
|
||||||
|
|
||||||
|
if (channelId != null) {
|
||||||
|
navigateToChannel(channelId, navController)
|
||||||
|
} else {
|
||||||
|
navController.navigate("no_current_channel") {
|
||||||
|
navController.graph.startDestinationRoute?.let { route ->
|
||||||
|
popUpTo(route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun navigateToChannel(channelId: String, navController: NavController, pure: Boolean = false) {
|
||||||
|
if (!pure) setCurrentChannel(channelId)
|
||||||
|
|
||||||
navController.navigate("channel/$channelId") {
|
navController.navigate("channel/$channelId") {
|
||||||
navController.graph.startDestinationRoute?.let { route ->
|
navController.graph.startDestinationRoute?.let { route ->
|
||||||
popUpTo(route)
|
popUpTo(route)
|
||||||
|
|
@ -101,7 +148,7 @@ class ChatRouterViewModel : ViewModel() {
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialNavigationApi::class, ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalMaterialNavigationApi::class, ExperimentalComposeUiApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = viewModel()) {
|
fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hiltViewModel()) {
|
||||||
val drawerState = rememberDoubleDrawerState()
|
val drawerState = rememberDoubleDrawerState()
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
@ -120,6 +167,16 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = vie
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(viewModel.currentChannel) {
|
||||||
|
snapshotFlow { viewModel.currentChannel }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.collect { channelId ->
|
||||||
|
if (channelId != null) {
|
||||||
|
viewModel.navigateToChannel(channelId, navController, pure = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ModalBottomSheetLayout(
|
ModalBottomSheetLayout(
|
||||||
sheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
|
sheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
|
||||||
sheetBackgroundColor = MaterialTheme.colorScheme.surface,
|
sheetBackgroundColor = MaterialTheme.colorScheme.surface,
|
||||||
|
|
@ -207,8 +264,14 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = vie
|
||||||
Crossfade(targetState = viewModel.currentServer) {
|
Crossfade(targetState = viewModel.currentServer) {
|
||||||
ChannelList(
|
ChannelList(
|
||||||
serverId = it,
|
serverId = it,
|
||||||
navController = navController,
|
drawerState = drawerState,
|
||||||
drawerState = drawerState
|
currentChannel = viewModel.currentChannel,
|
||||||
|
onChannelClick = { channelId ->
|
||||||
|
viewModel.navigateToChannel(channelId, navController)
|
||||||
|
},
|
||||||
|
onChannelLongClick = { channelId ->
|
||||||
|
navController.navigate("channel/$channelId/info")
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -239,6 +302,9 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = vie
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
composable("no_current_channel") {
|
||||||
|
NoCurrentChannelScreen()
|
||||||
|
}
|
||||||
|
|
||||||
bottomSheet("channel/{channelId}/info") { backStackEntry ->
|
bottomSheet("channel/{channelId}/info") { backStackEntry ->
|
||||||
val channelId = backStackEntry.arguments?.getString("channelId")
|
val channelId = backStackEntry.arguments?.getString("channelId")
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ import chat.revolt.api.realtime.frames.receivable.MessageFrame
|
||||||
import chat.revolt.api.routes.channel.SendMessageReply
|
import chat.revolt.api.routes.channel.SendMessageReply
|
||||||
import chat.revolt.api.routes.channel.ackChannel
|
import chat.revolt.api.routes.channel.ackChannel
|
||||||
import chat.revolt.api.routes.channel.fetchMessagesFromChannel
|
import chat.revolt.api.routes.channel.fetchMessagesFromChannel
|
||||||
|
import chat.revolt.api.routes.channel.fetchSingleChannel
|
||||||
import chat.revolt.api.routes.channel.sendMessage
|
import chat.revolt.api.routes.channel.sendMessage
|
||||||
import chat.revolt.api.routes.microservices.autumn.FileArgs
|
import chat.revolt.api.routes.microservices.autumn.FileArgs
|
||||||
import chat.revolt.api.routes.microservices.autumn.MAX_ATTACHMENTS_PER_MESSAGE
|
import chat.revolt.api.routes.microservices.autumn.MAX_ATTACHMENTS_PER_MESSAGE
|
||||||
|
|
@ -81,6 +82,8 @@ class ChannelScreenViewModel : ViewModel() {
|
||||||
val channel: Channel?
|
val channel: Channel?
|
||||||
get() = _channel
|
get() = _channel
|
||||||
|
|
||||||
|
private var _uiCallbackRegistered by mutableStateOf(false)
|
||||||
|
|
||||||
private var _channelCallback = mutableStateOf<RealtimeSocket.ChannelCallback?>(null)
|
private var _channelCallback = mutableStateOf<RealtimeSocket.ChannelCallback?>(null)
|
||||||
private val channelCallback: RealtimeSocket.ChannelCallback?
|
private val channelCallback: RealtimeSocket.ChannelCallback?
|
||||||
get() = _channelCallback.value
|
get() = _channelCallback.value
|
||||||
|
|
@ -222,8 +225,16 @@ class ChannelScreenViewModel : ViewModel() {
|
||||||
_channelCallback.value = ChannelScreenCallback()
|
_channelCallback.value = ChannelScreenCallback()
|
||||||
RealtimeSocket.registerChannelCallback(channel!!.id!!, channelCallback!!)
|
RealtimeSocket.registerChannelCallback(channel!!.id!!, channelCallback!!)
|
||||||
|
|
||||||
_uiCallbackReceiver.value = UiCallbackReceiver()
|
if (!_uiCallbackRegistered) {
|
||||||
UiCallbacks.registerReceiver(uiCallbackReceiver!!)
|
_uiCallbackReceiver.value = UiCallbackReceiver()
|
||||||
|
UiCallbacks.registerReceiver(uiCallbackReceiver!!)
|
||||||
|
_uiCallbackRegistered = true
|
||||||
|
} else {
|
||||||
|
Log.d(
|
||||||
|
"ChannelScreenViewModel",
|
||||||
|
"UI Callbacks already registered but trying to register again. Ignoring but this is a bug."
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchMessages() {
|
fun fetchMessages() {
|
||||||
|
|
@ -300,16 +311,22 @@ class ChannelScreenViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchChannel(id: String) {
|
fun fetchChannel(id: String) {
|
||||||
if (id in RevoltAPI.channelCache) {
|
viewModelScope.launch {
|
||||||
_channel = RevoltAPI.channelCache[id]
|
if (id !in RevoltAPI.channelCache) {
|
||||||
} else {
|
val channel = fetchSingleChannel(id)
|
||||||
Log.e("ChannelScreen", "Channel $id not in cache, for now this is fatal!") // FIXME
|
_channel = channel
|
||||||
}
|
RevoltAPI.channelCache[id] = channel
|
||||||
|
} else {
|
||||||
|
_channel = RevoltAPI.channelCache[id]
|
||||||
|
}
|
||||||
|
|
||||||
registerCallbacks()
|
registerCallbacks()
|
||||||
|
|
||||||
if (channel?.lastMessageID != null) {
|
if (_channel?.lastMessageID != null) {
|
||||||
ackNewest()
|
ackNewest()
|
||||||
|
} else {
|
||||||
|
Log.d("ChannelScreen", "No last message ID, not acking.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package chat.revolt.screens.chat.views
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
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.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import chat.revolt.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NoCurrentChannelScreen() {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(64.dp),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.no_active_channel),
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontSize = 24.sp,
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.no_active_channel_body),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -89,6 +89,11 @@
|
||||||
|
|
||||||
<string name="server_plus_alt">Add server</string>
|
<string name="server_plus_alt">Add server</string>
|
||||||
|
|
||||||
|
<string name="no_channels_heading">Bit awkward.</string>
|
||||||
|
<string name="no_channels_body">There aren\'t any channels in this server. Not even a welcome channel. How rude.</string>
|
||||||
|
<string name="no_active_channel">You\'re not in a channel right now.</string>
|
||||||
|
<string name="no_active_channel_body">Select a server from the left to get started. If you\'re feeling adventurous, you can create a new server.</string>
|
||||||
|
|
||||||
<string name="avatar_alt">%1$s\'s avatar</string>
|
<string name="avatar_alt">%1$s\'s avatar</string>
|
||||||
|
|
||||||
<string name="channel_dm">Direct Message</string>
|
<string name="channel_dm">Direct Message</string>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue