diff --git a/app/build.gradle.kts b/app/build.gradle.kts index efdced9..cbc7fbf 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -112,6 +112,7 @@ dependencies { implementation(libs.io.ktor.client) implementation(libs.io.ktor.content.negotiation) implementation(libs.io.ktor.json.serialization) + implementation(libs.fuzzywuzzy) // hilt implementation(libs.hilt) diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/acceptGame/AcceptGameScreen.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/acceptGame/AcceptGameScreen.kt index aa67308..4bde89d 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/acceptGame/AcceptGameScreen.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/acceptGame/AcceptGameScreen.kt @@ -34,6 +34,7 @@ import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import com.github.feelbeatapp.androidclient.R import com.github.feelbeatapp.androidclient.ui.FeelBeatRoute +import com.github.feelbeatapp.androidclient.ui.state.Song import com.github.feelbeatapp.androidclient.ui.startGame.PlayerCard @Composable diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/acceptGame/AcceptGameViewModel.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/acceptGame/AcceptGameViewModel.kt index fd8fed5..b7465df 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/acceptGame/AcceptGameViewModel.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/acceptGame/AcceptGameViewModel.kt @@ -3,27 +3,15 @@ package com.github.feelbeatapp.androidclient.ui.acceptGame import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.github.feelbeatapp.androidclient.R -import com.github.feelbeatapp.androidclient.ui.home.Room -import com.github.feelbeatapp.androidclient.ui.startGame.Player +import com.github.feelbeatapp.androidclient.ui.state.GameState +import com.github.feelbeatapp.androidclient.ui.state.Player +import com.github.feelbeatapp.androidclient.ui.state.Song import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -data class Song(val id: Int, val title: String) - -data class Playlist(val name: String, val songs: List) - -data class GameState( - val players: List = emptyList(), - val songs: List = emptyList(), - val selectedRoom: Room? = null, - val playlist: Playlist = Playlist(name = "Playlist #1", songs = emptyList()), - val snippetDuration: Int = 30, - val pointsToWin: Int = 10, -) - class AcceptGameViewModel : ViewModel() { private val _gameState = MutableStateFlow(GameState()) val gameState: StateFlow = _gameState.asStateFlow() diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/gameResult/GameResultScreen.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/gameResult/GameResultScreen.kt index 7f9f12f..f441f02 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/gameResult/GameResultScreen.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/gameResult/GameResultScreen.kt @@ -31,7 +31,7 @@ import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import com.github.feelbeatapp.androidclient.R import com.github.feelbeatapp.androidclient.ui.FeelBeatRoute -import com.github.feelbeatapp.androidclient.ui.guessSong.PlayerWithResult +import com.github.feelbeatapp.androidclient.ui.state.PlayerWithResult @Composable fun GameResultScreen( diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/gameResult/GameResultViewModel.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/gameResult/GameResultViewModel.kt index 911df3c..f3111d2 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/gameResult/GameResultViewModel.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/gameResult/GameResultViewModel.kt @@ -3,9 +3,9 @@ package com.github.feelbeatapp.androidclient.ui.gameResult import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.github.feelbeatapp.androidclient.R -import com.github.feelbeatapp.androidclient.ui.guessSong.PlayerWithResult +import com.github.feelbeatapp.androidclient.ui.state.Player +import com.github.feelbeatapp.androidclient.ui.state.PlayerWithResult import com.github.feelbeatapp.androidclient.ui.guessSong.ResultStatus -import com.github.feelbeatapp.androidclient.ui.startGame.Player import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/guessSong/GuessResultScreen.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/guessSong/GuessResultScreen.kt index 087ea07..7012e75 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/guessSong/GuessResultScreen.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/guessSong/GuessResultScreen.kt @@ -72,7 +72,7 @@ fun GuessResultScreen( if (guessState.players.any { it.resultStatus == ResultStatus.CORRECT }) { stringResource(R.string.you_guessed_song_correctly) } else { - stringResource(R.string.ups_that_s_not_correct) + stringResource(R.string.ups_that_s_not_correct_answer) }, style = MaterialTheme.typography.headlineLarge, fontWeight = FontWeight.Bold, diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/guessSong/GuessSongScreen.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/guessSong/GuessSongScreen.kt index 3c75008..1cc55e9 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/guessSong/GuessSongScreen.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/guessSong/GuessSongScreen.kt @@ -26,12 +26,8 @@ import androidx.compose.material3.Slider import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -45,8 +41,8 @@ import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import com.github.feelbeatapp.androidclient.R import com.github.feelbeatapp.androidclient.ui.FeelBeatRoute -import com.github.feelbeatapp.androidclient.ui.acceptGame.Song -import kotlinx.coroutines.delay +import com.github.feelbeatapp.androidclient.ui.state.PlayerWithResult +import com.github.feelbeatapp.androidclient.ui.state.Song @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -55,15 +51,11 @@ fun GuessSongScreen( viewModel: GuessSongViewModel = GuessSongViewModel(), ) { val guessState by viewModel.guessState.collectAsState() - var timeLeft by remember { mutableIntStateOf(guessState.snippetDuration) } + val timeLeft by viewModel.timeLeft.collectAsState() + val gameEnded by viewModel.gameEnded.collectAsState() - LaunchedEffect(key1 = timeLeft) { - if (timeLeft > 0) { - delay(timeMillis = 1000) - timeLeft -= 1 - } else { - navController.navigate(FeelBeatRoute.GUESS_RESULT.name) - } + if (gameEnded) { + navController.navigate(FeelBeatRoute.GUESS_RESULT.name) } Scaffold( @@ -111,9 +103,9 @@ fun GuessSongScreen( verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxSize(), ) { - items(guessState.songs.size) { index -> + items(guessState.filteredSongs.size) { index -> SongItem( - song = guessState.songs[index], + song = guessState.filteredSongs[index], onClick = { navController.navigate(FeelBeatRoute.GUESS_RESULT.name) }, ) } diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/guessSong/GuessSongViewModel.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/guessSong/GuessSongViewModel.kt index e7a49ce..e9880d9 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/guessSong/GuessSongViewModel.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/guessSong/GuessSongViewModel.kt @@ -4,35 +4,31 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.github.feelbeatapp.androidclient.R -import com.github.feelbeatapp.androidclient.ui.acceptGame.Playlist -import com.github.feelbeatapp.androidclient.ui.acceptGame.Song -import com.github.feelbeatapp.androidclient.ui.home.Room -import com.github.feelbeatapp.androidclient.ui.startGame.Player +import com.github.feelbeatapp.androidclient.ui.state.GuessState +import com.github.feelbeatapp.androidclient.ui.state.Player +import com.github.feelbeatapp.androidclient.ui.state.PlayerWithResult +import com.github.feelbeatapp.androidclient.ui.state.Song +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch - -data class PlayerWithResult(val player: Player, val resultStatus: ResultStatus, val points: Int) - -data class GuessState( - val players: List = emptyList(), - val songs: List = emptyList(), - val selectedRoom: Room? = null, - val playlist: Playlist = Playlist(name = "Playlist #1", songs = emptyList()), - val snippetDuration: Int = 30, - val pointsToWin: Int = 10, - val searchQuery: TextFieldValue = TextFieldValue(""), - val currentSong: Song? = null, -) +import me.xdrop.fuzzywuzzy.FuzzySearch class GuessSongViewModel : ViewModel() { private val _guessState = MutableStateFlow(GuessState()) val guessState: StateFlow = _guessState.asStateFlow() + private val _timeLeft = MutableStateFlow(_guessState.value.snippetDuration) + val timeLeft: StateFlow = _timeLeft.asStateFlow() + + private val _gameEnded = MutableStateFlow(false) + val gameEnded: StateFlow = _gameEnded.asStateFlow() + init { loadPlayers() loadPlaylist() + startTimer() } private fun loadPlayers() { @@ -64,20 +60,53 @@ class GuessSongViewModel : ViewModel() { viewModelScope.launch { val examplePlaylist = listOf( - Song(1, "Song 1"), - Song(2, "Song 2"), - Song(3, "Song 3"), - Song(4, "Song 4"), - Song(5, "Song 5"), - Song(6, "Song 6"), + Song(1, "Hello"), + Song(2, "Highway to Hell"), + Song(3, "Hell's Comin' with Me"), + Song(4, "Riptide"), + Song(5, "Rozmowa"), + Song(6, "Rower"), + Song(7, "R"), + Song(8, "Róż"), + Song(9, "Rota"), + Song(10, "Szarość i Róż"), ) _guessState.value = - _guessState.value.copy(songs = examplePlaylist, currentSong = examplePlaylist[0]) + _guessState.value.copy( + songs = examplePlaylist, + filteredSongs = examplePlaylist, + currentSong = examplePlaylist[0], + ) + } + } + + @SuppressWarnings("MagicNumber") + private fun startTimer() { + viewModelScope.launch { + while (_timeLeft.value > 0) { + delay(1000L) + _timeLeft.emit(_timeLeft.value - 1) + } + _gameEnded.value = true } } - fun updateSearchQuery(newQuery: TextFieldValue) { - _guessState.value = _guessState.value.copy(searchQuery = newQuery) + fun updateSearchQuery(query: TextFieldValue) { + _guessState.value = _guessState.value.copy(searchQuery = query) + performSearch(query.text) + } + + @SuppressWarnings("MagicNumber") + private fun performSearch(query: String) { + val sortedList = + if (query.isBlank()) { + _guessState.value.songs + } else { + _guessState.value.songs.sortedByDescending { song -> + FuzzySearch.ratio(song.title.lowercase(), query.lowercase()) + } + } + _guessState.value = _guessState.value.copy(filteredSongs = sortedList) } fun submitAnswer(playerName: String, isCorrect: Boolean) { diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/home/HomeScreen.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/home/HomeScreen.kt index a02ebf4..b111f24 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/home/HomeScreen.kt @@ -38,6 +38,7 @@ import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import com.github.feelbeatapp.androidclient.R import com.github.feelbeatapp.androidclient.ui.FeelBeatRoute +import com.github.feelbeatapp.androidclient.ui.state.Room @Composable fun HomeScreen( diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/home/HomeViewModel.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/home/HomeViewModel.kt index 840eee0..e0d403f 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/home/HomeViewModel.kt @@ -2,20 +2,12 @@ package com.github.feelbeatapp.androidclient.ui.home import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.github.feelbeatapp.androidclient.ui.state.Room import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch -data class Room( - val id: Int, - val name: String, - val maxPlayers: Int, - val snippetDuration: Int, - val pointsToWin: Int, - val playlistLink: String, -) - class HomeViewModel : ViewModel() { private val _rooms = MutableStateFlow>(emptyList()) diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/roomSettings/RoomSettingsViewModel.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/roomSettings/RoomSettingsViewModel.kt index 0e2b276..e95fc52 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/roomSettings/RoomSettingsViewModel.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/roomSettings/RoomSettingsViewModel.kt @@ -1,7 +1,7 @@ package com.github.feelbeatapp.androidclient.ui.roomSettings import androidx.lifecycle.ViewModel -import com.github.feelbeatapp.androidclient.ui.home.Room +import com.github.feelbeatapp.androidclient.ui.state.Room import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/startGame/StartGameScreen.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/startGame/StartGameScreen.kt index 55972db..90430fa 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/startGame/StartGameScreen.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/startGame/StartGameScreen.kt @@ -31,6 +31,7 @@ import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import com.github.feelbeatapp.androidclient.R import com.github.feelbeatapp.androidclient.ui.FeelBeatRoute +import com.github.feelbeatapp.androidclient.ui.state.Player @Composable fun StartGameScreen( diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/startGame/StartGameViewModel.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/startGame/StartGameViewModel.kt index 3b2fcd1..6d1768e 100644 --- a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/startGame/StartGameViewModel.kt +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/startGame/StartGameViewModel.kt @@ -3,13 +3,12 @@ package com.github.feelbeatapp.androidclient.ui.startGame import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.github.feelbeatapp.androidclient.R +import com.github.feelbeatapp.androidclient.ui.state.Player import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch -data class Player(val name: String, val image: Int) - class StartGameViewModel : ViewModel() { private val _players = MutableStateFlow>(emptyList()) val players: StateFlow> = _players.asStateFlow() diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/GameState.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/GameState.kt new file mode 100644 index 0000000..d324a12 --- /dev/null +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/GameState.kt @@ -0,0 +1,10 @@ +package com.github.feelbeatapp.androidclient.ui.state + +data class GameState( + val players: List = emptyList(), + val songs: List = emptyList(), + val selectedRoom: Room? = null, + val playlist: Playlist = Playlist(name = "Playlist #1", songs = emptyList()), + val snippetDuration: Int = 30, + val pointsToWin: Int = 10, +) diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/GuessState.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/GuessState.kt new file mode 100644 index 0000000..c809000 --- /dev/null +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/GuessState.kt @@ -0,0 +1,15 @@ +package com.github.feelbeatapp.androidclient.ui.state + +import androidx.compose.ui.text.input.TextFieldValue + +data class GuessState( + val players: List = emptyList(), + val songs: List = emptyList(), + val filteredSongs: List = emptyList(), + val selectedRoom: Room? = null, + val playlist: Playlist = Playlist(name = "Playlist #1", songs = emptyList()), + val snippetDuration: Int = 30, + val pointsToWin: Int = 10, + val searchQuery: TextFieldValue = TextFieldValue(""), + val currentSong: Song? = null, +) diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/Player.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/Player.kt new file mode 100644 index 0000000..bdadede --- /dev/null +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/Player.kt @@ -0,0 +1,3 @@ +package com.github.feelbeatapp.androidclient.ui.state + +data class Player(val name: String, val image: Int) diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/PlayerWithResult.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/PlayerWithResult.kt new file mode 100644 index 0000000..8ba6b7a --- /dev/null +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/PlayerWithResult.kt @@ -0,0 +1,5 @@ +package com.github.feelbeatapp.androidclient.ui.state + +import com.github.feelbeatapp.androidclient.ui.guessSong.ResultStatus + +data class PlayerWithResult(val player: Player, val resultStatus: ResultStatus, val points: Int) diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/Playlist.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/Playlist.kt new file mode 100644 index 0000000..71d492b --- /dev/null +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/Playlist.kt @@ -0,0 +1,3 @@ +package com.github.feelbeatapp.androidclient.ui.state + +data class Playlist(val name: String, val songs: List) diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/Room.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/Room.kt new file mode 100644 index 0000000..a8ff9f7 --- /dev/null +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/Room.kt @@ -0,0 +1,10 @@ +package com.github.feelbeatapp.androidclient.ui.state + +data class Room( + val id: Int, + val name: String, + val maxPlayers: Int, + val snippetDuration: Int, + val pointsToWin: Int, + val playlistLink: String, +) diff --git a/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/Song.kt b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/Song.kt new file mode 100644 index 0000000..aede2b5 --- /dev/null +++ b/app/src/main/java/com/github/feelbeatapp/androidclient/ui/state/Song.kt @@ -0,0 +1,3 @@ +package com.github.feelbeatapp.androidclient.ui.state + +data class Song(val id: Int, val title: String) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f06b46e..6f4bda9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,7 +29,7 @@ Save Settings Room Settings You guessed song correctly! - Ups, that\'s not correct + Ups, that\'s not correct answer time left Authorization server is unreachable \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 75c874b..3697e18 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] agp = "8.7.3" +fuzzywuzzy = "1.4.0" kotlin = "2.0.21" coreKtx = "1.15.0" junit = "4.13.2" @@ -23,6 +24,7 @@ coil = "3.0.4" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +fuzzywuzzy = { module = "me.xdrop:fuzzywuzzy", version.ref = "fuzzywuzzy" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }