Simplify database handling.
Some checks are pending
continuous-integration/drone/push Build is running

This commit is contained in:
2023-01-22 21:40:47 +01:00
parent f40b66077b
commit 8d24e46687
23 changed files with 265 additions and 159 deletions

View File

@@ -3,6 +3,8 @@ package me.zobrist.tichucounter.data
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import me.zobrist.tichucounter.data.entity.Game
import me.zobrist.tichucounter.data.entity.Round
@Database(entities = [Round::class, Game::class], version = 1)
@TypeConverters(DateConverter::class)

View File

@@ -1,15 +0,0 @@
package me.zobrist.tichucounter.data
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.util.*
@Entity
data class Game(
override var active: Boolean,
override var nameA: String,
override var nameB: String,
override val created: Date,
override var modified: Date,
@PrimaryKey(autoGenerate = true) override val uid: Long? = null
) : IGame, IEntity

View File

@@ -1,16 +0,0 @@
package me.zobrist.tichucounter.data
import androidx.room.Entity
import java.util.*
@Entity
data class GameAndScore(
override var active: Boolean,
override var nameA: String,
override var nameB: String,
override val created: Date,
override var modified: Date,
override var gameId: Long,
override var scoreA: Int,
override var scoreB: Int,
) : IGame, IRound

View File

@@ -2,6 +2,7 @@ package me.zobrist.tichucounter.data
import androidx.room.*
import kotlinx.coroutines.flow.Flow
import me.zobrist.tichucounter.data.entity.Game
@Dao
@@ -10,19 +11,13 @@ interface GameDao : DaoBase<Game> {
@Query("SELECT * FROM game")
fun getAll(): Flow<List<Game>>
@Query(
"SELECT active, " +
"nameA, " +
"nameB, " +
"created, " +
"modified, " +
"game.uid as gameId, " +
"COALESCE(SUM(round.scoreA), 0) as scoreA, " +
"COALESCE(SUM(round.scoreB), 0) as scoreB " +
"FROM game " +
"LEFT JOIN round ON round.gameId = game.uid GROUP BY game.uid ORDER BY modified DESC"
)
fun getAllWithPoints(): Flow<List<GameAndScore>>
@Transaction
@Query("SELECT * FROM game where uid ")
fun getGamesWithRounds(): Flow<List<GameWithScores>>
@Transaction
@Query("SELECT * FROM game WHERE active is 1")
fun getActiveWithRounds(): Flow<GameWithScores?>
@Query("SELECT * FROM game WHERE uid is :gameId")
fun getGameById(gameId: Long): Flow<Game>
@@ -30,6 +25,7 @@ interface GameDao : DaoBase<Game> {
@Query("SELECT * FROM game WHERE active is 1")
fun getActive(): Flow<Game?>
@Query("UPDATE game SET active = 1 WHERE uid is :gameId;")
fun setActive(gameId: Long)

View File

@@ -0,0 +1,17 @@
package me.zobrist.tichucounter.data
import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.Relation
import me.zobrist.tichucounter.data.entity.Game
import me.zobrist.tichucounter.data.entity.Round
@Entity
data class GameWithScores(
@Embedded val game: Game,
@Relation(
parentColumn = "uid",
entityColumn = "gameId"
)
val rounds: List<Round>
)

View File

@@ -1,5 +0,0 @@
package me.zobrist.tichucounter.data
interface IEntity {
val uid: Long?
}

View File

@@ -1,11 +0,0 @@
package me.zobrist.tichucounter.data
import java.util.*
interface IGame {
var active: Boolean
var nameA: String
var nameB: String
val created: Date
var modified: Date
}

View File

@@ -1,7 +0,0 @@
package me.zobrist.tichucounter.data
interface IRound {
var gameId: Long
var scoreA: Int
var scoreB: Int
}

View File

@@ -1,12 +0,0 @@
package me.zobrist.tichucounter.data
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class Round(
override var gameId: Long,
override var scoreA: Int,
override var scoreB: Int,
@PrimaryKey(autoGenerate = true) override val uid: Long? = null
) : IRound, IEntity

View File

@@ -1,7 +1,7 @@
package me.zobrist.tichucounter.data
import androidx.room.*
import kotlinx.coroutines.flow.Flow
import me.zobrist.tichucounter.data.entity.Round
@Dao
interface RoundDao : DaoBase<Round> {
@@ -12,20 +12,4 @@ interface RoundDao : DaoBase<Round> {
@Query("SELECT * FROM round WHERE gameId is :gameId")
fun getAllForGame(gameId: Long?): List<Round>
@Query(
"SELECT gameId, SUM(scoreA) as scoreA, SUM(scoreB) as scoreB " +
"FROM round " +
"LEFT JOIN game ON game.uid = round.gameId " +
"WHERE game.active == 1"
)
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
fun getRoundSumForActiveGame(): Flow<Round>
@Query(
"SELECT gameId, scoreA, scoreB, round.uid " +
"FROM round " +
"LEFT JOIN game ON game.uid = round.gameId " +
"WHERE game.active == 1"
)
fun getForActiveGame(): Flow<List<Round>>
}

View File

@@ -0,0 +1,16 @@
package me.zobrist.tichucounter.data.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
import me.zobrist.tichucounter.data.entity.IEntity
import java.util.*
@Entity
data class Game(
var active: Boolean,
var nameA: String,
var nameB: String,
val created: Date,
var modified: Date,
@PrimaryKey(autoGenerate = true) override val uid: Long = 0
) : IEntity

View File

@@ -0,0 +1,5 @@
package me.zobrist.tichucounter.data.entity
interface IEntity {
val uid: Long
}

View File

@@ -0,0 +1,13 @@
package me.zobrist.tichucounter.data.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
import me.zobrist.tichucounter.data.entity.IEntity
@Entity
data class Round(
var gameId: Long,
var scoreA: Int,
var scoreB: Int,
@PrimaryKey(autoGenerate = true) override val uid: Long = 0
) : IEntity

View File

@@ -0,0 +1,17 @@
package me.zobrist.tichucounter.domain
import me.zobrist.tichucounter.data.GameWithScores
class GameWithScoresExtension {
}
fun GameWithScores.getTotalPoints(): Pair<Int, Int> {
var scoreA = 0
var scoreB = 0
this.rounds.forEach {
scoreA += it.scoreA
scoreB += it.scoreB
}
return Pair(scoreA, scoreB)
}

View File

@@ -2,13 +2,13 @@ package me.zobrist.tichucounter.repository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.zobrist.tichucounter.data.Game
import me.zobrist.tichucounter.data.GameDao
import me.zobrist.tichucounter.data.Round
import me.zobrist.tichucounter.data.RoundDao
import me.zobrist.tichucounter.data.*
import me.zobrist.tichucounter.data.entity.Game
import me.zobrist.tichucounter.data.entity.Round
import java.util.*
import javax.inject.Inject
@@ -116,4 +116,12 @@ class GameRepository @Inject constructor(
}
}
}
fun getActiveGameFlow(): Flow<GameWithScores?> {
return gameDao.getActiveWithRounds()
}
fun getAllWithRoundFlow(): Flow<List<GameWithScores>> {
return gameDao.getGamesWithRounds()
}
}

View File

@@ -10,8 +10,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import me.zobrist.tichucounter.data.Round
import me.zobrist.tichucounter.data.RoundDao
import me.zobrist.tichucounter.data.entity.Round
import me.zobrist.tichucounter.domain.NavigationAction
import me.zobrist.tichucounter.domain.TopBarAction
import me.zobrist.tichucounter.repository.GameRepository
@@ -19,8 +18,7 @@ import javax.inject.Inject
@HiltViewModel
class MainViewModel @Inject constructor(
private val gameRepository: GameRepository,
roundDao: RoundDao
private val gameRepository: GameRepository
) : ViewModel() {
private var redoRounds = mutableStateListOf<Round>()
@@ -37,14 +35,18 @@ class MainViewModel @Inject constructor(
init {
viewModelScope.launch {
roundDao.getForActiveGame().collect {
isUndoActionActive = it.isNotEmpty()
if (expectedRoundCount != it.count()) {
redoRounds.clear()
gameRepository.getActiveGameFlow().collect {
if (it != null) {
isUndoActionActive = it.rounds.isNotEmpty()
if (expectedRoundCount != it.rounds.count()) {
redoRounds.clear()
}
expectedRoundCount = it.rounds.count()
}
expectedRoundCount = it.count()
}
}
}

View File

@@ -9,7 +9,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.tooling.preview.Preview
import me.zobrist.tichucounter.data.Round
import me.zobrist.tichucounter.data.entity.Round
import me.zobrist.tichucounter.ui.AppTheme

View File

@@ -8,10 +8,9 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import me.zobrist.tichucounter.data.GameDao
import me.zobrist.tichucounter.data.Round
import me.zobrist.tichucounter.data.RoundDao
import me.zobrist.tichucounter.data.entity.Round
import me.zobrist.tichucounter.domain.Tichu
import me.zobrist.tichucounter.domain.getTotalPoints
import me.zobrist.tichucounter.repository.GameRepository
import javax.inject.Inject
@@ -48,9 +47,7 @@ interface ICounterViewModel {
@HiltViewModel
class CounterViewModel @Inject constructor(
private val gameRepository: GameRepository,
private val roundDao: RoundDao,
private val gameDao: GameDao
private val gameRepository: GameRepository
) :
ViewModel(), ICounterViewModel {
@@ -112,24 +109,18 @@ class CounterViewModel @Inject constructor(
init {
viewModelScope.launch {
roundDao.getForActiveGame().collect {
roundScoreList = it
}
}
viewModelScope.launch {
gameDao.getActive().collect {
gameRepository.getActiveGameFlow().collect {
if (it != null) {
teamNameA = it.nameA
teamNameB = it.nameB
}
}
}
viewModelScope.launch {
roundDao.getRoundSumForActiveGame().collect { score ->
totalScoreA = score.scoreA
totalScoreB = score.scoreB
val score = it.getTotalPoints()
roundScoreList = it.rounds
totalScoreA = score.first
totalScoreB = score.second
teamNameA = it.game.nameA
teamNameB = it.game.nameB
}
}
}
}

View File

@@ -17,7 +17,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import me.zobrist.tichucounter.data.Round
import me.zobrist.tichucounter.data.entity.Round
import me.zobrist.tichucounter.ui.AppTheme
@Composable

View File

@@ -15,7 +15,10 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import me.zobrist.tichucounter.R
import me.zobrist.tichucounter.data.GameAndScore
import me.zobrist.tichucounter.data.entity.Game
import me.zobrist.tichucounter.data.GameWithScores
import me.zobrist.tichucounter.data.entity.Round
import me.zobrist.tichucounter.domain.getTotalPoints
import java.text.DateFormat
import java.util.*
@@ -65,7 +68,7 @@ fun DeleteConfirmDialog(show: Boolean = true, onExecuted: (Boolean) -> Unit = {}
@Composable
fun HistoryList(
games: List<GameAndScore>,
games: List<GameWithScores>,
onOpenClicked: (GameId: Long) -> Unit,
onDeleteClicked: (GameId: Long) -> Unit
) {
@@ -80,7 +83,7 @@ fun HistoryList(
@Composable
fun HistoryListItem(
game: GameAndScore,
game: GameWithScores,
onOpenClicked: (GameId: Long) -> Unit,
onDeleteClicked: (GameId: Long) -> Unit
) {
@@ -88,13 +91,15 @@ fun HistoryListItem(
DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault())
val cardColor = if (game.active) {
val cardColor = if (game.game.active) {
CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.secondaryContainer)
} else {
CardDefaults.cardColors()
}
val totalScores = game.getTotalPoints()
Card(
modifier = Modifier
.fillMaxWidth()
@@ -107,18 +112,18 @@ fun HistoryListItem(
) {
Column(Modifier.weight(4f)) {
Text(
text = game.nameA + " vs " + game.nameB,
text = game.game.nameA + " vs " + game.game.nameB,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.headlineSmall
)
Text(
text = game.scoreA.toString() + " : " + game.scoreB.toString(),
text = totalScores.first.toString() + " : " + totalScores.second.toString(),
style = MaterialTheme.typography.bodyLarge
)
Spacer(modifier = Modifier.padding(5.dp))
Text(
text = format.format(game.modified),
text = format.format(game.game.modified),
style = MaterialTheme.typography.labelSmall
)
}
@@ -128,11 +133,11 @@ fun HistoryListItem(
.width(70.dp)
) {
ElevatedButton(onClick = { onOpenClicked(game.gameId) }, enabled = true) {
ElevatedButton(onClick = { onOpenClicked(game.game.uid) }, enabled = true) {
Icon(Icons.Outlined.OpenInFull, null)
}
ElevatedButton(
onClick = { onDeleteClicked(game.gameId) }, enabled = !game.active
onClick = { onDeleteClicked(game.game.uid) }, enabled = !game.game.active
) {
Icon(Icons.Outlined.Delete, null)
}
@@ -145,11 +150,26 @@ fun HistoryListItem(
@Composable
private fun HistoryListPreview() {
val tempData = listOf(
GameAndScore(true, "abc", "def", Date(), Date(), 1, 10, 50),
GameAndScore(false, "ADTH", "dogfg", Date(), Date(), 2, 20, 60),
GameAndScore(false, "TeamA3 langer Name", "TeamB3", Date(), Date(), 3, 30, 70),
GameAndScore(false, "TeamA4", "TeamB4", Date(), Date(), 4, 40, 80),
GameAndScore(false, "TeamA5", "TeamB5", Date(), Date(), 5, 50, 90)
GameWithScores(
Game(true, "abc", "def", Date(), Date()),
listOf(Round(1, 550, 500))
),
GameWithScores(
Game(false, "ADTH", "dogfg", Date(), Date()),
listOf(Round(2, 20, 60))
),
GameWithScores(
Game(false, "TeamA3 langer Name", "TeamB3", Date(), Date()),
listOf(Round(3, 30, 70))
),
GameWithScores(
Game(false, "TeamA4", "TeamB4", Date(), Date()),
listOf(Round(4, 40, 80))
),
GameWithScores(
Game(false, "TeamA5", "TeamB5", Date(), Date()),
listOf(Round(5, 50, 90))
)
)
HistoryList(tempData, {}) {}
}

View File

@@ -7,24 +7,23 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import me.zobrist.tichucounter.data.GameAndScore
import me.zobrist.tichucounter.data.GameDao
import me.zobrist.tichucounter.data.GameWithScores
import me.zobrist.tichucounter.repository.GameRepository
import javax.inject.Inject
@HiltViewModel
class HistoryViewModel @Inject constructor(
private val gameDao: GameDao,
private val gameRepository: GameRepository
) : ViewModel() {
var gameAndHistory by mutableStateOf(emptyList<GameAndScore>())
var gameAndHistory by mutableStateOf(emptyList<GameWithScores>())
private set
init {
viewModelScope.launch {
gameDao.getAllWithPoints().collect { games ->
gameRepository.getAllWithRoundFlow().collect() { games ->
gameAndHistory = games
}
}