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
@@ -1,14 +1,16 @@
package com.github.feelbeatapp.androidclient.error

import android.util.Log
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import javax.inject.Inject

class ErrorHandler @Inject constructor() : ErrorEmitter, ErrorReceiver {
private val _errors = MutableSharedFlow<FeelBeatException>()
override val errors = _errors.asSharedFlow()

override suspend fun submitError(exception: FeelBeatException) {
Log.e("App error", exception.toString())
_errors.emit(exception)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.github.feelbeatapp.androidclient.model

import kotlinx.serialization.Serializable

@Serializable
data class CreateRoomPayload(
val maxPlayers: Int,
val turnCount: Int,
val timePenaltyPerSecond: Int,
val basePoints: Int,
val incorrectGuessPenalty: Int,
val playListId: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.github.feelbeatapp.androidclient.model

import kotlinx.serialization.Serializable

@Serializable
data class RoomListView(
val id: String,
val name: String,
val players: Int,
val maxPlayers: Int,
val imageUrl: String,
)
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.github.feelbeatapp.androidclient.network.api

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

import com.github.feelbeatapp.androidclient.model.CreateRoomPayload
import com.github.feelbeatapp.androidclient.model.RoomListView

interface FeelBeatApi {
suspend fun createRoom(settings: RoomSettings): String
suspend fun createRoom(payload: CreateRoomPayload): String

suspend fun fetchRooms(): List<RoomListView>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import com.github.feelbeatapp.androidclient.auth.AuthManager
import com.github.feelbeatapp.androidclient.error.ErrorCode
import com.github.feelbeatapp.androidclient.error.FeelBeatException
import com.github.feelbeatapp.androidclient.error.FeelBeatServerException
import com.github.feelbeatapp.androidclient.network.api.payloads.CreateRoomPayload
import com.github.feelbeatapp.androidclient.model.CreateRoomPayload
import com.github.feelbeatapp.androidclient.model.RoomListView
import com.github.feelbeatapp.androidclient.network.api.responses.CreateRoomResponse
import com.github.feelbeatapp.androidclient.ui.app.model.RoomSettings
import com.github.feelbeatapp.androidclient.network.api.responses.FetchRoomsResponse
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.request.headers
import io.ktor.client.request.post
import io.ktor.client.request.setBody
Expand All @@ -28,8 +30,7 @@ constructor(
private val httpClient: HttpClient,
private val authManager: AuthManager,
) : FeelBeatApi {
override suspend fun createRoom(settings: RoomSettings): String {
val payload = CreateRoomPayload.fromRoomSettings(settings)
override suspend fun createRoom(payload: CreateRoomPayload): String {
val token = authManager.getAccessToken()

val res =
Expand All @@ -49,7 +50,7 @@ constructor(
}

if (res.status != HttpStatusCode.OK) {
throw FeelBeatServerException(res.bodyAsText())
throw FeelBeatServerException(res.bodyAsText().trim())
}

val (roomId) =
Expand All @@ -65,4 +66,39 @@ constructor(

return roomId
}

override suspend fun fetchRooms(): List<RoomListView> {
val token = authManager.getAccessToken()

val res =
try {
httpClient.get("$baseUrl/rooms") {
headers { set("Authorization", "Bearer $token") }
}
} catch (e: Exception) {
when (e) {
is IOException,
is UnresolvedAddressException ->
throw FeelBeatException(ErrorCode.FEELBEAT_SERVER_UNREACHABLE, e)
else -> throw e
}
}

if (res.status != HttpStatusCode.OK) {
throw FeelBeatServerException(res.bodyAsText().trim())
}

val (rooms) =
try {
res.body<FetchRoomsResponse>()
} catch (e: UnsupportedOperationException) {
throw FeelBeatException(
ErrorCode.FEELBEAT_SERVER_INCORRECT_RESPONSE_FORMAT,
"Failed to parse server response",
e,
)
}

return rooms
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.github.feelbeatapp.androidclient.network.api.responses

import com.github.feelbeatapp.androidclient.model.RoomListView
import kotlinx.serialization.Serializable

@Serializable data class FetchRoomsResponse(val rooms: List<RoomListView>)
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.github.feelbeatapp.androidclient.R
import com.github.feelbeatapp.androidclient.ui.app.model.PlayerWithResult
import com.github.feelbeatapp.androidclient.ui.app.uimodel.PlayerWithResult
import com.github.feelbeatapp.androidclient.ui.app.navigation.AppRoute

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.feelbeatapp.androidclient.R
import com.github.feelbeatapp.androidclient.ui.app.game.guesssong.ResultStatus
import com.github.feelbeatapp.androidclient.ui.app.model.Player
import com.github.feelbeatapp.androidclient.ui.app.model.PlayerWithResult
import com.github.feelbeatapp.androidclient.ui.app.uimodel.Player
import com.github.feelbeatapp.androidclient.ui.app.uimodel.PlayerWithResult
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.github.feelbeatapp.androidclient.R
import com.github.feelbeatapp.androidclient.ui.app.model.PlayerWithResult
import com.github.feelbeatapp.androidclient.ui.app.model.Song
import com.github.feelbeatapp.androidclient.ui.app.uimodel.PlayerWithResult
import com.github.feelbeatapp.androidclient.ui.app.uimodel.Song
import com.github.feelbeatapp.androidclient.ui.app.navigation.AppRoute

@OptIn(ExperimentalMaterial3Api::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ 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.app.model.Player
import com.github.feelbeatapp.androidclient.ui.app.model.PlayerWithResult
import com.github.feelbeatapp.androidclient.ui.app.model.Song
import com.github.feelbeatapp.androidclient.ui.app.uimodel.Player
import com.github.feelbeatapp.androidclient.ui.app.uimodel.PlayerWithResult
import com.github.feelbeatapp.androidclient.ui.app.uimodel.Song
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.github.feelbeatapp.androidclient.ui.app.game.guesssong

import androidx.compose.ui.text.input.TextFieldValue
import com.github.feelbeatapp.androidclient.ui.app.model.PlayerWithResult
import com.github.feelbeatapp.androidclient.ui.app.model.Playlist
import com.github.feelbeatapp.androidclient.ui.app.model.Room
import com.github.feelbeatapp.androidclient.ui.app.model.Song
import com.github.feelbeatapp.androidclient.ui.app.uimodel.PlayerWithResult
import com.github.feelbeatapp.androidclient.ui.app.uimodel.Playlist
import com.github.feelbeatapp.androidclient.ui.app.uimodel.Room
import com.github.feelbeatapp.androidclient.ui.app.uimodel.Song

data class GuessState(
val players: List<PlayerWithResult> = emptyList(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.github.feelbeatapp.androidclient.R
import com.github.feelbeatapp.androidclient.ui.app.model.Player
import com.github.feelbeatapp.androidclient.ui.app.uimodel.Player
import com.github.feelbeatapp.androidclient.ui.app.navigation.AppRoute

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.github.feelbeatapp.androidclient.ui.app.game.startgame
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.feelbeatapp.androidclient.R
import com.github.feelbeatapp.androidclient.ui.app.model.Player
import com.github.feelbeatapp.androidclient.ui.app.uimodel.Player
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,75 @@ package com.github.feelbeatapp.androidclient.ui.app.home

import androidx.compose.foundation.background
import androidx.compose.foundation.border
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
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.runtime.setValue
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.hilt.navigation.compose.hiltViewModel
import com.github.feelbeatapp.androidclient.R
import com.github.feelbeatapp.androidclient.ui.app.model.Room
import com.github.feelbeatapp.androidclient.model.RoomListView

@Composable
fun HomeScreen(
onRoomSelect: (Room) -> Unit,
onRoomSelect: (String) -> Unit,
onNewRoom: () -> Unit,
homeViewModel: HomeViewModel = hiltViewModel(),
) {
val rooms by homeViewModel.rooms.collectAsState()
val loading by homeViewModel.loading.collectAsState()

LaunchedEffect(null) { homeViewModel.loadRooms() }

Column(modifier = Modifier.fillMaxSize()) {
Text(
text = stringResource(R.string.current_games),
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(16.dp),
)
LazyColumn(
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp).weight(1f),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
items(rooms) { room -> RoomItem(room = room, onClick = { onRoomSelect(room) }) }

if (loading) {
CircularProgressIndicator(
color = MaterialTheme.colorScheme.secondary,
trackColor = MaterialTheme.colorScheme.surfaceVariant,
strokeWidth = 4.dp,
modifier = Modifier.width(50.dp).height(50.dp),
)
} else {
RoomList(
items = rooms,
isRefreshing = loading,
onRefresh = { homeViewModel.loadRooms() },
onRoomSelect = onRoomSelect,
)
}

Box(
Expand Down Expand Up @@ -78,8 +98,24 @@ fun HomeScreen(
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RoomList(
items: List<RoomListView>,
isRefreshing: Boolean,
onRefresh: () -> Unit,
onRoomSelect: (String) -> Unit,
modifier: Modifier = Modifier,
) {
PullToRefreshBox(isRefreshing = isRefreshing, onRefresh = onRefresh, modifier = modifier) {
LazyColumn(Modifier.fillMaxSize()) {
items(items) { ListItem({ RoomItem(room = it, onClick = { onRoomSelect(it.id) }) }) }
}
}
}

@Composable
fun RoomItem(room: Room, onClick: () -> Unit) {
fun RoomItem(room: RoomListView, onClick: () -> Unit) {
Card(
onClick = onClick,
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
Expand Down
Loading
Loading