Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ interface AuthManager {

suspend fun fetchAccessToken(code: String)

fun cancelLoginFlow()

suspend fun getAccessToken(): String
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ constructor(
onAuthenticated()
}

override fun cancelLoginFlow() {
state = AuthState.NOT_AUTHENTICATED
}

override suspend fun getAccessToken(): String {
var auth = checkNotNull(authData) { "Not authenticated" }
if (hasExpired(auth.expires)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package com.github.feelbeatapp.androidclient.auth.storage

import android.content.Context
import android.content.Intent
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import com.github.feelbeatapp.androidclient.auth.AuthData
import dagger.hilt.android.qualifiers.ApplicationContext
import java.time.Instant
import java.time.LocalDate
import javax.inject.Inject

class PreferencesAuthStorage @Inject constructor(@ApplicationContext ctx: Context) : AuthStorage {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class AuthActivity : ComponentActivity() {
val code = intentUri.getQueryParameter("code")

if (error != null || code == null) {
authManager.cancelLoginFlow()
startActivity(Intent(this, MainActivity::class.java))
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,56 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.github.feelbeatapp.androidclient.ui.game.GameScreen
import com.github.feelbeatapp.androidclient.ui.acceptGame.AcceptGameScreen
import com.github.feelbeatapp.androidclient.ui.gameResult.GameResultScreen
import com.github.feelbeatapp.androidclient.ui.guessSong.GuessResultScreen
import com.github.feelbeatapp.androidclient.ui.guessSong.GuessSongScreen
import com.github.feelbeatapp.androidclient.ui.home.HomeScreen
import com.github.feelbeatapp.androidclient.ui.login.LoginScreen
import com.github.feelbeatapp.androidclient.ui.newRoomSettings.NewRoomSettingsScreen
import com.github.feelbeatapp.androidclient.ui.roomSettings.RoomSettingsScreen
import com.github.feelbeatapp.androidclient.ui.startGame.StartGameScreen
import com.github.feelbeatapp.androidclient.ui.theme.FeelBeatTheme

@Composable
fun FeelBeatApp(
@Suppress("UnusedParameter") widthSizeClass: WindowWidthSizeClass,
startRoute: FeelBeatRoute,
startScreen: FeelBeatRoute,
modifier: Modifier = Modifier,
) {
FeelBeatTheme {
val navController = rememberNavController()

Box(modifier = modifier) {
NavHost(navController, startDestination = startRoute.name) {
NavHost(navController, startDestination = startScreen.name) {
composable(route = FeelBeatRoute.LOGIN.name) { LoginScreen() }

composable(route = FeelBeatRoute.HOME.name) {
HomeScreen(parentNavController = navController)
HomeScreen(navController = navController)
}
composable(route = FeelBeatRoute.NEW_ROOM_SETTINGS.name) {
NewRoomSettingsScreen(navController = navController)
}
composable(route = FeelBeatRoute.ROOM_SETTINGS.name) {
RoomSettingsScreen(navController = navController)
}
composable(route = FeelBeatRoute.ACCEPT_GAME.name) {
AcceptGameScreen(navController = navController)
}
composable(route = FeelBeatRoute.ACCOUNT_SETTINGS.name) {
// AccountSettingsScreen(parentNavController = navController)
}
composable(route = FeelBeatRoute.GAME_RESULT.name) {
GameResultScreen(navController = navController)
}
composable(route = FeelBeatRoute.GUESS_SONG.name) {
GuessSongScreen(navController = navController)
}
composable(route = FeelBeatRoute.GUESS_RESULT.name) {
GuessResultScreen(navController = navController)
}
composable(route = FeelBeatRoute.START_GAME.name) {
StartGameScreen(navController = navController)
}

composable(route = FeelBeatRoute.GAME.name) { GameScreen() }
}
}
}
Expand All @@ -39,5 +66,5 @@ fun FeelBeatApp(
@Preview
@Composable
fun AppPreview() {
FeelBeatApp(WindowWidthSizeClass.Compact, FeelBeatRoute.HOME)
FeelBeatApp(WindowWidthSizeClass.Compact, FeelBeatRoute.LOGIN)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ package com.github.feelbeatapp.androidclient.ui
enum class FeelBeatRoute {
LOGIN,
HOME,
LOBBY,
GAME,
NEW_ROOM_SETTINGS,
ROOM_SETTINGS,
ACCEPT_GAME,
ACCOUNT_SETTINGS,
GAME_RESULT,
GUESS_SONG,
GUESS_RESULT,
START_GAME,
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,21 @@ import androidx.activity.enableEdgeToEdge
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
import com.github.feelbeatapp.androidclient.auth.AuthManager
import com.github.feelbeatapp.androidclient.auth.AuthState
import com.github.feelbeatapp.androidclient.network.fullduplex.NetworkAgent
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject lateinit var socket: NetworkAgent
@Inject lateinit var authManager: AuthManager

@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
// socket.connect("/ws")

val startRoute =
if (authManager.isAuthenticated()) FeelBeatRoute.HOME
else FeelBeatRoute.LOGIN
if (authManager.isAuthenticated()) FeelBeatRoute.HOME else FeelBeatRoute.LOGIN

enableEdgeToEdge()
setContent {
val widthSizeClass = calculateWindowSizeClass(this).widthSizeClass
FeelBeatApp(widthSizeClass, startRoute)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package com.github.feelbeatapp.androidclient.ui.acceptGame

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
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.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.startGame.PlayerCard

@Composable
fun AcceptGameScreen(
viewModel: AcceptGameViewModel = AcceptGameViewModel(),
navController: NavController,
isRoomCreator: Boolean = true,
) {
val gameState = viewModel.gameState.collectAsState().value

Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) {
Column(
modifier =
Modifier.fillMaxSize()
.padding(bottom = 56.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
) {
IconButton(onClick = { navController.navigate(FeelBeatRoute.ACCEPT_GAME.name) }) {
Icon(
Icons.AutoMirrored.Filled.KeyboardArrowLeft,
contentDescription = stringResource(R.string.back),
)
}

Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.fillMaxWidth().padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
gameState.players.forEach { player -> PlayerCard(player = player) }
}

Spacer(modifier = Modifier.height(32.dp))

Column(
horizontalAlignment = Alignment.Start,
modifier = Modifier.padding(horizontal = 16.dp),
) {
Text(
text =
stringResource(
id = R.string.snippet_duration_val,
gameState.snippetDuration,
),
style = MaterialTheme.typography.bodyMedium,
)
Text(
text = stringResource(id = R.string.points_to_win_val, gameState.pointsToWin),
style = MaterialTheme.typography.bodyMedium,
)
}

Spacer(modifier = Modifier.height(32.dp))

Text(
text = stringResource(id = R.string.playlist_name, gameState.playlist.name),
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(horizontal = 16.dp),
)

gameState.playlist.songs.forEach { song -> SongItem(song = song) }

Spacer(modifier = Modifier.height(32.dp))

Button(
onClick = { navController.navigate(FeelBeatRoute.START_GAME.name) },
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
) {
Text(stringResource(R.string.play), style = MaterialTheme.typography.headlineMedium)
}
}

if (isRoomCreator) {
BottomNavigationBar(
navController = navController,
modifier = Modifier.align(Alignment.BottomCenter),
)
}
}
}

@Composable
fun BottomNavigationBar(navController: NavController, modifier: Modifier = Modifier) {
NavigationBar(
modifier = modifier,
containerColor = MaterialTheme.colorScheme.surface,
contentColor = MaterialTheme.colorScheme.primary,
) {
NavigationBarItem(
icon = {
Icon(Icons.Filled.Home, contentDescription = stringResource(R.string.selected_room))
},
label = { Text(stringResource(R.string.selected_room)) },
selected = false,
onClick = { navController.navigate(FeelBeatRoute.ACCEPT_GAME.name) },
)
NavigationBarItem(
icon = {
Icon(Icons.Filled.Settings, contentDescription = stringResource(R.string.settings))
},
label = { Text(stringResource(R.string.settings)) },
selected = false,
onClick = { navController.navigate(FeelBeatRoute.ROOM_SETTINGS.name) },
)
}
}

@Composable
fun SongItem(song: Song) {
Row(
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp, horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = song.title,
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.weight(1f),
)
}
}

@Preview
@Composable
fun PreviewAcceptScreen() {
val navController = rememberNavController()
AcceptGameScreen(navController = navController)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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 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<Song>)

data class GameState(
val players: List<Player> = emptyList(),
val songs: List<Song> = 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> = _gameState.asStateFlow()

init {
loadPlayers()
loadSongs()
}

private fun loadPlayers() {
viewModelScope.launch {
val examplePlayers =
listOf(
Player("User123", R.drawable.userimage),
Player("User456", R.drawable.userimage),
Player("User789", R.drawable.userimage),
)

_gameState.update { it.copy(players = examplePlayers) }
}
}

@SuppressWarnings("MagicNumber")
private fun loadSongs() {
viewModelScope.launch {
val exampleSongs =
listOf(
Song(1, "Song 1"),
Song(2, "Song 2"),
Song(3, "Song 3"),
Song(4, "Song 4"),
Song(5, "Song 5"),
)
_gameState.update { it.copy(songs = exampleSongs) }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
Expand Down
Loading
Loading