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
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
</activity>

<activity
android:name=".ui.AuthActivity"
android:name=".ui.login.AuthActivity"
android:exported="true"
android:noHistory="true"
android:theme="@style/Theme.FeelBeatAndroidClient">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.github.feelbeatapp.androidclient.network.api

import com.github.feelbeatapp.androidclient.ui.model.RoomSettings
import com.github.feelbeatapp.androidclient.ui.app.model.RoomSettings


interface FeelBeatApi {
suspend fun createRoom(settings: RoomSettings): String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.github.feelbeatapp.androidclient.error.ErrorCode
import com.github.feelbeatapp.androidclient.error.FeelBeatException
import com.github.feelbeatapp.androidclient.network.api.payloads.CreateRoomPayload
import com.github.feelbeatapp.androidclient.network.api.responses.CreateRoomResponse
import com.github.feelbeatapp.androidclient.ui.model.RoomSettings
import com.github.feelbeatapp.androidclient.ui.app.model.RoomSettings
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.headers
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.github.feelbeatapp.androidclient.network.api.payloads

import com.github.feelbeatapp.androidclient.ui.model.RoomSettings
import com.github.feelbeatapp.androidclient.ui.app.model.RoomSettings
import io.ktor.http.Url
import kotlinx.serialization.Serializable

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,27 @@ 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.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.app.navigation.AppGraph
import com.github.feelbeatapp.androidclient.ui.login.LoginScreen
import com.github.feelbeatapp.androidclient.ui.roomsettings.screens.NewRoomSettingsScreen
import com.github.feelbeatapp.androidclient.ui.startgame.StartGameScreen
import com.github.feelbeatapp.androidclient.ui.theme.FeelBeatTheme

@Composable
fun FeelBeatApp(startScreen: FeelBeatRoute, modifier: Modifier = Modifier) {
fun FeelBeatApp(startScreen: RootRoute, modifier: Modifier = Modifier) {
FeelBeatTheme {
val navController = rememberNavController()

Box(modifier = modifier) {
NavHost(navController, startDestination = startScreen.name) {
composable(route = FeelBeatRoute.LOGIN.name) { LoginScreen() }
composable(route = FeelBeatRoute.HOME.name) {
HomeScreen(navController = navController)
}
composable(route = FeelBeatRoute.NEW_ROOM_SETTINGS.name) {
NewRoomSettingsScreen(
onNavigateTo = { navController.navigate(it) },
onNavigateBack = { navController.popBackStack() },
composable(route = RootRoute.APP.name) {
AppGraph(
onLogout = {
navController.navigate(RootRoute.LOGIN.name) {
popUpTo(navController.graph.id)
}
}
)
}
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 = RootRoute.LOGIN.name) { LoginScreen() }
}
}
}
Expand All @@ -53,6 +36,5 @@ fun FeelBeatApp(startScreen: FeelBeatRoute, modifier: Modifier = Modifier) {
@Preview
@Composable
fun AppPreview() {
FeelBeatApp(FeelBeatRoute.LOGIN)

FeelBeatApp(RootRoute.LOGIN)
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
import com.github.feelbeatapp.androidclient.auth.AuthManager
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
Expand All @@ -14,16 +12,12 @@ import javax.inject.Inject
class MainActivity : ComponentActivity() {
@Inject lateinit var authManager: AuthManager

@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

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

enableEdgeToEdge()
setContent {
FeelBeatApp(startRoute)
}
setContent { FeelBeatApp(startRoute) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.github.feelbeatapp.androidclient.ui

enum class RootRoute {
LOGIN,
APP,
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package com.github.feelbeatapp.androidclient.ui.app

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
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 coil3.compose.AsyncImage
import com.github.feelbeatapp.androidclient.R
import com.github.feelbeatapp.androidclient.ui.theme.FeelBeatTheme

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppScreen(
title: String,
backVisible: Boolean,
onLogout: () -> Unit,
onNavigateBack: () -> Unit,
appViewModel: AppViewModel = hiltViewModel(),
bottomBar: @Composable () -> Unit = {},
content: @Composable () -> Unit = {},
) {
val playerIdentity by appViewModel.playerIdentity.collectAsState()

var playerIdentitySheetOpen by remember { mutableStateOf(false) }

if (playerIdentitySheetOpen) {
ModalBottomSheet(onDismissRequest = { playerIdentitySheetOpen = false }) {
UserAccountBottomSheetContent(
playerIdentity = playerIdentity,
onLogoutClick = {
appViewModel.logout()
playerIdentitySheetOpen = false
onLogout()
},
)
}
}

Scaffold(
topBar = {
AppBar(
title = title,
backVisible = backVisible,
imageUrl = playerIdentity?.imageUrl,
onAccountClick = { playerIdentitySheetOpen = true },
onNavigateBack = onNavigateBack,
)
},
bottomBar = { bottomBar() },
modifier = Modifier.fillMaxSize(),
) { padding ->
Box(modifier = Modifier.padding(padding).fillMaxSize()) { content() }
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppBar(
title: String,
backVisible: Boolean,
imageUrl: String?,
onAccountClick: () -> Unit,
onNavigateBack: () -> Unit,
) {
CenterAlignedTopAppBar(
title = { Text(title) },
colors =
TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary,
),
actions = {
IconButton(onClick = onAccountClick) {
AsyncImage(
model = imageUrl,
contentDescription = stringResource(R.string.player_image),
modifier = Modifier.size(80.dp).clip(MaterialTheme.shapes.large),
contentScale = ContentScale.Crop,
placeholder = painterResource(R.drawable.account),
error = painterResource(R.drawable.account),
)
}
},
navigationIcon = {
if (backVisible) {
IconButton(onClick = onNavigateBack) {
Icon(
Icons.AutoMirrored.Filled.KeyboardArrowLeft,
contentDescription = stringResource(R.string.back),
)
}
}
},
)
}

@Composable
fun UserAccountBottomSheetContent(playerIdentity: PlayerIdentity?, onLogoutClick: () -> Unit) {
Column(
modifier = Modifier.fillMaxWidth().padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
Box(
modifier =
Modifier.size(80.dp).background(Color.Gray, shape = MaterialTheme.shapes.large),
contentAlignment = Alignment.Center,
) {
AsyncImage(
model = playerIdentity?.imageUrl,
contentDescription = "Player Image",
modifier = Modifier.size(80.dp).clip(MaterialTheme.shapes.large),
contentScale = ContentScale.Crop,
placeholder = painterResource(R.drawable.account),
error = painterResource(R.drawable.account),
)
}
Text(text = playerIdentity?.name ?: "...", style = MaterialTheme.typography.titleMedium)
Box(
modifier =
Modifier.fillMaxWidth()
.background(MaterialTheme.colorScheme.primary, MaterialTheme.shapes.medium)
.padding(8.dp),
contentAlignment = Alignment.Center,
) {
Text(
text = "log out",
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onPrimary,
modifier = Modifier.padding(vertical = 4.dp).clickable { onLogoutClick() },
)
}
}
}

@Composable
@Preview
fun AppScreenPreview() {
FeelBeatTheme { AppScreen(title = "Title", backVisible = true, {}, {}) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.github.feelbeatapp.androidclient.ui.app

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.feelbeatapp.androidclient.auth.AuthManager
import com.github.feelbeatapp.androidclient.network.spotify.SpotifyAPI
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

data class PlayerIdentity(val name: String, val imageUrl: String)

@HiltViewModel
class AppViewModel
@Inject
constructor(private val authManager: AuthManager, private val spotifyAPI: SpotifyAPI) :
ViewModel() {
private val _playerIdentity = MutableStateFlow<PlayerIdentity?>(null)
val playerIdentity = _playerIdentity.asStateFlow()

init {
loadPlayerIdentity()
}

private fun loadPlayerIdentity() {
viewModelScope.launch(Dispatchers.IO) {
val profile = spotifyAPI.getProfile()
_playerIdentity.value =
PlayerIdentity(name = profile.displayName, imageUrl = profile.images.first().url)
}
}

fun logout() {
authManager.logout()
}
}
Loading
Loading