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()
|
||||
)
|
||||
|
||||
fun makeSpecial(timestamp: Long, entropy: ByteArray): String {
|
||||
fun makeSpecial(timestamp: Long, entropy: ByteArray = fetchEntropy()): String {
|
||||
if (timestamp < minTimestamp || timestamp > maxTimestamp) {
|
||||
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.RevoltJson
|
||||
import chat.revolt.api.internals.ULID
|
||||
import chat.revolt.api.schemas.Channel
|
||||
import chat.revolt.api.schemas.Message
|
||||
import chat.revolt.api.schemas.MessagesInChannel
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.parameter
|
||||
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
|
||||
|
||||
suspend fun fetchMessagesFromChannel(
|
||||
|
|
@ -95,4 +101,16 @@ suspend fun ackChannel(channelId: String, messageId: String = ULID.makeNext()) {
|
|||
RevoltHttp.put("/channels/$channelId/ack/$messageId") {
|
||||
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.RevoltJson
|
||||
import chat.revolt.api.schemas.User
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import kotlinx.serialization.SerializationException
|
||||
|
||||
suspend fun fetchSelf(): User {
|
||||
|
|
@ -50,7 +50,9 @@ suspend fun fetchUser(id: String): User {
|
|||
|
||||
val user = RevoltJson.decodeFromString(User.serializer(), response)
|
||||
|
||||
RevoltAPI.userCache[user.id!!] = user
|
||||
user.id?.let {
|
||||
RevoltAPI.userCache[it] = user
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -95,7 +96,7 @@ fun Message(
|
|||
if (message.tail == false) {
|
||||
UserAvatar(
|
||||
username = author.username ?: "",
|
||||
userId = author.id!!,
|
||||
userId = author.id ?: message.id ?: ULID.makeSpecial(0),
|
||||
avatar = author.avatar,
|
||||
rawUrl = message.masquerade?.avatar?.let { asJanuaryProxyUrl(it) }
|
||||
)
|
||||
|
|
@ -107,7 +108,9 @@ fun Message(
|
|||
if (message.tail == false) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = message.masquerade?.name ?: author.username ?: "",
|
||||
text = message.masquerade?.name
|
||||
?: author.username
|
||||
?: stringResource(id = R.string.unknown),
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = if (message.masquerade?.colour != null) {
|
||||
WebCompat.parseColour(message.masquerade.colour)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
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.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
|
|
@ -10,15 +12,14 @@ import androidx.compose.material3.MaterialTheme
|
|||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
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.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import chat.revolt.R
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.schemas.ChannelType
|
||||
|
|
@ -29,17 +30,19 @@ import kotlinx.coroutines.launch
|
|||
@Composable
|
||||
fun RowScope.ChannelList(
|
||||
serverId: String,
|
||||
navController: NavController,
|
||||
drawerState: DoubleDrawerState
|
||||
drawerState: DoubleDrawerState,
|
||||
currentChannel: String?,
|
||||
onChannelClick: (String) -> Unit,
|
||||
onChannelLongClick: (String) -> Unit,
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
|
||||
Surface(
|
||||
tonalElevation = 1.dp,
|
||||
modifier = Modifier
|
||||
.padding(start = 4.dp, top = 8.dp, bottom = 8.dp)
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
|
|
@ -57,9 +60,7 @@ fun RowScope.ChannelList(
|
|||
name = channel.name
|
||||
?: "GDM #${channel.id}",
|
||||
channelType = ChannelType.Group,
|
||||
selected = (channel.id == navBackStackEntry?.arguments?.getString(
|
||||
"channelId"
|
||||
)),
|
||||
selected = currentChannel == channel.id,
|
||||
hasUnread = channel.lastMessageID?.let { lastMessageID ->
|
||||
RevoltAPI.unreads.hasUnread(
|
||||
channel.id!!,
|
||||
|
|
@ -67,15 +68,11 @@ fun RowScope.ChannelList(
|
|||
)
|
||||
} ?: false,
|
||||
onClick = {
|
||||
navController.navigate("channel/${channel.id}") {
|
||||
navController.graph.startDestinationRoute?.let { route ->
|
||||
popUpTo(route)
|
||||
}
|
||||
}
|
||||
onChannelClick(channel.id ?: return@DrawerChannel)
|
||||
coroutineScope.launch { drawerState.focusCenter() }
|
||||
},
|
||||
onLongClick = {
|
||||
navController.navigate("channel/${channel.id}/info")
|
||||
onChannelLongClick(channel.id ?: return@DrawerChannel)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -91,37 +88,52 @@ fun RowScope.ChannelList(
|
|||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
server?.channels?.forEach { channelId ->
|
||||
RevoltAPI.channelCache[channelId]?.let { ch ->
|
||||
DrawerChannel(
|
||||
name = ch.name!!,
|
||||
channelType = ch.channelType!!,
|
||||
selected = navBackStackEntry?.arguments?.getString(
|
||||
"channelId"
|
||||
) == ch.id,
|
||||
hasUnread = ch.lastMessageID?.let { lastMessageID ->
|
||||
RevoltAPI.unreads.hasUnread(
|
||||
ch.id!!,
|
||||
lastMessageID
|
||||
)
|
||||
} ?: true,
|
||||
onClick = {
|
||||
coroutineScope.launch { drawerState.focusCenter() }
|
||||
navController.navigate("channel/${ch.id}") {
|
||||
navController.graph.startDestinationRoute?.let { route ->
|
||||
popUpTo(route)
|
||||
}
|
||||
if (server?.channels?.isEmpty() == true) {
|
||||
Column(
|
||||
Modifier.weight(1f),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.no_channels_heading),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = 24.sp,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.no_channels_body),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.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.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.NavHost
|
||||
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.ServerDrawerSeparator
|
||||
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.sheets.ChannelContextSheet
|
||||
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.views.ChannelScreen
|
||||
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.ModalBottomSheetLayout
|
||||
import com.google.accompanist.navigation.material.bottomSheet
|
||||
import com.google.accompanist.navigation.material.rememberBottomSheetNavigator
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
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")
|
||||
val currentServer: String
|
||||
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
|
||||
|
||||
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) {
|
||||
setCurrentServer(serverId)
|
||||
|
||||
if (serverId == "home") {
|
||||
navController.navigate("home") {
|
||||
navController.graph.startDestinationRoute?.let { route ->
|
||||
popUpTo(route)
|
||||
}
|
||||
}
|
||||
setCurrentServer("home")
|
||||
return
|
||||
}
|
||||
|
||||
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.graph.startDestinationRoute?.let { route ->
|
||||
popUpTo(route)
|
||||
|
|
@ -101,7 +148,7 @@ class ChatRouterViewModel : ViewModel() {
|
|||
|
||||
@OptIn(ExperimentalMaterialNavigationApi::class, ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = viewModel()) {
|
||||
fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hiltViewModel()) {
|
||||
val drawerState = rememberDoubleDrawerState()
|
||||
val scope = rememberCoroutineScope()
|
||||
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(
|
||||
sheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
|
||||
sheetBackgroundColor = MaterialTheme.colorScheme.surface,
|
||||
|
|
@ -207,8 +264,14 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = vie
|
|||
Crossfade(targetState = viewModel.currentServer) {
|
||||
ChannelList(
|
||||
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 ->
|
||||
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.ackChannel
|
||||
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.microservices.autumn.FileArgs
|
||||
import chat.revolt.api.routes.microservices.autumn.MAX_ATTACHMENTS_PER_MESSAGE
|
||||
|
|
@ -81,6 +82,8 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
val channel: Channel?
|
||||
get() = _channel
|
||||
|
||||
private var _uiCallbackRegistered by mutableStateOf(false)
|
||||
|
||||
private var _channelCallback = mutableStateOf<RealtimeSocket.ChannelCallback?>(null)
|
||||
private val channelCallback: RealtimeSocket.ChannelCallback?
|
||||
get() = _channelCallback.value
|
||||
|
|
@ -222,8 +225,16 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
_channelCallback.value = ChannelScreenCallback()
|
||||
RealtimeSocket.registerChannelCallback(channel!!.id!!, channelCallback!!)
|
||||
|
||||
_uiCallbackReceiver.value = UiCallbackReceiver()
|
||||
UiCallbacks.registerReceiver(uiCallbackReceiver!!)
|
||||
if (!_uiCallbackRegistered) {
|
||||
_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() {
|
||||
|
|
@ -300,16 +311,22 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
}
|
||||
|
||||
fun fetchChannel(id: String) {
|
||||
if (id in RevoltAPI.channelCache) {
|
||||
_channel = RevoltAPI.channelCache[id]
|
||||
} else {
|
||||
Log.e("ChannelScreen", "Channel $id not in cache, for now this is fatal!") // FIXME
|
||||
}
|
||||
viewModelScope.launch {
|
||||
if (id !in RevoltAPI.channelCache) {
|
||||
val channel = fetchSingleChannel(id)
|
||||
_channel = channel
|
||||
RevoltAPI.channelCache[id] = channel
|
||||
} else {
|
||||
_channel = RevoltAPI.channelCache[id]
|
||||
}
|
||||
|
||||
registerCallbacks()
|
||||
registerCallbacks()
|
||||
|
||||
if (channel?.lastMessageID != null) {
|
||||
ackNewest()
|
||||
if (_channel?.lastMessageID != null) {
|
||||
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="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="channel_dm">Direct Message</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue