moving to coroutines

This commit is contained in:
2025-07-03 22:14:29 -04:00
parent 62f6128a2e
commit 8085c55c24
6 changed files with 369 additions and 177 deletions

View File

@@ -9,7 +9,13 @@ import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import com.mattintech.lchat.utils.LOG_PREFIX
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
@RequiresApi(Build.VERSION_CODES.O)
class WifiAwareManager(private val context: Context) {
@@ -26,27 +32,57 @@ class WifiAwareManager(private val context: Context) {
private var subscribeDiscoverySession: SubscribeDiscoverySession? = null
private val peerHandles = ConcurrentHashMap<String, PeerHandle>()
// Replace callbacks with Flows
private val _messageFlow = MutableSharedFlow<Triple<String, String, String>>()
val messageFlow: SharedFlow<Triple<String, String, String>> = _messageFlow.asSharedFlow()
private val _connectionFlow = MutableSharedFlow<Pair<String, Boolean>>()
val connectionFlow: SharedFlow<Pair<String, Boolean>> = _connectionFlow.asSharedFlow()
// Keep legacy callbacks for backward compatibility
private var messageCallback: ((String, String, String) -> Unit)? = null
private var connectionCallback: ((String, Boolean) -> Unit)? = null
private val attachCallback = object : AttachCallback() {
override fun onAttached(session: WifiAwareSession) {
Log.d(TAG, "Wi-Fi Aware attached")
wifiAwareSession = session
}
override fun onAttachFailed() {
Log.e(TAG, "Wi-Fi Aware attach failed")
// Exception handler for coroutine errors
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
Log.e(TAG, "Coroutine exception: ", throwable)
}
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + exceptionHandler)
fun initialize() {
coroutineScope.launch {
initializeAsync()
}
}
fun initialize() {
suspend fun initializeAsync(): Result<Unit> = withContext(Dispatchers.IO) {
wifiAwareManager = context.getSystemService(Context.WIFI_AWARE_SERVICE) as? android.net.wifi.aware.WifiAwareManager
if (wifiAwareManager?.isAvailable == true) {
wifiAwareManager?.attach(attachCallback, null)
attachToWifiAware()
} else {
Log.e(TAG, "Wi-Fi Aware is not available")
Result.failure(Exception("Wi-Fi Aware is not available"))
}
}
private suspend fun attachToWifiAware(): Result<Unit> = suspendCancellableCoroutine { continuation ->
wifiAwareManager?.attach(object : AttachCallback() {
override fun onAttached(session: WifiAwareSession) {
Log.d(TAG, "Wi-Fi Aware attached")
wifiAwareSession = session
continuation.resume(Result.success(Unit))
}
override fun onAttachFailed() {
Log.e(TAG, "Wi-Fi Aware attach failed")
continuation.resume(Result.failure(Exception("Wi-Fi Aware attach failed")))
}
}, null)
continuation.invokeOnCancellation {
Log.d(TAG, "Wi-Fi Aware attach cancelled")
}
}
@@ -67,8 +103,15 @@ class WifiAwareManager(private val context: Context) {
Log.d(TAG, "Host: Received message: $messageStr")
if (messageStr == "CONNECT_REQUEST") {
Log.d(TAG, "Host: Received connection request")
acceptConnection(peerHandle)
Log.d(TAG, "Host: Received connection request from peer")
coroutineScope.launch {
val result = acceptConnectionAsync(peerHandle)
if (result.isSuccess) {
Log.d(TAG, "Host: Successfully accepted connection")
} else {
Log.e(TAG, "Host: Failed to accept connection: ${result.exceptionOrNull()?.message}")
}
}
} else {
handleIncomingMessage(peerHandle, message)
}
@@ -103,9 +146,13 @@ class WifiAwareManager(private val context: Context) {
subscribeDiscoverySession?.sendMessage(peerHandle, 0, "CONNECT_REQUEST".toByteArray())
// Wait a bit for host to prepare, then connect
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
connectToPeer(peerHandle, roomName)
}, 500)
coroutineScope.launch {
delay(500)
val result = connectToPeerAsync(peerHandle, roomName)
if (result.isFailure) {
Log.e(TAG, "Failed to connect to peer: ${result.exceptionOrNull()?.message}")
}
}
}
override fun onMessageReceived(peerHandle: PeerHandle, message: ByteArray) {
@@ -115,6 +162,13 @@ class WifiAwareManager(private val context: Context) {
}
private fun connectToPeer(peerHandle: PeerHandle, roomName: String) {
coroutineScope.launch {
connectToPeerAsync(peerHandle, roomName)
}
}
private suspend fun connectToPeerAsync(peerHandle: PeerHandle, roomName: String): Result<Unit> = withContext(Dispatchers.IO) {
suspendCancellableCoroutine { continuation ->
Log.d(TAG, "connectToPeer: Starting connection to room: $roomName")
val networkSpecifier = WifiAwareNetworkSpecifier.Builder(subscribeDiscoverySession!!, peerHandle)
.setPskPassphrase("lchat-secure-key")
@@ -129,62 +183,117 @@ class WifiAwareManager(private val context: Context) {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
try {
connectivityManager.requestNetwork(networkRequest, object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: android.net.Network) {
Log.d(TAG, "onAvailable: Network connected for room: $roomName")
connectionCallback?.invoke(roomName, true)
try {
var isResumed = false
val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: android.net.Network) {
Log.d(TAG, "onAvailable: Network connected for room: $roomName")
if (!isResumed) {
isResumed = true
continuation.resume(Result.success(Unit))
}
// Emit to flow and legacy callback
coroutineScope.launch {
_connectionFlow.emit(Pair(roomName, true))
}
connectionCallback?.invoke(roomName, true)
}
override fun onLost(network: android.net.Network) {
Log.d(TAG, "onLost: Network lost for room: $roomName")
coroutineScope.launch {
_connectionFlow.emit(Pair(roomName, false))
}
connectionCallback?.invoke(roomName, false)
}
override fun onUnavailable() {
Log.e(TAG, "onUnavailable: Network request failed for room: $roomName")
if (!isResumed) {
isResumed = true
continuation.resume(Result.failure(Exception("Network unavailable for room: $roomName")))
}
coroutineScope.launch {
_connectionFlow.emit(Pair(roomName, false))
}
connectionCallback?.invoke(roomName, false)
}
override fun onCapabilitiesChanged(network: android.net.Network, networkCapabilities: NetworkCapabilities) {
Log.d(TAG, "onCapabilitiesChanged: Capabilities changed for room: $roomName")
}
override fun onLinkPropertiesChanged(network: android.net.Network, linkProperties: android.net.LinkProperties) {
Log.d(TAG, "onLinkPropertiesChanged: Link properties changed for room: $roomName")
}
}
override fun onLost(network: android.net.Network) {
Log.d(TAG, "onLost: Network lost for room: $roomName")
connectionCallback?.invoke(roomName, false)
}
connectivityManager.requestNetwork(networkRequest, callback, android.os.Handler(android.os.Looper.getMainLooper()))
override fun onUnavailable() {
Log.e(TAG, "onUnavailable: Network request failed for room: $roomName")
connectionCallback?.invoke(roomName, false)
}
Log.d(TAG, "connectToPeer: Network request submitted for room: $roomName")
override fun onCapabilitiesChanged(network: android.net.Network, networkCapabilities: NetworkCapabilities) {
Log.d(TAG, "onCapabilitiesChanged: Capabilities changed for room: $roomName")
continuation.invokeOnCancellation {
connectivityManager.unregisterNetworkCallback(callback)
}
override fun onLinkPropertiesChanged(network: android.net.Network, linkProperties: android.net.LinkProperties) {
Log.d(TAG, "onLinkPropertiesChanged: Link properties changed for room: $roomName")
} catch (e: Exception) {
Log.e(TAG, "connectToPeer: Failed to request network", e)
continuation.resume(Result.failure(e))
coroutineScope.launch {
_connectionFlow.emit(Pair(roomName, false))
}
}, android.os.Handler(android.os.Looper.getMainLooper()), 30000) // 30 second timeout
Log.d(TAG, "connectToPeer: Network request submitted for room: $roomName")
} catch (e: Exception) {
Log.e(TAG, "connectToPeer: Failed to request network", e)
connectionCallback?.invoke(roomName, false)
connectionCallback?.invoke(roomName, false)
}
}
}
private fun acceptConnection(peerHandle: PeerHandle) {
Log.d(TAG, "acceptConnection: Accepting connection from client")
val networkSpecifier = WifiAwareNetworkSpecifier.Builder(publishDiscoverySession!!, peerHandle)
.setPskPassphrase("lchat-secure-key")
.setPort(PORT)
.build()
private suspend fun acceptConnectionAsync(peerHandle: PeerHandle): Result<Unit> = withContext(Dispatchers.IO) {
suspendCancellableCoroutine { continuation ->
Log.d(TAG, "acceptConnection: Accepting connection from client")
val networkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
.setNetworkSpecifier(networkSpecifier)
.build()
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.requestNetwork(networkRequest, object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: android.net.Network) {
Log.d(TAG, "Client connected")
peerHandles[peerHandle.toString()] = peerHandle
try {
val networkSpecifier = WifiAwareNetworkSpecifier.Builder(publishDiscoverySession!!, peerHandle)
.setPskPassphrase("lchat-secure-key")
.setPort(PORT)
.build()
val networkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
.setNetworkSpecifier(networkSpecifier)
.build()
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
var isResumed = false
val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: android.net.Network) {
Log.d(TAG, "Client connected")
peerHandles[peerHandle.toString()] = peerHandle
if (!isResumed) {
isResumed = true
continuation.resume(Result.success(Unit))
}
}
override fun onUnavailable() {
Log.e(TAG, "Failed to accept client connection - Check if Wi-Fi is enabled")
if (!isResumed) {
isResumed = true
continuation.resume(Result.failure(Exception("Failed to accept client connection")))
}
}
}
connectivityManager.requestNetwork(networkRequest, callback)
continuation.invokeOnCancellation {
connectivityManager.unregisterNetworkCallback(callback)
}
} catch (e: Exception) {
Log.e(TAG, "acceptConnection: Failed to accept connection", e)
continuation.resume(Result.failure(e))
}
override fun onUnavailable() {
Log.e(TAG, "Failed to accept client connection - Check if Wi-Fi is enabled")
}
})
}
}
private fun handleIncomingMessage(peerHandle: PeerHandle, message: ByteArray) {
@@ -192,6 +301,10 @@ class WifiAwareManager(private val context: Context) {
val messageStr = String(message)
val parts = messageStr.split("|", limit = 3)
if (parts.size == 3) {
// Emit to flow and legacy callback
coroutineScope.launch {
_messageFlow.emit(Triple(parts[0], parts[1], parts[2]))
}
messageCallback?.invoke(parts[0], parts[1], parts[2])
}
} catch (e: Exception) {
@@ -226,5 +339,6 @@ class WifiAwareManager(private val context: Context) {
subscribeDiscoverySession?.close()
wifiAwareSession?.close()
peerHandles.clear()
coroutineScope.cancel()
}
}

View File

@@ -19,6 +19,14 @@ class ChatRepository @Inject constructor(
private val wifiAwareManager: WifiAwareManager,
private val messageDao: MessageDao
) {
// Exception handler for repository operations
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
android.util.Log.e("ChatRepository", "Repository coroutine exception: ", throwable)
_connectionState.value = ConnectionState.Error(throwable.message ?: "Unknown error")
}
// Repository scope for background operations
private val repositoryScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + exceptionHandler)
private var currentRoomName: String = ""
@@ -47,37 +55,65 @@ class ChatRepository @Inject constructor(
init {
wifiAwareManager.initialize()
setupWifiAwareCallbacks()
// Only use Flow-based collection, not callbacks
collectWifiAwareFlows()
}
private fun setupWifiAwareCallbacks() {
wifiAwareManager.setMessageCallback { userId, userName, content ->
val message = Message(
id = UUID.randomUUID().toString(),
userId = userId,
userName = userName,
content = content,
timestamp = System.currentTimeMillis(),
isOwnMessage = false
)
addMessage(message)
// Update last activity time
lastActivityTime = System.currentTimeMillis()
// If we're receiving messages, we must be connected
if (_connectionState.value !is ConnectionState.Connected &&
_connectionState.value !is ConnectionState.Hosting) {
when (_connectionState.value) {
is ConnectionState.Hosting -> {} // Keep hosting state
else -> _connectionState.value = ConnectionState.Connected("Active")
private fun collectWifiAwareFlows() {
// Collect messages from Flow
repositoryScope.launch {
try {
wifiAwareManager.messageFlow.collect { (userId, userName, content) ->
val message = Message(
id = UUID.randomUUID().toString(),
userId = userId,
userName = userName,
content = content,
timestamp = System.currentTimeMillis(),
isOwnMessage = false
)
addMessage(message)
// Update last activity time
lastActivityTime = System.currentTimeMillis()
// If we're receiving messages, we must be connected
if (_connectionState.value !is ConnectionState.Connected &&
_connectionState.value !is ConnectionState.Hosting) {
when (_connectionState.value) {
is ConnectionState.Hosting -> {} // Keep hosting state
else -> _connectionState.value = ConnectionState.Connected("Active")
}
}
}
} catch (e: Exception) {
android.util.Log.e("ChatRepository", "Error collecting message flow", e)
}
}
// Collect connection state from Flow
repositoryScope.launch {
try {
wifiAwareManager.connectionFlow.collect { (roomName, isConnected) ->
if (isConnected) {
currentRoomName = roomName
_connectionState.value = ConnectionState.Connected(roomName)
loadMessagesFromDatabase(roomName)
} else {
_connectionState.value = ConnectionState.Disconnected
}
// Call the legacy callback if set
connectionCallback?.invoke(roomName, isConnected)
}
} catch (e: Exception) {
android.util.Log.e("ChatRepository", "Error collecting connection flow", e)
_connectionState.value = ConnectionState.Error(e.message ?: "Connection error")
}
messageCallback?.invoke(userId, userName, content)
}
}
// Removed setupWifiAwareCallbacks - now using Flow-based collection only
fun startHostMode(roomName: String) {
currentRoomName = roomName
wifiAwareManager.startHostMode(roomName)
@@ -87,10 +123,16 @@ class ChatRepository @Inject constructor(
}
private fun loadMessagesFromDatabase(roomName: String) {
CoroutineScope(Dispatchers.IO).launch {
val storedMessages = messageDao.getMessagesForRoomOnce(roomName)
.map { it.toMessage() }
_messages.value = storedMessages
repositoryScope.launch {
try {
val storedMessages = messageDao.getMessagesForRoomOnce(roomName)
.map { it.toMessage() }
_messages.value = storedMessages
} catch (e: Exception) {
android.util.Log.e("ChatRepository", "Error loading messages from database", e)
// Don't crash, just continue with empty messages
_messages.value = emptyList()
}
}
}
@@ -123,11 +165,17 @@ class ChatRepository @Inject constructor(
}
private fun addMessage(message: Message) {
_messages.value = _messages.value + message
// Add message and sort by timestamp to ensure proper order
_messages.value = (_messages.value + message).sortedBy { it.timestamp }
// Save to database
CoroutineScope(Dispatchers.IO).launch {
messageDao.insertMessage(message.toEntity(currentRoomName))
repositoryScope.launch {
try {
messageDao.insertMessage(message.toEntity(currentRoomName))
} catch (e: Exception) {
android.util.Log.e("ChatRepository", "Error saving message to database", e)
// Don't crash, message is already in memory
}
}
}
@@ -141,16 +189,7 @@ class ChatRepository @Inject constructor(
fun setConnectionCallback(callback: (String, Boolean) -> Unit) {
connectionCallback = callback
wifiAwareManager.setConnectionCallback { roomName, isConnected ->
if (isConnected) {
currentRoomName = roomName
_connectionState.value = ConnectionState.Connected(roomName)
loadMessagesFromDatabase(roomName)
} else {
_connectionState.value = ConnectionState.Disconnected
}
callback(roomName, isConnected)
}
// Connection state is now handled by Flow collection
}
fun stop() {
@@ -158,11 +197,12 @@ class ChatRepository @Inject constructor(
wifiAwareManager.stop()
_connectionState.value = ConnectionState.Disconnected
_connectedUsers.value = emptyList()
repositoryScope.cancel()
}
private fun startConnectionMonitoring() {
connectionCheckJob?.cancel()
connectionCheckJob = CoroutineScope(Dispatchers.IO).launch {
connectionCheckJob = repositoryScope.launch {
while (isActive) {
delay(5000) // Check every 5 seconds
val timeSinceLastActivity = System.currentTimeMillis() - lastActivityTime

View File

@@ -18,6 +18,10 @@ import com.mattintech.lchat.viewmodel.LobbyState
import com.mattintech.lchat.viewmodel.LobbyViewModel
import com.mattintech.lchat.utils.LOG_PREFIX
import dagger.hilt.android.AndroidEntryPoint
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.launch
@AndroidEntryPoint
class LobbyFragment : Fragment() {
@@ -94,48 +98,60 @@ class LobbyFragment : Fragment() {
}
private fun observeViewModel() {
viewModel.savedUserName.observe(viewLifecycleOwner) { savedName ->
if (!savedName.isNullOrEmpty() && binding.nameInput.text.isNullOrEmpty()) {
binding.nameInput.setText(savedName)
}
}
viewModel.state.observe(viewLifecycleOwner) { state ->
when (state) {
is LobbyState.Idle -> {
binding.noRoomsText.visibility = View.GONE
}
is LobbyState.Connecting -> {
if (binding.modeRadioGroup.checkedRadioButtonId == R.id.clientRadio) {
binding.noRoomsText.visibility = View.VISIBLE
binding.noRoomsText.text = getString(R.string.connecting)
// Collect saved user name
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.savedUserName.collect { savedName ->
if (!savedName.isNullOrEmpty() && binding.nameInput.text.isNullOrEmpty()) {
binding.nameInput.setText(savedName)
}
}
is LobbyState.Connected -> {
if (binding.modeRadioGroup.checkedRadioButtonId == R.id.clientRadio) {
val userName = binding.nameInput.text?.toString()?.trim() ?: ""
viewModel.onConnectedToRoom(state.roomName, userName)
}
}
is LobbyState.Error -> {
binding.noRoomsText.visibility = View.VISIBLE
binding.noRoomsText.text = state.message
Toast.makeText(context, state.message, Toast.LENGTH_LONG).show()
}
}
}
viewModel.events.observe(viewLifecycleOwner) { event ->
when (event) {
is LobbyEvent.NavigateToChat -> {
navigateToChat(event.roomName, event.userName, event.isHost)
viewModel.clearEvent()
// Collect state
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.state.collect { state ->
when (state) {
is LobbyState.Idle -> {
binding.noRoomsText.visibility = View.GONE
}
is LobbyState.Connecting -> {
if (binding.modeRadioGroup.checkedRadioButtonId == R.id.clientRadio) {
binding.noRoomsText.visibility = View.VISIBLE
binding.noRoomsText.text = getString(R.string.connecting)
}
}
is LobbyState.Connected -> {
if (binding.modeRadioGroup.checkedRadioButtonId == R.id.clientRadio) {
val userName = binding.nameInput.text?.toString()?.trim() ?: ""
viewModel.onConnectedToRoom(state.roomName, userName)
}
}
is LobbyState.Error -> {
binding.noRoomsText.visibility = View.VISIBLE
binding.noRoomsText.text = state.message
Toast.makeText(context, state.message, Toast.LENGTH_LONG).show()
}
}
}
is LobbyEvent.ShowError -> {
Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show()
viewModel.clearEvent()
}
}
// Collect events
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.events.collect { event ->
when (event) {
is LobbyEvent.NavigateToChat -> {
navigateToChat(event.roomName, event.userName, event.isHost)
}
is LobbyEvent.ShowError -> {
Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show()
}
}
}
null -> {}
}
}
}

View File

@@ -1,7 +1,5 @@
package com.mattintech.lchat.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mattintech.lchat.data.Message
@@ -25,8 +23,8 @@ class ChatViewModel @Inject constructor(
private val chatRepository: ChatRepository
) : ViewModel() {
private val _state = MutableLiveData<ChatState>(ChatState.Connected)
val state: LiveData<ChatState> = _state
private val _state = MutableStateFlow<ChatState>(ChatState.Connected)
val state: StateFlow<ChatState> = _state.asStateFlow()
private val _messagesFlow = MutableStateFlow<Flow<List<Message>>>(flowOf(emptyList()))
@@ -85,10 +83,8 @@ class ChatViewModel @Inject constructor(
}
fun disconnect() {
viewModelScope.launch {
chatRepository.stop()
_state.value = ChatState.Disconnected
}
chatRepository.stop()
_state.value = ChatState.Disconnected
}
override fun onCleared() {

View File

@@ -1,12 +1,16 @@
package com.mattintech.lchat.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mattintech.lchat.repository.ChatRepository
import com.mattintech.lchat.utils.PreferencesManager
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -32,14 +36,14 @@ class LobbyViewModel @Inject constructor(
private val preferencesManager: PreferencesManager
) : ViewModel() {
private val _state = MutableLiveData<LobbyState>(LobbyState.Idle)
val state: LiveData<LobbyState> = _state
private val _state = MutableStateFlow<LobbyState>(LobbyState.Idle)
val state: StateFlow<LobbyState> = _state.asStateFlow()
private val _events = MutableLiveData<LobbyEvent?>()
val events: LiveData<LobbyEvent?> = _events
private val _events = MutableSharedFlow<LobbyEvent>()
val events: SharedFlow<LobbyEvent> = _events.asSharedFlow()
private val _savedUserName = MutableLiveData<String?>()
val savedUserName: LiveData<String?> = _savedUserName
private val _savedUserName = MutableStateFlow<String?>(null)
val savedUserName: StateFlow<String?> = _savedUserName.asStateFlow()
init {
setupConnectionCallback()
@@ -64,12 +68,16 @@ class LobbyViewModel @Inject constructor(
fun startHostMode(roomName: String, userName: String) {
if (roomName.isBlank()) {
_events.value = LobbyEvent.ShowError("Please enter a room name")
viewModelScope.launch {
_events.emit(LobbyEvent.ShowError("Please enter a room name"))
}
return
}
if (userName.isBlank()) {
_events.value = LobbyEvent.ShowError("Please enter your name")
viewModelScope.launch {
_events.emit(LobbyEvent.ShowError("Please enter your name"))
}
return
}
@@ -77,13 +85,15 @@ class LobbyViewModel @Inject constructor(
_state.value = LobbyState.Connecting
preferencesManager.saveUserName(userName)
chatRepository.startHostMode(roomName)
_events.value = LobbyEvent.NavigateToChat(roomName, userName, true)
_events.emit(LobbyEvent.NavigateToChat(roomName, userName, true))
}
}
fun startClientMode(userName: String) {
if (userName.isBlank()) {
_events.value = LobbyEvent.ShowError("Please enter your name")
viewModelScope.launch {
_events.emit(LobbyEvent.ShowError("Please enter your name"))
}
return
}
@@ -96,11 +106,13 @@ class LobbyViewModel @Inject constructor(
fun onConnectedToRoom(roomName: String, userName: String) {
preferencesManager.saveUserName(userName)
_events.value = LobbyEvent.NavigateToChat(roomName, userName, false)
viewModelScope.launch {
_events.emit(LobbyEvent.NavigateToChat(roomName, userName, false))
}
}
fun clearEvent() {
_events.value = null
// SharedFlow doesn't need clearing, events are consumed once
}
fun saveUserName(name: String) {