From a49270e7c32ba142ccb720bf066207d852130954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20St=C4=99pie=C5=84?= Date: Sat, 11 Jan 2025 17:22:45 +0100 Subject: [PATCH 1/3] Implement room leaving and admin transfer --- .../androidclient/game/datastreaming/Game.kt | 4 ++++ .../game/datastreaming/GameDataStreamer.kt | 2 ++ .../datastreaming/RemoteGameDataStreamer.kt | 13 ++++++++++--- .../messages/server/PlayerLeftMessage.kt | 8 +++++++- .../androidclient/game/model/GameState.kt | 1 + .../infra/network/WebsocketClient.kt | 8 ++++---- .../androidclient/ui/app/home/HomeScreen.kt | 5 ++++- .../ui/app/home/HomeViewModel.kt | 19 +++++++++++++------ 8 files changed, 45 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/Game.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/Game.kt index de9b652..6c2c5cc 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/Game.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/Game.kt @@ -15,4 +15,8 @@ class Game(private var gameState: GameState) { fun removePlayer(playerId: String) { gameState = gameState.copy(players = gameState.players.filter { it.id != playerId }) } + + fun setAdmin(playerId: String) { + gameState = gameState.copy(adminId = playerId) + } } diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/GameDataStreamer.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/GameDataStreamer.kt index 5a4cfdb..85f1b26 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/GameDataStreamer.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/GameDataStreamer.kt @@ -6,5 +6,7 @@ import kotlinx.coroutines.flow.StateFlow interface GameDataStreamer { suspend fun joinRoom(roomId: String) + fun leaveRoom() + fun gameStateFlow(): StateFlow } diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/RemoteGameDataStreamer.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/RemoteGameDataStreamer.kt index 743efd8..3278aa8 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/RemoteGameDataStreamer.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/RemoteGameDataStreamer.kt @@ -10,7 +10,6 @@ import com.github.feelbeatapp.androidclient.game.model.GameState import com.github.feelbeatapp.androidclient.infra.error.ErrorCode import com.github.feelbeatapp.androidclient.infra.error.FeelBeatException import com.github.feelbeatapp.androidclient.infra.network.NetworkClient -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel @@ -21,6 +20,7 @@ import kotlinx.coroutines.launch import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonPrimitive +import javax.inject.Inject class RemoteGameDataStreamer @Inject constructor(private val networkClient: NetworkClient) : GameDataStreamer { @@ -29,10 +29,14 @@ class RemoteGameDataStreamer @Inject constructor(private val networkClient: Netw private var scope: CoroutineScope? = null override suspend fun joinRoom(roomId: String) { - scope?.cancel() + leaveRoom() val newScope = CoroutineScope(Dispatchers.IO) scope = newScope newScope.launch { networkClient.connect("/ws/$roomId").collect { processMessage(it) } } + } + + override fun leaveRoom() { + scope?.cancel() scope = null gameStateFlow.value = null } @@ -53,7 +57,9 @@ class RemoteGameDataStreamer @Inject constructor(private val networkClient: Netw gameStateFlow.value = game?.gameState() } ServerMessageType.PLAYER_LEFT.name -> { - game?.removePlayer(Json.decodeFromString(content).payload) + val payload = Json.decodeFromString(content).payload + game?.removePlayer(payload.left) + game?.setAdmin(payload.admin) gameStateFlow.value = game?.gameState() } else -> Log.w("RemoteGameDataStreamer", "Received unexpected message: $content") @@ -71,6 +77,7 @@ class RemoteGameDataStreamer @Inject constructor(private val networkClient: Netw playlistName = initialState.playlist.name, playlistImageUrl = initialState.playlist.imageUrl, adminId = initialState.admin, + me = initialState.me, players = initialState.players, songs = initialState.playlist.songs.map { it.toSongModel() }, ) diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/messages/server/PlayerLeftMessage.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/messages/server/PlayerLeftMessage.kt index fba6702..16fab15 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/messages/server/PlayerLeftMessage.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/messages/server/PlayerLeftMessage.kt @@ -7,5 +7,11 @@ import kotlinx.serialization.Serializable @SerialName("PLAYER_LEFT") data class PlayerLeftMessage( override val type: String = ServerMessageType.PLAYER_LEFT.name, - val payload: String, + val payload: PlayerLeftPayload, ) : ServerMessage() + +@Serializable +data class PlayerLeftPayload( + val left: String, + val admin: String +) diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/game/model/GameState.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/game/model/GameState.kt index 88965a9..d2ec060 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/game/model/GameState.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/game/model/GameState.kt @@ -5,6 +5,7 @@ data class GameState( val playlistName: String, val playlistImageUrl: String, val adminId: String, + val me: String, val players: List, val songs: List, ) diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/infra/network/WebsocketClient.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/infra/network/WebsocketClient.kt index 93b78e4..5089137 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/infra/network/WebsocketClient.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/infra/network/WebsocketClient.kt @@ -15,14 +15,14 @@ import io.ktor.websocket.WebSocketSession import io.ktor.websocket.close import io.ktor.websocket.readText import io.ktor.websocket.send -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.onCompletion import java.util.ArrayDeque import java.util.Queue import javax.inject.Inject import javax.inject.Named +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onCompletion /** Websocket implementation of communication with FeelBeat server */ class WebsocketClient diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/home/HomeScreen.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/home/HomeScreen.kt index 1171d58..5ea647a 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/home/HomeScreen.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/home/HomeScreen.kt @@ -42,7 +42,10 @@ fun HomeScreen( val rooms by homeViewModel.rooms.collectAsState() val loading by homeViewModel.loading.collectAsState() - LaunchedEffect(null) { homeViewModel.loadRooms() } + LaunchedEffect(null) { + homeViewModel.leaveRoom() + homeViewModel.loadRooms() + } Box(modifier = Modifier.fillMaxSize()) { Column(modifier = Modifier.fillMaxSize()) { diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/home/HomeViewModel.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/home/HomeViewModel.kt index d4bc401..bff5396 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/home/HomeViewModel.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/home/HomeViewModel.kt @@ -2,29 +2,36 @@ package com.github.feelbeatapp.androidclient.ui.app.home import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.github.feelbeatapp.androidclient.api.feelbeat.FeelBeatApi +import com.github.feelbeatapp.androidclient.api.feelbeat.responses.RoomListViewResponse +import com.github.feelbeatapp.androidclient.game.datastreaming.GameDataStreamer import com.github.feelbeatapp.androidclient.infra.error.ErrorReceiver import com.github.feelbeatapp.androidclient.infra.error.FeelBeatException -import com.github.feelbeatapp.androidclient.api.feelbeat.responses.RoomListViewResponse -import com.github.feelbeatapp.androidclient.api.feelbeat.FeelBeatApi import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject -constructor(private val feelBeatApi: FeelBeatApi, private val errorReceiver: ErrorReceiver) : - ViewModel() { +constructor( + private val feelBeatApi: FeelBeatApi, + private val errorReceiver: ErrorReceiver, + private val gameDataStreamer: GameDataStreamer, +) : ViewModel() { private val _rooms = MutableStateFlow>(listOf()) val rooms: StateFlow> = _rooms.asStateFlow() private val _loading = MutableStateFlow(false) val loading = _loading.asStateFlow() - @SuppressWarnings("MagicNumber") + fun leaveRoom() { + gameDataStreamer.leaveRoom() + } + fun loadRooms() { _loading.value = true viewModelScope.launch { From dec6984283dd945f204e562ac16a0b0f0746fd62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20St=C4=99pie=C5=84?= Date: Sun, 12 Jan 2025 01:27:45 +0100 Subject: [PATCH 2/3] Enforce player limit --- .../game/datastreaming/GameDataStreamer.kt | 3 +- .../datastreaming/RemoteGameDataStreamer.kt | 45 +++++++++++++--- .../androidclient/infra/error/ErrorHandler.kt | 2 +- .../infra/network/WebsocketClient.kt | 5 +- .../ui/app/components/SongCard.kt | 2 +- .../androidclient/ui/app/home/HomeScreen.kt | 6 +-- .../lobby/{ => components}/LobbyBottomBar.kt | 2 +- .../ui/app/lobby/lobbyhome/LobbyHomeScreen.kt | 23 +++----- .../app/lobby/lobbyhome/LobbyHomeViewModel.kt | 7 +-- .../app/lobby/lobbysongs/LobbySongsScreen.kt | 6 +++ .../lobby/lobbysongs/LobbySongsViewModel.kt | 18 +------ .../ui/app/lobby/viewmodels/LobbyViewModel.kt | 41 ++++++++++++++ .../ui/app/navigation/AppGraph.kt | 24 +++++++-- .../ui/app/navigation/GameGraph.kt | 6 +-- .../ui/app/navigation/LobbyGraph.kt | 8 ++- .../roomsettings/components/SettingSlider.kt | 25 +++++++-- .../components/SettingsControls.kt | 53 +++++++++++-------- ...gsScreen.kt => LobbyRoomSettingsScreen.kt} | 22 +++++--- .../screens/NewRoomSettingsScreen.kt | 6 +-- .../viewmodels/EditRoomSettingsViewModel.kt | 3 -- .../viewmodels/LobbyRoomSettingsViewModel.kt | 30 +++++++++++ .../LoadingScreen.kt} | 10 ++-- .../androidclient/ui/login/AuthActivity.kt | 3 +- 23 files changed, 238 insertions(+), 112 deletions(-) rename app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/{ => components}/LobbyBottomBar.kt (96%) create mode 100644 app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/viewmodels/LobbyViewModel.kt rename app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/screens/{EditRoomSettingsScreen.kt => LobbyRoomSettingsScreen.kt} (63%) delete mode 100644 app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/viewmodels/EditRoomSettingsViewModel.kt create mode 100644 app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/viewmodels/LobbyRoomSettingsViewModel.kt rename app/src/main/java/com/github/feelbeatapp/androidclient/ui/{login/AuthLoadingScreen.kt => loading/LoadingScreen.kt} (90%) diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/GameDataStreamer.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/GameDataStreamer.kt index 85f1b26..f1120ba 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/GameDataStreamer.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/GameDataStreamer.kt @@ -1,10 +1,11 @@ package com.github.feelbeatapp.androidclient.game.datastreaming import com.github.feelbeatapp.androidclient.game.model.GameState +import kotlinx.coroutines.Deferred import kotlinx.coroutines.flow.StateFlow interface GameDataStreamer { - suspend fun joinRoom(roomId: String) + suspend fun joinRoom(roomId: String): Deferred fun leaveRoom() diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/RemoteGameDataStreamer.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/RemoteGameDataStreamer.kt index 3278aa8..58862f7 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/RemoteGameDataStreamer.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/game/datastreaming/RemoteGameDataStreamer.kt @@ -8,32 +8,61 @@ import com.github.feelbeatapp.androidclient.game.datastreaming.messages.server.P import com.github.feelbeatapp.androidclient.game.datastreaming.messages.server.ServerMessageType import com.github.feelbeatapp.androidclient.game.model.GameState import com.github.feelbeatapp.androidclient.infra.error.ErrorCode +import com.github.feelbeatapp.androidclient.infra.error.ErrorReceiver import com.github.feelbeatapp.androidclient.infra.error.FeelBeatException import com.github.feelbeatapp.androidclient.infra.network.NetworkClient +import javax.inject.Inject +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonPrimitive -import javax.inject.Inject -class RemoteGameDataStreamer @Inject constructor(private val networkClient: NetworkClient) : +class RemoteGameDataStreamer +@Inject +constructor(private val networkClient: NetworkClient, private val errorReceiver: ErrorReceiver) : GameDataStreamer { private var game: Game? = null private var gameStateFlow = MutableStateFlow(null) private var scope: CoroutineScope? = null - override suspend fun joinRoom(roomId: String) { - leaveRoom() - val newScope = CoroutineScope(Dispatchers.IO) - scope = newScope - newScope.launch { networkClient.connect("/ws/$roomId").collect { processMessage(it) } } - } + override suspend fun joinRoom(roomId: String): Deferred = + withContext(Dispatchers.IO) { + leaveRoom() + val newScope = CoroutineScope(Dispatchers.IO) + scope = newScope + + val connectionEstablished = CompletableDeferred() + + newScope.launch { + try { + networkClient.connect("/ws/$roomId").collect { + if (!connectionEstablished.isCompleted) { + connectionEstablished.complete(Unit) + } + processMessage(it) + } + } catch (e: FeelBeatException) { + if (!connectionEstablished.isCompleted) { + connectionEstablished.completeExceptionally(e) + } + errorReceiver.submitError(e) + } finally { + connectionEstablished.completeExceptionally(CancellationException()) + } + } + + return@withContext connectionEstablished + } override fun leaveRoom() { scope?.cancel() diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/infra/error/ErrorHandler.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/infra/error/ErrorHandler.kt index b6f66a9..fa44c56 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/infra/error/ErrorHandler.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/infra/error/ErrorHandler.kt @@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow class ErrorHandler @Inject constructor() : ErrorEmitter, ErrorReceiver { - private val _errors = MutableSharedFlow() + private val _errors = MutableSharedFlow(extraBufferCapacity = 32) override val errors = _errors.asSharedFlow() override suspend fun submitError(exception: FeelBeatException) { diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/infra/network/WebsocketClient.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/infra/network/WebsocketClient.kt index 5089137..aef857f 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/infra/network/WebsocketClient.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/infra/network/WebsocketClient.kt @@ -19,10 +19,11 @@ import java.util.ArrayDeque import java.util.Queue import javax.inject.Inject import javax.inject.Named +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.flowOn /** Websocket implementation of communication with FeelBeat server */ class WebsocketClient @@ -60,10 +61,10 @@ constructor( } } } + .flowOn(Dispatchers.IO) .catch { e -> throw FeelBeatException(ErrorCode.FEELBEAT_SERVER_FAILED_TO_JOIN_ROOM, e) } - .onCompletion { Log.d("Look", "completed") } } override suspend fun disconnect() { diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/components/SongCard.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/components/SongCard.kt index dc19ac7..0d07b5f 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/components/SongCard.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/components/SongCard.kt @@ -62,7 +62,7 @@ fun SongCard( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(10.dp, 0.dp), ) { - Column(verticalArrangement = Arrangement.Center) { + Column(verticalArrangement = Arrangement.Center, modifier = Modifier.weight(1f)) { Text(title, style = MaterialTheme.typography.titleMedium) Text(artist, style = MaterialTheme.typography.titleSmall) diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/home/HomeScreen.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/home/HomeScreen.kt index 5ea647a..4502230 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/home/HomeScreen.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/home/HomeScreen.kt @@ -21,7 +21,6 @@ import androidx.compose.material3.Text import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -29,6 +28,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.github.feelbeatapp.androidclient.R import com.github.feelbeatapp.androidclient.api.feelbeat.responses.RoomListViewResponse import com.github.feelbeatapp.androidclient.ui.app.components.RoomCard @@ -39,8 +39,8 @@ fun HomeScreen( onNewRoom: () -> Unit, homeViewModel: HomeViewModel = hiltViewModel(), ) { - val rooms by homeViewModel.rooms.collectAsState() - val loading by homeViewModel.loading.collectAsState() + val rooms by homeViewModel.rooms.collectAsStateWithLifecycle() + val loading by homeViewModel.loading.collectAsStateWithLifecycle() LaunchedEffect(null) { homeViewModel.leaveRoom() diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/LobbyBottomBar.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/components/LobbyBottomBar.kt similarity index 96% rename from app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/LobbyBottomBar.kt rename to app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/components/LobbyBottomBar.kt index df20084..f19650c 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/LobbyBottomBar.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/components/LobbyBottomBar.kt @@ -1,4 +1,4 @@ -package com.github.feelbeatapp.androidclient.ui.app.lobby +package com.github.feelbeatapp.androidclient.ui.app.lobby.components import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Home diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/lobbyhome/LobbyHomeScreen.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/lobbyhome/LobbyHomeScreen.kt index 6a1c98e..837a7b5 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/lobbyhome/LobbyHomeScreen.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/lobbyhome/LobbyHomeScreen.kt @@ -20,8 +20,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale @@ -31,27 +30,19 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil3.compose.AsyncImage import com.github.feelbeatapp.androidclient.R import com.github.feelbeatapp.androidclient.ui.app.components.PlayerCard +import com.github.feelbeatapp.androidclient.ui.loading.LoadingScreen @OptIn(ExperimentalLayoutApi::class) @Composable -fun LobbyHomeScreen( - roomId: String, - onPlay: () -> Unit, - viewModel: LobbyHomeViewModel = hiltViewModel(), -) { - val lobbyState = viewModel.lobbyHomeState.collectAsState().value - - LaunchedEffect(roomId) { - if (roomId != lobbyState.currentRoomId) { - viewModel.joinRoom(roomId) - } - } +fun LobbyHomeScreen(onPlay: () -> Unit, viewModel: LobbyHomeViewModel = hiltViewModel()) { + val lobbyState by viewModel.lobbyHomeState.collectAsStateWithLifecycle() if (lobbyState.currentRoomId == null) { - Text("Loading ") + LoadingScreen() return } @@ -109,5 +100,5 @@ fun LobbyHomeScreen( @Preview(showBackground = true) @Composable fun PreviewAcceptScreen() { - LobbyHomeScreen("Room id", {}) + LobbyHomeScreen({}) } diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/lobbyhome/LobbyHomeViewModel.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/lobbyhome/LobbyHomeViewModel.kt index 5eaf87e..6e30872 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/lobbyhome/LobbyHomeViewModel.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/lobbyhome/LobbyHomeViewModel.kt @@ -6,11 +6,11 @@ import com.github.feelbeatapp.androidclient.game.datastreaming.GameDataStreamer import com.github.feelbeatapp.androidclient.game.model.GameState import com.github.feelbeatapp.androidclient.game.model.Player import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import javax.inject.Inject data class LobbyHomeState( val currentRoomId: String? = null, @@ -45,9 +45,4 @@ class LobbyHomeViewModel @Inject constructor(private val gameDataStreamer: GameD _lobbyHomeState.update { it.copy(currentRoomId = null) } } } - - suspend fun joinRoom(roomId: String) { - _lobbyHomeState.update { it.copy(currentRoomId = null) } - gameDataStreamer.joinRoom(roomId) - } } diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/lobbysongs/LobbySongsScreen.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/lobbysongs/LobbySongsScreen.kt index 627cf4e..e1b1b94 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/lobbysongs/LobbySongsScreen.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/lobbysongs/LobbySongsScreen.kt @@ -14,12 +14,18 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.github.feelbeatapp.androidclient.ui.app.components.SongCard +import com.github.feelbeatapp.androidclient.ui.loading.LoadingScreen import com.github.feelbeatapp.androidclient.ui.theme.FeelBeatTheme @Composable fun LobbySongsScreen(viewModel: LobbySongsViewModel = hiltViewModel()) { val songs by viewModel.songs.collectAsStateWithLifecycle() + if (songs.isEmpty()) { + LoadingScreen() + return + } + LazyColumn( verticalArrangement = Arrangement.spacedBy(16.dp), horizontalAlignment = Alignment.CenterHorizontally, diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/lobbysongs/LobbySongsViewModel.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/lobbysongs/LobbySongsViewModel.kt index 206f178..c4967d9 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/lobbysongs/LobbySongsViewModel.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/lobbysongs/LobbySongsViewModel.kt @@ -1,39 +1,25 @@ package com.github.feelbeatapp.androidclient.ui.app.lobby.lobbysongs -import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.github.feelbeatapp.androidclient.game.datastreaming.GameDataStreamer import com.github.feelbeatapp.androidclient.game.model.Song import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject -import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import javax.inject.Inject @HiltViewModel class LobbySongsViewModel @Inject constructor(private val gameDataStreamer: GameDataStreamer) : ViewModel() { - private val _songs = - MutableStateFlow>( - listOf( - Song( - "asdf", - "Aerials", - "System of a down", - "https://upload.wikimedia.org/wikipedia/it/2/20/Aerials.png", - 320.seconds, - ) - ) - ) + private val _songs = MutableStateFlow>(listOf()) val songs = _songs.asStateFlow() init { viewModelScope.launch { gameDataStreamer.gameStateFlow().collect { gameState -> _songs.value = gameState?.songs ?: listOf() - Log.d("yup", "updating") } } } diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/viewmodels/LobbyViewModel.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/viewmodels/LobbyViewModel.kt new file mode 100644 index 0000000..ea5e17f --- /dev/null +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/lobby/viewmodels/LobbyViewModel.kt @@ -0,0 +1,41 @@ +package com.github.feelbeatapp.androidclient.ui.app.lobby.viewmodels + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.github.feelbeatapp.androidclient.game.datastreaming.GameDataStreamer +import com.github.feelbeatapp.androidclient.infra.error.FeelBeatException +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +data class LobbyState(val loading: Boolean = false, val joinFailed: Boolean = false) + +@HiltViewModel +class LobbyViewModel @Inject constructor(private val gameDataStreamer: GameDataStreamer) : + ViewModel() { + private val _lobbyState = MutableStateFlow(LobbyState()) + val lobbyState = _lobbyState.asStateFlow() + + fun joinRoom(roomId: String) { + if (lobbyState.value.loading) { + return + } + + _lobbyState.update { it.copy(loading = true, joinFailed = false) } + viewModelScope.launch { + try { + gameDataStreamer.joinRoom(roomId).await() + _lobbyState.update { it.copy(loading = false, joinFailed = false) } + } catch (_: FeelBeatException) { + _lobbyState.update { it.copy(loading = false, joinFailed = true) } + } + } + } + + fun reset() { + _lobbyState.value = LobbyState(loading = false, joinFailed = false) + } +} diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/navigation/AppGraph.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/navigation/AppGraph.kt index 446fd25..e22ab4d 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/navigation/AppGraph.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/navigation/AppGraph.kt @@ -1,9 +1,12 @@ package com.github.feelbeatapp.androidclient.ui.app.navigation import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavBackStackEntry import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost @@ -14,15 +17,24 @@ import androidx.navigation.compose.rememberNavController import com.github.feelbeatapp.androidclient.R import com.github.feelbeatapp.androidclient.ui.app.AppScreen import com.github.feelbeatapp.androidclient.ui.app.home.HomeScreen -import com.github.feelbeatapp.androidclient.ui.app.lobby.LobbyBottomBar +import com.github.feelbeatapp.androidclient.ui.app.lobby.components.LobbyBottomBar +import com.github.feelbeatapp.androidclient.ui.app.lobby.viewmodels.LobbyViewModel import com.github.feelbeatapp.androidclient.ui.app.roomsettings.screens.NewRoomSettingsScreen import com.github.feelbeatapp.androidclient.ui.theme.FeelBeatTheme @Composable -fun AppGraph(onLogout: () -> Unit) { +fun AppGraph(onLogout: () -> Unit, lobbyViewModel: LobbyViewModel = hiltViewModel()) { val navController = rememberNavController() val navBackStackEntry by navController.currentBackStackEntryAsState() val route = navBackStackEntry?.destination?.route + val lobbyState by lobbyViewModel.lobbyState.collectAsStateWithLifecycle() + + LaunchedEffect(lobbyState.joinFailed) { + if (lobbyState.joinFailed) { + navController.popBackStack(AppRoute.HOME.route, inclusive = false) + lobbyViewModel.reset() + } + } AppScreen( title = getRouteTitle(route), @@ -44,6 +56,7 @@ fun AppGraph(onLogout: () -> Unit) { composable(route = AppRoute.HOME.route) { HomeScreen( onRoomSelect = { roomId -> + lobbyViewModel.joinRoom(roomId) navController.navigate( AppRoute.ROOM_LOBBY.withArgs(mapOf("roomId" to roomId)) ) @@ -54,6 +67,7 @@ fun AppGraph(onLogout: () -> Unit) { composable(route = AppRoute.NEW_ROOM.route) { NewRoomSettingsScreen( onRoomCreated = { + lobbyViewModel.joinRoom(it) navController.navigate( AppRoute.ROOM_LOBBY.withArgs(mapOf("roomId" to it)) ) { @@ -71,7 +85,7 @@ fun AppGraph(onLogout: () -> Unit) { AppRoute.START_GAME.withArgs(mapOf("roomId" to roomId)) ) } - } + }, ) } @@ -125,9 +139,9 @@ fun getNavigateBackBehaviour(route: String?, navController: NavHostController): } } -fun NavBackStackEntry.getRoomId(): String { +fun NavBackStackEntry.getRoomId(): String? { val roomId = arguments?.getString("roomId") - return roomId!! + return roomId } @Composable diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/navigation/GameGraph.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/navigation/GameGraph.kt index f42c85c..bda7449 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/navigation/GameGraph.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/navigation/GameGraph.kt @@ -9,15 +9,15 @@ import com.github.feelbeatapp.androidclient.ui.app.game.startgame.StartGameScree fun NavGraphBuilder.gameGraph(onNavigate: (String) -> Unit) { composable(route = AppRoute.START_GAME.route) { - StartGameScreen(roomId = it.getRoomId(), onNavigate = onNavigate) + StartGameScreen(roomId = it.getRoomId()!!, onNavigate = onNavigate) } composable(route = AppRoute.GUESS.route) { - GuessSongScreen(roomId = it.getRoomId(), onNavigate = onNavigate) + GuessSongScreen(roomId = it.getRoomId()!!, onNavigate = onNavigate) } composable(route = AppRoute.GUESS_RESULT.route) { - GuessResultScreen(roomId = it.getRoomId(), onNavigate = onNavigate) + GuessResultScreen(roomId = it.getRoomId()!!, onNavigate = onNavigate) } composable(route = AppRoute.GAME_RESULT.route) { GameResultScreen(onNavigate = onNavigate) } diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/navigation/LobbyGraph.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/navigation/LobbyGraph.kt index 2e6116a..5f3bc85 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/navigation/LobbyGraph.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/navigation/LobbyGraph.kt @@ -4,14 +4,12 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import com.github.feelbeatapp.androidclient.ui.app.lobby.lobbyhome.LobbyHomeScreen import com.github.feelbeatapp.androidclient.ui.app.lobby.lobbysongs.LobbySongsScreen -import com.github.feelbeatapp.androidclient.ui.app.roomsettings.screens.EditRoomSettingsScreen +import com.github.feelbeatapp.androidclient.ui.app.roomsettings.screens.LobbyRoomSettingsScreen fun NavGraphBuilder.lobbyGraph(onPlay: () -> Unit) { - composable(route = AppRoute.ROOM_LOBBY.route) { - LobbyHomeScreen(it.getRoomId(), onPlay = onPlay) - } + composable(route = AppRoute.ROOM_LOBBY.route) { LobbyHomeScreen(onPlay = onPlay) } composable(route = AppRoute.ROOM_SONGS.route) { LobbySongsScreen() } - composable(route = AppRoute.ROOM_EDIT.route) { EditRoomSettingsScreen() } + composable(route = AppRoute.ROOM_EDIT.route) { LobbyRoomSettingsScreen() } } diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/components/SettingSlider.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/components/SettingSlider.kt index 5895759..ac26181 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/components/SettingSlider.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/components/SettingSlider.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Slider @@ -12,7 +13,9 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.github.feelbeatapp.androidclient.ui.theme.FeelBeatTheme @Composable fun SettingSlider( @@ -20,7 +23,8 @@ fun SettingSlider( value: Int, onValueChange: (Int) -> Unit, valueRange: IntRange, - steps: Int, + interval: Int, + enabled: Boolean = true, modifier: Modifier = Modifier, ) { Column(modifier = modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) { @@ -34,11 +38,26 @@ fun SettingSlider( value = value.toFloat(), onValueChange = { onValueChange(it.toInt()) }, valueRange = valueRange.first.toFloat()..valueRange.last.toFloat(), - steps = steps - 1, - modifier = Modifier.weight(1f), + steps = (valueRange.last - valueRange.first) / interval - 1, + enabled = enabled, + modifier = Modifier.weight(1f).height(30.dp), ) Spacer(modifier = Modifier.width(16.dp)) Text(text = value.toString(), style = MaterialTheme.typography.bodyMedium) } } } + +@Composable +@Preview(showBackground = true) +fun SettingSliderPreview() { + FeelBeatTheme { + SettingSlider( + label = "Label preview", + value = 2, + onValueChange = {}, + valueRange = 1..5, + interval = 1, + ) + } +} diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/components/SettingsControls.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/components/SettingsControls.kt index 007e522..84f0536 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/components/SettingsControls.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/components/SettingsControls.kt @@ -18,15 +18,16 @@ import com.github.feelbeatapp.androidclient.R import com.github.feelbeatapp.androidclient.ui.app.roomsettings.viewmodels.RoomSettingsViewModel @Composable -fun SettingsControls(viewModel: RoomSettingsViewModel) { +fun SettingsControls(viewModel: RoomSettingsViewModel, enabled: Boolean = true) { val roomSettings by viewModel.roomSettings.collectAsState() SettingSlider( label = stringResource(R.string.number_of_players), value = roomSettings.maxPlayers, onValueChange = { viewModel.setMaxPlayers(it) }, - valueRange = 1..5, - steps = 4, + valueRange = 1..7, + interval = 1, + enabled = enabled, ) SettingSlider( @@ -34,7 +35,8 @@ fun SettingsControls(viewModel: RoomSettingsViewModel) { value = roomSettings.turnCount, onValueChange = { viewModel.setTurnCount(it) }, valueRange = 1..10, - steps = 9, + interval = 9, + enabled = enabled, ) SettingSlider( @@ -42,7 +44,8 @@ fun SettingsControls(viewModel: RoomSettingsViewModel) { value = roomSettings.timePenaltyPerSecond, onValueChange = { viewModel.setTimePenaltyPerSecond(it) }, valueRange = 1..20, - steps = 19, + interval = 19, + enabled = enabled, ) SettingSlider( @@ -50,7 +53,8 @@ fun SettingsControls(viewModel: RoomSettingsViewModel) { value = roomSettings.basePoints, onValueChange = { viewModel.setBasePoints(it) }, valueRange = 100..1000, - steps = 9, + interval = 9, + enabled = enabled, ) SettingSlider( @@ -58,24 +62,27 @@ fun SettingsControls(viewModel: RoomSettingsViewModel) { value = roomSettings.incorrectGuessPenalty, onValueChange = { viewModel.setIncorrectGuessPenalty(it) }, valueRange = 50..500, - steps = 9, + interval = 9, + enabled = enabled, ) - Text( - text = stringResource(R.string.playlist_link), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(top = 8.dp), - ) + if (enabled) { + Text( + text = stringResource(R.string.playlist_link), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(top = 8.dp), + ) - TextField( - value = roomSettings.playlistLink, - keyboardOptions = - KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Uri, - imeAction = ImeAction.Done, - ), - onValueChange = { viewModel.setPlaylistLink(it) }, - modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp), - label = { Text(stringResource(R.string.enter_playlist_link)) }, - ) + TextField( + value = roomSettings.playlistLink, + keyboardOptions = + KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Uri, + imeAction = ImeAction.Done, + ), + onValueChange = { viewModel.setPlaylistLink(it) }, + modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp), + label = { Text(stringResource(R.string.enter_playlist_link)) }, + ) + } } diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/screens/EditRoomSettingsScreen.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/screens/LobbyRoomSettingsScreen.kt similarity index 63% rename from app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/screens/EditRoomSettingsScreen.kt rename to app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/screens/LobbyRoomSettingsScreen.kt index 8bf7ca1..af5709f 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/screens/EditRoomSettingsScreen.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/screens/LobbyRoomSettingsScreen.kt @@ -7,28 +7,38 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.github.feelbeatapp.androidclient.ui.app.roomsettings.components.SettingsControls -import com.github.feelbeatapp.androidclient.ui.app.roomsettings.viewmodels.EditRoomSettingsViewModel -import com.github.feelbeatapp.androidclient.ui.app.roomsettings.viewmodels.RoomSettingsViewModel +import com.github.feelbeatapp.androidclient.ui.app.roomsettings.viewmodels.LobbyRoomSettingsViewModel +import com.github.feelbeatapp.androidclient.ui.loading.LoadingScreen @Composable -fun EditRoomSettingsScreen( - viewModel: RoomSettingsViewModel = EditRoomSettingsViewModel(), +fun LobbyRoomSettingsScreen( + viewModel: LobbyRoomSettingsViewModel = hiltViewModel(), modifier: Modifier = Modifier, ) { + val editRoomState by viewModel.editRoomState.collectAsStateWithLifecycle() + + if (!editRoomState.loaded) { + LoadingScreen() + return + } + Column( modifier = modifier.fillMaxSize().padding(16.dp).verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(24.dp), ) { - SettingsControls(viewModel) + SettingsControls(viewModel, enabled = editRoomState.isAdmin) } } @Preview(showBackground = true, widthDp = 360, heightDp = 640) @Composable fun PreviewRoomSettingsScreen() { - EditRoomSettingsScreen() + LobbyRoomSettingsScreen() } diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/screens/NewRoomSettingsScreen.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/screens/NewRoomSettingsScreen.kt index 067a123..572e103 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/screens/NewRoomSettingsScreen.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/screens/NewRoomSettingsScreen.kt @@ -16,13 +16,13 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.github.feelbeatapp.androidclient.R import com.github.feelbeatapp.androidclient.ui.app.roomsettings.components.SettingsControls import com.github.feelbeatapp.androidclient.ui.app.roomsettings.viewmodels.NewRoomSettingsViewModel @@ -33,8 +33,8 @@ fun NewRoomSettingsScreen( newRoomSettingsViewModel: NewRoomSettingsViewModel = hiltViewModel(), modifier: Modifier = Modifier, ) { - val createdRoomId by newRoomSettingsViewModel.roomCreated.collectAsState(null) - val loading by newRoomSettingsViewModel.loading.collectAsState() + val createdRoomId by newRoomSettingsViewModel.roomCreated.collectAsStateWithLifecycle(null) + val loading by newRoomSettingsViewModel.loading.collectAsStateWithLifecycle() LaunchedEffect(createdRoomId) { val roomId = createdRoomId diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/viewmodels/EditRoomSettingsViewModel.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/viewmodels/EditRoomSettingsViewModel.kt deleted file mode 100644 index 2049bd2..0000000 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/viewmodels/EditRoomSettingsViewModel.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.github.feelbeatapp.androidclient.ui.app.roomsettings.viewmodels - -class EditRoomSettingsViewModel : RoomSettingsViewModel() diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/viewmodels/LobbyRoomSettingsViewModel.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/viewmodels/LobbyRoomSettingsViewModel.kt new file mode 100644 index 0000000..550c8dd --- /dev/null +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/roomsettings/viewmodels/LobbyRoomSettingsViewModel.kt @@ -0,0 +1,30 @@ +package com.github.feelbeatapp.androidclient.ui.app.roomsettings.viewmodels + +import androidx.lifecycle.viewModelScope +import com.github.feelbeatapp.androidclient.game.datastreaming.GameDataStreamer +import com.github.feelbeatapp.androidclient.game.model.GameState +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +data class EditRoomState(val isAdmin: Boolean = false, val loaded: Boolean = false) + +@HiltViewModel +class LobbyRoomSettingsViewModel +@Inject +constructor(private val gameDataStreamer: GameDataStreamer) : RoomSettingsViewModel() { + private val _editRoomState = MutableStateFlow(EditRoomState()) + val editRoomState = _editRoomState.asStateFlow() + + init { + updateState(gameDataStreamer.gameStateFlow().value) + viewModelScope.launch { gameDataStreamer.gameStateFlow().collect { updateState(it) } } + } + + private fun updateState(gameState: GameState?) { + _editRoomState.value = + EditRoomState(isAdmin = gameState?.adminId == gameState?.me, loaded = gameState != null) + } +} diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/login/AuthLoadingScreen.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/loading/LoadingScreen.kt similarity index 90% rename from app/src/main/java/com/github/feelbeatapp/androidclient/ui/login/AuthLoadingScreen.kt rename to app/src/main/java/com/github/feelbeatapp/androidclient/ui/loading/LoadingScreen.kt index 8545094..38134db 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/login/AuthLoadingScreen.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/loading/LoadingScreen.kt @@ -1,4 +1,4 @@ -package com.github.feelbeatapp.androidclient.ui.login +package com.github.feelbeatapp.androidclient.ui.loading import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -20,7 +20,7 @@ import com.github.feelbeatapp.androidclient.ui.theme.FeelBeatTheme const val SPINNER_HEIGHT_OFFSET = 0.5f @Composable -fun AuthLoadingScreen() { +fun LoadingScreen(text: String = "Loading") { FeelBeatTheme { Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -39,7 +39,7 @@ fun AuthLoadingScreen() { } Text( - text = "Loading", + text = text, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleLarge, ) @@ -49,6 +49,6 @@ fun AuthLoadingScreen() { @Preview(showBackground = true) @Composable -fun AuthLoadingScreenPreview() { - AuthLoadingScreen() +fun LoadingScreenPreview() { + LoadingScreen() } diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/login/AuthActivity.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/login/AuthActivity.kt index 6693d49..060687c 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/login/AuthActivity.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/login/AuthActivity.kt @@ -8,6 +8,7 @@ import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import com.github.feelbeatapp.androidclient.infra.auth.AuthManager import com.github.feelbeatapp.androidclient.ui.MainActivity +import com.github.feelbeatapp.androidclient.ui.loading.LoadingScreen import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -42,6 +43,6 @@ class AuthActivity : ComponentActivity() { finish() } - setContent { AuthLoadingScreen() } + setContent { LoadingScreen() } } } From e20274b6504db8d70c3722e6d147792fe224f9ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20St=C4=99pie=C5=84?= Date: Sun, 12 Jan 2025 02:40:52 +0100 Subject: [PATCH 3/3] Add room leaving on logout, fix too room list --- .../github/feelbeatapp/androidclient/ui/app/AppViewModel.kt | 5 ++++- .../feelbeatapp/androidclient/ui/app/home/HomeScreen.kt | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/AppViewModel.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/AppViewModel.kt index 0507967..ff8143f 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/AppViewModel.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/AppViewModel.kt @@ -5,17 +5,18 @@ import androidx.compose.material3.SnackbarHostState import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.github.feelbeatapp.androidclient.api.spotify.SpotifyAPI +import com.github.feelbeatapp.androidclient.game.datastreaming.GameDataStreamer import com.github.feelbeatapp.androidclient.infra.auth.AuthManager import com.github.feelbeatapp.androidclient.infra.error.ErrorEmitter import com.github.feelbeatapp.androidclient.infra.error.FeelBeatException import com.github.feelbeatapp.androidclient.infra.error.FeelBeatServerException import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext -import javax.inject.Inject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import javax.inject.Inject data class PlayerIdentity(val name: String, val imageUrl: String) @@ -27,6 +28,7 @@ constructor( private val authManager: AuthManager, private val spotifyAPI: SpotifyAPI, private val errorEmitter: ErrorEmitter, + private val gameDataStreamer: GameDataStreamer ) : ViewModel() { private val _playerIdentity = MutableStateFlow(null) val playerIdentity = _playerIdentity.asStateFlow() @@ -67,5 +69,6 @@ constructor( fun logout() { authManager.logout() + gameDataStreamer.leaveRoom() } } diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/home/HomeScreen.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/home/HomeScreen.kt index 4502230..8b869cf 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/home/HomeScreen.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/app/home/HomeScreen.kt @@ -109,7 +109,7 @@ fun RoomList( modifier: Modifier = Modifier, ) { PullToRefreshBox(isRefreshing = isRefreshing, onRefresh = onRefresh, modifier = modifier) { - LazyColumn(Modifier) { + LazyColumn(modifier = Modifier.fillMaxSize()) { items(items) { ListItem({ RoomCard(room = it, onClick = { onRoomSelect(it.id) }) }) } } }