feat: connection handling
- also adds an internet check on launch
This commit is contained in:
parent
8f068edeec
commit
d93b9f1bcb
|
|
@ -4,6 +4,7 @@ import android.os.Handler
|
|||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import chat.revolt.api.realtime.DisconnectionState
|
||||
import chat.revolt.api.realtime.RealtimeSocket
|
||||
import chat.revolt.api.routes.user.fetchSelf
|
||||
import chat.revolt.api.schemas.*
|
||||
|
|
@ -98,13 +99,7 @@ object RevoltAPI {
|
|||
} else {
|
||||
Log.e("RevoltAPI", "WebSocket error", e)
|
||||
}
|
||||
RealtimeSocket.open = false
|
||||
|
||||
Log.i("RevoltAPI", "Reconnecting in 2.5 seconds...")
|
||||
Thread.sleep(2500)
|
||||
runBlocking {
|
||||
connectWS()
|
||||
} // FIXME ASAP move this to ChatRouting (whenever that's a thing) and do it properly
|
||||
RealtimeSocket.updateDisconnectionState(DisconnectionState.Disconnected)
|
||||
}
|
||||
}
|
||||
socketThread!!.start()
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package chat.revolt.api.realtime
|
|||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import chat.revolt.api.REVOLT_WEBSOCKET
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.RevoltHttp
|
||||
|
|
@ -14,16 +15,29 @@ import io.ktor.websocket.*
|
|||
import kotlinx.coroutines.channels.consumeEach
|
||||
import java.util.Calendar
|
||||
|
||||
enum class DisconnectionState {
|
||||
Disconnected,
|
||||
Reconnecting,
|
||||
Connected
|
||||
}
|
||||
|
||||
object RealtimeSocket {
|
||||
var socket: WebSocketSession? = null
|
||||
var open: Boolean = false
|
||||
|
||||
private var _disconnectionState = mutableStateOf(DisconnectionState.Disconnected)
|
||||
val disconnectionState: DisconnectionState
|
||||
get() = _disconnectionState.value
|
||||
|
||||
fun updateDisconnectionState(state: DisconnectionState) {
|
||||
_disconnectionState.value = state
|
||||
}
|
||||
|
||||
suspend fun connect(token: String) {
|
||||
RevoltHttp.ws(REVOLT_WEBSOCKET) {
|
||||
socket = this
|
||||
|
||||
Log.d("RealtimeSocket", "Connected to websocket.")
|
||||
open = true
|
||||
updateDisconnectionState(DisconnectionState.Connected)
|
||||
|
||||
// Send authorization frame
|
||||
val authFrame = AuthorizationFrame("Authenticate", token)
|
||||
|
|
@ -46,7 +60,7 @@ object RealtimeSocket {
|
|||
}
|
||||
|
||||
suspend fun sendPing() {
|
||||
if (!open) return
|
||||
if (disconnectionState != DisconnectionState.Connected) return
|
||||
|
||||
val pingPacket = PingFrame("Ping", Calendar.getInstance().timeInMillis.toInt())
|
||||
socket?.send(RevoltJson.encodeToString(PingFrame.serializer(), pingPacket))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
package chat.revolt.components.chat
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Done
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.revolt.R
|
||||
import chat.revolt.api.realtime.DisconnectionState
|
||||
|
||||
@Composable
|
||||
private fun DisconnectedNoticeBase(
|
||||
background: Color,
|
||||
icon: ImageVector,
|
||||
text: String,
|
||||
canTapToRetry: Boolean = false,
|
||||
onRetry: () -> Unit = {}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(background)
|
||||
.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||
.clickable(enabled = canTapToRetry, onClick = onRetry),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
imageVector = icon,
|
||||
contentDescription = null
|
||||
)
|
||||
Text(
|
||||
text = text,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
if (canTapToRetry) {
|
||||
Text(
|
||||
text = stringResource(R.string.tap_to_reconnect),
|
||||
modifier = Modifier.padding(start = 8.dp),
|
||||
fontWeight = FontWeight.Normal
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DisconnectedNotice(
|
||||
state: DisconnectionState,
|
||||
onReconnect: () -> Unit
|
||||
) {
|
||||
when (state) {
|
||||
DisconnectionState.Disconnected -> DisconnectedNoticeBase(
|
||||
background = Color(0xfffe4654),
|
||||
icon = Icons.Default.Warning,
|
||||
text = stringResource(id = R.string.disconnected),
|
||||
canTapToRetry = true,
|
||||
onRetry = onReconnect
|
||||
)
|
||||
DisconnectionState.Reconnecting -> DisconnectedNoticeBase(
|
||||
background = Color(0xfffcb205),
|
||||
icon = Icons.Default.Refresh,
|
||||
text = stringResource(id = R.string.reconnecting),
|
||||
)
|
||||
DisconnectionState.Connected -> DisconnectedNoticeBase(
|
||||
background = Color(0xff4b9f6a),
|
||||
icon = Icons.Default.Done,
|
||||
text = stringResource(id = R.string.reconnected),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package chat.revolt.components.screens.splash
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Button
|
||||
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.font.FontWeight
|
||||
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 DisconnectedScreen(
|
||||
onRetry: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.surface),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.no_connection),
|
||||
style = MaterialTheme.typography.displaySmall.copy(
|
||||
fontSize = 30.sp,
|
||||
fontWeight = FontWeight.Black,
|
||||
textAlign = TextAlign.Center
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.no_connection_message),
|
||||
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.6f),
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
|
||||
Button(onClick = onRetry) {
|
||||
Text(stringResource(R.string.tap_to_retry))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
package chat.revolt.screens
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -17,15 +21,20 @@ import chat.revolt.R
|
|||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.components.generic.RemoteImage
|
||||
import chat.revolt.components.generic.drawableResource
|
||||
import chat.revolt.components.screens.splash.DisconnectedScreen
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
class SplashScreenViewModel @Inject constructor(
|
||||
private val kvStorage: KVStorage
|
||||
private val kvStorage: KVStorage,
|
||||
@ApplicationContext private val context: Context
|
||||
) : ViewModel() {
|
||||
|
||||
private var _navigateTo by mutableStateOf("")
|
||||
val navigateTo: String
|
||||
get() = _navigateTo
|
||||
|
|
@ -34,8 +43,36 @@ class SplashScreenViewModel @Inject constructor(
|
|||
_navigateTo = value
|
||||
}
|
||||
|
||||
init {
|
||||
private var _isConnected by mutableStateOf(false)
|
||||
val isConnected: Boolean
|
||||
get() = _isConnected
|
||||
|
||||
fun setIsConnected(value: Boolean) {
|
||||
_isConnected = value
|
||||
}
|
||||
|
||||
private fun hasInternetConnection(): Boolean {
|
||||
val connectivityManager =
|
||||
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
val networkCapabilities = connectivityManager.activeNetwork ?: return false
|
||||
val actNw =
|
||||
connectivityManager.getNetworkCapabilities(networkCapabilities) ?: return false
|
||||
|
||||
return when {
|
||||
actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
|
||||
actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
|
||||
actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun checkLoggedInState() {
|
||||
viewModelScope.launch {
|
||||
setIsConnected(hasInternetConnection())
|
||||
|
||||
if (!isConnected) return@launch
|
||||
|
||||
val token = kvStorage.get("sessionToken") ?: return@launch setNavigateTo("login")
|
||||
|
||||
val valid = RevoltAPI.checkSessionToken(token)
|
||||
|
|
@ -49,10 +86,23 @@ class SplashScreenViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
checkLoggedInState()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SplashScreen(navController: NavController, viewModel: SplashScreenViewModel = hiltViewModel()) {
|
||||
if (!viewModel.isConnected) {
|
||||
DisconnectedScreen(
|
||||
onRetry = {
|
||||
viewModel.checkLoggedInState()
|
||||
}
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package chat.revolt.screens.chat
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -33,7 +34,10 @@ import chat.revolt.api.RevoltAPI
|
|||
import chat.revolt.components.generic.RemoteImage
|
||||
import chat.revolt.screens.chat.views.HomeScreen
|
||||
import chat.revolt.R
|
||||
import chat.revolt.api.realtime.DisconnectionState
|
||||
import chat.revolt.api.realtime.RealtimeSocket
|
||||
import chat.revolt.api.schemas.ChannelType
|
||||
import chat.revolt.components.chat.DisconnectedNotice
|
||||
import chat.revolt.components.screens.chat.DrawerChannel
|
||||
import chat.revolt.screens.chat.views.ChannelScreen
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -60,120 +64,130 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = vie
|
|||
val navController = rememberNavController()
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
|
||||
DismissibleNavigationDrawer(
|
||||
drawerState = channelDrawerState,
|
||||
drawerContent = {
|
||||
ModalDrawerSheet(drawerContainerColor = MaterialTheme.colorScheme.surfaceVariant) {
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
Row {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { viewModel.goToHome() },
|
||||
Column() {
|
||||
AnimatedVisibility(visible = RealtimeSocket.disconnectionState != DisconnectionState.Connected) {
|
||||
DisconnectedNotice(
|
||||
state = RealtimeSocket.disconnectionState,
|
||||
onReconnect = {
|
||||
RealtimeSocket.updateDisconnectionState(DisconnectionState.Reconnecting)
|
||||
scope.launch { RevoltAPI.connectWS() }
|
||||
})
|
||||
}
|
||||
DismissibleNavigationDrawer(
|
||||
drawerState = channelDrawerState,
|
||||
drawerContent = {
|
||||
ModalDrawerSheet(drawerContainerColor = MaterialTheme.colorScheme.surfaceVariant) {
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
Row {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.size(48.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Home,
|
||||
contentDescription = stringResource(id = R.string.home),
|
||||
modifier = Modifier.padding(4.dp)
|
||||
)
|
||||
}
|
||||
|
||||
RevoltAPI.serverCache.values.forEach { server ->
|
||||
if (server.name == null) return@forEach
|
||||
|
||||
if (server.icon != null) {
|
||||
RemoteImage(
|
||||
url = "$REVOLT_FILES/icons/${server.icon.id!!}/server.png?max_side=256",
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.clickable { viewModel.setCurrentServer(server.id!!) },
|
||||
description = "${server.name}"
|
||||
IconButton(
|
||||
onClick = { viewModel.goToHome() },
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.size(48.dp)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Home,
|
||||
contentDescription = stringResource(id = R.string.home),
|
||||
modifier = Modifier.padding(4.dp)
|
||||
)
|
||||
} else {
|
||||
// return a placeholder icon, currently the first letter of the server name in a circle
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.clickable { viewModel.setCurrentServer(server.id!!) }
|
||||
) {
|
||||
Text(
|
||||
text = server.name.first().toString(),
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
}
|
||||
|
||||
RevoltAPI.serverCache.values.forEach { server ->
|
||||
if (server.name == null) return@forEach
|
||||
|
||||
if (server.icon != null) {
|
||||
RemoteImage(
|
||||
url = "$REVOLT_FILES/icons/${server.icon.id!!}/server.png?max_side=256",
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.clickable { viewModel.setCurrentServer(server.id!!) },
|
||||
description = "${server.name}"
|
||||
)
|
||||
} else {
|
||||
// return a placeholder icon, currently the first letter of the server name in a circle
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.clickable { viewModel.setCurrentServer(server.id!!) }
|
||||
) {
|
||||
Text(
|
||||
text = server.name.first().toString(),
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Crossfade(targetState = viewModel.currentServer) {
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
) {
|
||||
if (it == "home") {
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
RevoltAPI.channelCache.values.filter { it.channelType == ChannelType.DirectMessage }
|
||||
.forEach { channel ->
|
||||
DrawerChannel(
|
||||
name = "DM #${channel.id}", // TODO get user or group name
|
||||
channelType = ChannelType.DirectMessage,
|
||||
selected = channel.id == (navBackStackEntry?.arguments?.getString(
|
||||
"channelId"
|
||||
) ?: false),
|
||||
onClick = {
|
||||
navController.navigate("channel/${channel.id}")
|
||||
scope.launch {
|
||||
channelDrawerState.close()
|
||||
Crossfade(targetState = viewModel.currentServer) {
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
) {
|
||||
if (it == "home") {
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
RevoltAPI.channelCache.values.filter { it.channelType == ChannelType.DirectMessage }
|
||||
.forEach { channel ->
|
||||
DrawerChannel(
|
||||
name = "DM #${channel.id}", // TODO get user or group name
|
||||
channelType = ChannelType.DirectMessage,
|
||||
selected = channel.id == (navBackStackEntry?.arguments?.getString(
|
||||
"channelId"
|
||||
) ?: false),
|
||||
onClick = {
|
||||
navController.navigate("channel/${channel.id}")
|
||||
scope.launch {
|
||||
channelDrawerState.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val server = RevoltAPI.serverCache[it]
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val server = RevoltAPI.serverCache[it]
|
||||
|
||||
Text(
|
||||
text = server?.name ?: stringResource(R.string.unknown),
|
||||
fontWeight = FontWeight.Black,
|
||||
fontSize = 24.sp,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
Text(
|
||||
text = server?.name ?: stringResource(R.string.unknown),
|
||||
fontWeight = FontWeight.Black,
|
||||
fontSize = 24.sp,
|
||||
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,
|
||||
onClick = {
|
||||
scope.launch { channelDrawerState.close() }
|
||||
navController.navigate("channel/${ch.id}")
|
||||
})
|
||||
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,
|
||||
onClick = {
|
||||
scope.launch { channelDrawerState.close() }
|
||||
navController.navigate("channel/${ch.id}")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -182,18 +196,19 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = vie
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
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)
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,24 @@
|
|||
package chat.revolt.screens.chat.views
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.components.screens.home.LinkOnHome
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import chat.revolt.R
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -23,37 +26,19 @@ import javax.inject.Inject
|
|||
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)",
|
||||
text = stringResource(id = R.string.home),
|
||||
style = MaterialTheme.typography.displaySmall.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
textAlign = TextAlign.Left,
|
||||
|
|
@ -64,7 +49,9 @@ fun HomeScreen(navController: NavController, viewModel: HomeScreenViewModel = hi
|
|||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Button(
|
||||
LinkOnHome(
|
||||
heading = stringResource(id = R.string.logout),
|
||||
icon = Icons.Default.Close,
|
||||
onClick = {
|
||||
viewModel.logout()
|
||||
navController.navigate("login/greeting") {
|
||||
|
|
@ -72,12 +59,6 @@ fun HomeScreen(navController: NavController, viewModel: HomeScreenViewModel = hi
|
|||
inclusive = true
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 30.dp, top = 5.dp, start = 20.dp, end = 20.dp)
|
||||
) {
|
||||
Text("Logout")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -75,7 +75,9 @@
|
|||
<string name="select_channel">Select a server and channel by swiping from the left.</string>
|
||||
|
||||
<string name="unknown">Unknown</string>
|
||||
|
||||
<string name="home">Home</string>
|
||||
<string name="logout">Log out</string>
|
||||
|
||||
<string name="avatar_alt">%1$s\'s avatar</string>
|
||||
|
||||
|
|
@ -92,4 +94,13 @@
|
|||
<string name="message_field_placeholder_notes">Add a note</string>
|
||||
|
||||
<string name="reply_message_not_cached">Unknown message, tap to jump</string>
|
||||
|
||||
<string name="disconnected">Disconnected</string>
|
||||
<string name="tap_to_reconnect">Tap to reconnect</string>
|
||||
<string name="reconnecting">Reconnecting…</string>
|
||||
<string name="reconnected">Reconnected</string>
|
||||
|
||||
<string name="no_connection">No connection</string>
|
||||
<string name="no_connection_message">You are not connected to the internet. Please check your connection and try again.</string>
|
||||
<string name="tap_to_retry">Tap to retry</string>
|
||||
</resources>
|
||||
Loading…
Reference in New Issue