1 Commits

Author SHA1 Message Date
30723f7862 Merge pull request 'release/2.0' (#21) from release/2.0 into master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: fabian/TichuCounter#21
2023-01-28 23:29:25 +01:00
17 changed files with 113 additions and 460 deletions

View File

@@ -39,10 +39,9 @@ steps:
from_secret: SeafileApiKey from_secret: SeafileApiKey
APK_FILE: app/build/outputs/apk/release/app-release.apk APK_FILE: app/build/outputs/apk/release/app-release.apk
BUNDLE_FILE: app/build/outputs/bundle/release/app-release.aab BUNDLE_FILE: app/build/outputs/bundle/release/app-release.aab
SEAFILE_REPO: 6debeef9-121e-46ba-acc7-81e109fdcbdd SEAFILE_REPO: daffda8b-5840-4a65-b6d0-73b991facfb6
commands: commands:
- 'UPLOAD_URL=$(curl -H "Authorization: Token $SEAFILE_API_KEY" https://seafile.zobrist.me/api2/repos/$SEAFILE_REPO/upload-link/ | tr -d "\"")' - 'UPLOAD_URL=$(curl -H "Authorization: Token $SEAFILE_API_KEY" https://seafile.zobrist.me/api2/repos/$SEAFILE_REPO/upload-link/ | tr -d "\"")'
- echo $UPLOAD_URL
- 'curl -H "Authorization: Token $SEAFILE_API_KEY" -F file=@$APK_FILE -F parent_dir=/ -F relative_path=latest/ -F replace=1 "$UPLOAD_URL"' - 'curl -H "Authorization: Token $SEAFILE_API_KEY" -F file=@$APK_FILE -F parent_dir=/ -F relative_path=latest/ -F replace=1 "$UPLOAD_URL"'
- 'curl -H "Authorization: Token $SEAFILE_API_KEY" -F file=@$BUNDLE_FILE -F parent_dir=/ -F relative_path=latest/ -F replace=1 "$UPLOAD_URL"' - 'curl -H "Authorization: Token $SEAFILE_API_KEY" -F file=@$BUNDLE_FILE -F parent_dir=/ -F relative_path=latest/ -F replace=1 "$UPLOAD_URL"'
@@ -53,7 +52,7 @@ steps:
from_secret: SeafileApiKey from_secret: SeafileApiKey
APK_FILE: app/build/outputs/apk/release/app-release.apk APK_FILE: app/build/outputs/apk/release/app-release.apk
BUNDLE_FILE: app/build/outputs/bundle/release/app-release.aab BUNDLE_FILE: app/build/outputs/bundle/release/app-release.aab
SEAFILE_REPO: 6debeef9-121e-46ba-acc7-81e109fdcbdd SEAFILE_REPO: daffda8b-5840-4a65-b6d0-73b991facfb6
commands: commands:
- 'UPLOAD_URL=$(curl -H "Authorization: Token $SEAFILE_API_KEY" https://seafile.zobrist.me/api2/repos/$SEAFILE_REPO/upload-link/ | tr -d "\"")' - 'UPLOAD_URL=$(curl -H "Authorization: Token $SEAFILE_API_KEY" https://seafile.zobrist.me/api2/repos/$SEAFILE_REPO/upload-link/ | tr -d "\"")'
- 'curl -H "Authorization: Token $SEAFILE_API_KEY" -F file=@$APK_FILE -F parent_dir=/ -F relative_path=tagged/$DRONE_TAG/ -F replace=1 "$UPLOAD_URL"' - 'curl -H "Authorization: Token $SEAFILE_API_KEY" -F file=@$APK_FILE -F parent_dir=/ -F relative_path=tagged/$DRONE_TAG/ -F replace=1 "$UPLOAD_URL"'

View File

@@ -16,7 +16,7 @@ def keystoreProperties = new Properties()
def versionProperties = new Properties() def versionProperties = new Properties()
def versionMajor = 2 def versionMajor = 2
def versionMinor = 1 def versionMinor = 0
// Load your keystore.properties file into the keystoreProperties object. // Load your keystore.properties file into the keystoreProperties object.
keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
@@ -72,11 +72,11 @@ android {
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_17 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_1_8
} }
kotlinOptions { kotlinOptions {
jvmTarget = '17' jvmTarget = '1.8'
} }
namespace 'me.zobrist.tichucounter' namespace 'me.zobrist.tichucounter'
packagingOptions { packagingOptions {

View File

@@ -0,0 +1,22 @@
package me.zobrist.tichucounter
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("me.zobrist.tichucounter", appContext.packageName)
}
}

View File

@@ -1,275 +0,0 @@
package me.zobrist.tichucounter
import android.content.Context
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.runTest
import me.zobrist.tichucounter.data.AppDatabase
import me.zobrist.tichucounter.data.GameDao
import me.zobrist.tichucounter.data.RoundDao
import me.zobrist.tichucounter.repository.GameRepository
import org.junit.After
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.io.IOException
import java.util.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class RepositoryInstrumentedTest {
private lateinit var gameDao: GameDao
private lateinit var roundDao: RoundDao
private lateinit var repository: GameRepository
private lateinit var db: AppDatabase
@Before
fun createDb() {
val context = ApplicationProvider.getApplicationContext<Context>()
db = Room.inMemoryDatabaseBuilder(
context, AppDatabase::class.java
).build()
roundDao = db.roundDao()
gameDao = db.gameDao()
repository = GameRepository(gameDao, roundDao)
}
@After
@Throws(IOException::class)
fun closeDb() {
db.close()
}
@Test
@Throws(Exception::class)
fun gameInitialisation() = runTest {
repository.getActiveGameFlow().take(1).collect {
assertEquals("TeamA", it.game.nameA)
assertEquals("TeamB", it.game.nameB)
assertTrue(it.game.active)
assertEquals(0, it.rounds.count())
}
}
@Test
@Throws(Exception::class)
fun modifyNames() = runTest {
repository.getActiveGameFlow().take(1).collect {
}
repository.updateActiveTeamName(nameA = "aaa")
repository.getActiveGameFlow().take(1).collect {
assertEquals("aaa", it.game.nameA)
assertEquals("TeamB", it.game.nameB)
}
repository.updateActiveTeamName(nameB = "bbb")
repository.getActiveGameFlow().take(1).collect {
assertEquals("aaa", it.game.nameA)
assertEquals("bbb", it.game.nameB)
}
}
@Test
@Throws(Exception::class)
fun newGame() = runTest {
repository.getActiveGameFlow().take(1).collect {
}
repository.newGame()
repository.newGame()
repository.newGame()
repository.newGame()
repository.newGame()
repository.getAllWithRoundFlow().take(1).collect() { it ->
assertEquals(6, it.count())
var uid: Long = 1
it.forEach { game ->
assertEquals(uid++, game.game.uid)
assertEquals(0, game.rounds.count())
}
}
}
@Test
@Throws(Exception::class)
fun setActive() = runTest {
repository.getActiveGameFlow().take(1).collect {
}
repository.newGame()
repository.newGame()
repository.newGame()
repository.newGame()
repository.newGame()
repository.getAllWithRoundFlow().take(1).collect() { it ->
val filtered = it.filter { it.game.active }
assertEquals(1, filtered.count())
assertEquals(6, filtered.first().game.uid)
}
repository.setActive(2)
repository.getAllWithRoundFlow().take(1).collect() { it ->
val filtered = it.filter { it.game.active }
assertEquals(1, filtered.count())
assertEquals(2, filtered.first().game.uid)
}
}
@Test
@Throws(Exception::class)
fun addRoundToActiveGame() = runTest {
repository.getActiveGameFlow().take(1).collect {
}
repository.newGame()
repository.newGame()
repository.newGame()
repository.newGame()
repository.newGame()
repository.addRoundToActiveGame(1, 1)
repository.addRoundToActiveGame(2, 2)
repository.addRoundToActiveGame(3, 3)
repository.addRoundToActiveGame(4, 4)
repository.addRoundToActiveGame(5, 5)
repository.addRoundToActiveGame(6, 6)
repository.getAllWithRoundFlow().take(1).collect() { it ->
val filtered = it.filter { it.rounds.isNotEmpty() }
assertEquals(1, filtered.count())
assertEquals(6, filtered.first().rounds.count())
}
}
@Test
@Throws(Exception::class)
fun lastRound() = runTest {
repository.getActiveGameFlow().take(1).collect {
}
repository.newGame()
repository.newGame()
repository.newGame()
repository.newGame()
repository.newGame()
assertNull(repository.getLastRound())
repository.addRoundToActiveGame(1, 1)
repository.addRoundToActiveGame(2, 2)
repository.addRoundToActiveGame(3, 3)
repository.addRoundToActiveGame(4, 4)
repository.addRoundToActiveGame(5, 5)
repository.addRoundToActiveGame(6, 6)
var lastRound = repository.getLastRound()
assertEquals(6, lastRound?.scoreA)
assertEquals(6, lastRound?.scoreB)
repository.deleteLastRound()
lastRound = repository.getLastRound()
assertEquals(5, lastRound?.scoreA)
assertEquals(5, lastRound?.scoreB)
repository.deleteLastRound()
repository.deleteLastRound()
repository.deleteLastRound()
repository.deleteLastRound()
repository.deleteLastRound()
assertNull(repository.getLastRound())
// No error thrown
repository.deleteLastRound()
}
@Test
@Throws(Exception::class)
fun deleteInactive() = runTest {
repository.getActiveGameFlow().take(1).collect {
}
for (i in 1..6) {
repository.newGame()
repository.addRoundToActiveGame(1, 1)
repository.addRoundToActiveGame(2, 2)
repository.addRoundToActiveGame(3, 3)
repository.addRoundToActiveGame(4, 4)
repository.addRoundToActiveGame(5, 5)
repository.addRoundToActiveGame(6, 6)
}
assertEquals(6 * 6, roundDao.getAll().count())
repository.deleteAllInactive()
// Consists of two transactions. Delete games then delete rounds.
repository.getAllWithRoundFlow().take(1).collect() { it ->
assertEquals(1, it.count())
assertEquals(6, it.first().rounds.count())
}
assertEquals(6, roundDao.getAll().count())
}
@Test
@Throws(Exception::class)
fun deleteById() = runTest {
repository.getActiveGameFlow().take(1).collect {
}
for (i in 1..6) {
repository.newGame()
repository.addRoundToActiveGame(1, 1)
repository.addRoundToActiveGame(2, 2)
repository.addRoundToActiveGame(3, 3)
repository.addRoundToActiveGame(4, 4)
repository.addRoundToActiveGame(5, 5)
repository.addRoundToActiveGame(6, 6)
}
// Non existing Id
repository.deleteGame(10)
repository.getAllWithRoundFlow().take(1).collect() { it ->
assertEquals(7, it.count())
}
// Non existing Id
val toDelete: Long = 3
repository.deleteGame(toDelete)
repository.getAllWithRoundFlow().take(1).collect() { it ->
assertEquals(6, it.count())
assertEquals(0, it.count { it.game.uid == toDelete })
}
}
}

View File

@@ -12,7 +12,7 @@ interface GameDao : DaoBase<Game> {
fun getAll(): Flow<List<Game>> fun getAll(): Flow<List<Game>>
@Transaction @Transaction
@Query("SELECT * FROM game") @Query("SELECT * FROM game where uid ")
fun getGamesWithRounds(): Flow<List<GameWithScores>> fun getGamesWithRounds(): Flow<List<GameWithScores>>
@Transaction @Transaction
@@ -20,13 +20,10 @@ interface GameDao : DaoBase<Game> {
fun getActiveWithRounds(): Flow<GameWithScores?> fun getActiveWithRounds(): Flow<GameWithScores?>
@Query("SELECT * FROM game WHERE uid is :gameId") @Query("SELECT * FROM game WHERE uid is :gameId")
fun getGameById(gameId: Long): Game fun getGameById(gameId: Long): Flow<Game>
@Query("SELECT * FROM game WHERE active is 1") @Query("SELECT * FROM game WHERE active is 1")
fun getActiveAsFlow(): Flow<Game?> fun getActive(): Flow<Game?>
@Query("SELECT * FROM game WHERE active is 1")
fun getActive(): Game?
@Query("UPDATE game SET active = 1 WHERE uid is :gameId;") @Query("UPDATE game SET active = 1 WHERE uid is :gameId;")

View File

@@ -8,10 +8,10 @@ import me.zobrist.tichucounter.data.entity.Round
@Entity @Entity
data class GameWithScores( data class GameWithScores(
@Embedded val game: Game = Game(), @Embedded val game: Game,
@Relation( @Relation(
parentColumn = "uid", parentColumn = "uid",
entityColumn = "gameId" entityColumn = "gameId"
) )
val rounds: List<Round> = emptyList() val rounds: List<Round>
) )

View File

@@ -6,10 +6,10 @@ import java.util.*
@Entity @Entity
data class Game( data class Game(
var active: Boolean = true, var active: Boolean,
var nameA: String = "TeamA", var nameA: String,
var nameB: String = "TeamB", var nameB: String,
val created: Date = Date(), val created: Date,
var modified: Date = Date(), var modified: Date,
@PrimaryKey(autoGenerate = true) override val uid: Long = 0 @PrimaryKey(autoGenerate = true) override val uid: Long = 0
) : IEntity ) : IEntity

View File

@@ -1,12 +0,0 @@
package me.zobrist.tichucounter.domain
fun String.digitCount(): Int {
var count = 0
this.forEach {
if (it.isDigit()) {
count++
}
}
return count
}

View File

@@ -3,8 +3,6 @@ package me.zobrist.tichucounter.repository
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -21,15 +19,20 @@ class GameRepository @Inject constructor(
private val roundDao: RoundDao private val roundDao: RoundDao
) { ) {
private var activeGame: Game = Game(true, "TeamA", "TeamB", Date(), Date()) private var _activeGame: Game? = null
val activeGame: Game
get() {
return _activeGame!!
}
init { init {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
gameDao.getActiveAsFlow().collect { gameDao.getActive().collect {
if (it == null) { if (it == null) {
newGame() gameDao.insert(Game(true, "TeamA", "TeamB", Date(), Date()))
} else { } else {
activeGame = it _activeGame = it
} }
} }
} }
@@ -37,25 +40,16 @@ class GameRepository @Inject constructor(
suspend fun newGame() { suspend fun newGame() {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val id = gameDao.insert(Game(true, activeGame.nameA, activeGame.nameB, Date(), Date())) val id =
gameDao.insert(Game(true, activeGame.nameA, activeGame.nameB, Date(), Date()))
setActive(id) setActive(id)
} }
} }
suspend fun updateActiveTeamName(nameA: String? = null, nameB: String? = null) { suspend fun updateGame(game: Game) {
game.modified = Date()
val newA = nameA ?: activeGame.nameA
val newB = nameB ?: activeGame.nameB
if (newA == activeGame.nameA && newB == activeGame.nameB) {
return
}
activeGame.modified = Date()
activeGame.nameA = newA
activeGame.nameB = newB
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
gameDao.update(activeGame) gameDao.update(game)
} }
} }
@@ -98,10 +92,11 @@ class GameRepository @Inject constructor(
suspend fun deleteGame(uid: Long) { suspend fun deleteGame(uid: Long) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
try { try {
val game = gameDao.getGameById(uid) gameDao.getGameById(uid).take(1).collect {
gameDao.delete(game) gameDao.delete(it)
val rounds = roundDao.getAllForGame(game.uid) val rounds = roundDao.getAllForGame(it.uid)
roundDao.delete(rounds) roundDao.delete(rounds)
}
} catch (_: NullPointerException) { } catch (_: NullPointerException) {
} }
} }
@@ -112,8 +107,9 @@ class GameRepository @Inject constructor(
try { try {
gameDao.getAll().take(1).collect { games -> gameDao.getAll().take(1).collect { games ->
val gamesToDelete = games.filter { it.uid != activeGame.uid } val activeId = games.first { it.active }.uid
val roundsToDelete = roundDao.getAll().filter { it.gameId != activeGame.uid } val gamesToDelete = games.filter { !it.active }
val roundsToDelete = roundDao.getAll().filter { it.gameId != activeId }
gameDao.delete(gamesToDelete) gameDao.delete(gamesToDelete)
roundDao.delete(roundsToDelete) roundDao.delete(roundsToDelete)
@@ -123,8 +119,8 @@ class GameRepository @Inject constructor(
} }
} }
fun getActiveGameFlow(): Flow<GameWithScores> { fun getActiveGameFlow(): Flow<GameWithScores?> {
return gameDao.getActiveWithRounds().filter { it != null }.map { it!! } return gameDao.getActiveWithRounds()
} }
fun getAllWithRoundFlow(): Flow<List<GameWithScores>> { fun getAllWithRoundFlow(): Flow<List<GameWithScores>> {

View File

@@ -34,8 +34,9 @@ class MainViewModel @Inject constructor(
gameRepository.getActiveGameFlow().collect { gameRepository.getActiveGameFlow().collect {
activeGameHasRounds = it.rounds.isNotEmpty() == true activeGameHasRounds = it?.rounds?.isNotEmpty() == true
if (it != null) {
isUndoActionActive = it.rounds.isNotEmpty() isUndoActionActive = it.rounds.isNotEmpty()
if (expectedRoundCount != it.rounds.count()) { if (expectedRoundCount != it.rounds.count()) {
@@ -46,6 +47,7 @@ class MainViewModel @Inject constructor(
} }
} }
} }
}
fun undoLastRound() { fun undoLastRound() {
viewModelScope.launch { viewModelScope.launch {

View File

@@ -103,7 +103,7 @@ internal class PreviewViewModel : ICounterViewModel {
override var teamNameB: String = "Team B" override var teamNameB: String = "Team B"
override var currentScoreA: String = "" override var currentScoreA: String = ""
override var currentScoreB: String = "45" override var currentScoreB: String = "45"
override var isValidRound: Boolean = false override var enableSubmit: Boolean = false
override var isAFocused: Boolean = false override var isAFocused: Boolean = false
override var isBFocused: Boolean = false override var isBFocused: Boolean = false
override var requestFocusA: FocusRequester = FocusRequester() override var requestFocusA: FocusRequester = FocusRequester()
@@ -137,6 +137,9 @@ internal class PreviewViewModel : ICounterViewModel {
override fun addSub100Clicked(toAdd: Int) { override fun addSub100Clicked(toAdd: Int) {
} }
override fun deleteClicked() {
}
override fun updateNameA(value: String) { override fun updateNameA(value: String) {
} }
@@ -158,7 +161,4 @@ internal class PreviewViewModel : ICounterViewModel {
override fun showKeyboard() { override fun showKeyboard() {
} }
override fun deleteState(pressed: Boolean) {
}
} }

View File

@@ -7,12 +7,9 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.zobrist.tichucounter.data.entity.Round import me.zobrist.tichucounter.data.entity.Round
import me.zobrist.tichucounter.domain.Tichu import me.zobrist.tichucounter.domain.Tichu
import me.zobrist.tichucounter.domain.digitCount
import me.zobrist.tichucounter.domain.getTotalPoints import me.zobrist.tichucounter.domain.getTotalPoints
import me.zobrist.tichucounter.repository.GameRepository import me.zobrist.tichucounter.repository.GameRepository
import javax.inject.Inject import javax.inject.Inject
@@ -23,7 +20,7 @@ interface IKeyBoardViewModel {
val currentScoreA: String val currentScoreA: String
val currentScoreB: String val currentScoreB: String
val isValidRound: Boolean val enableSubmit: Boolean
val isAFocused: Boolean val isAFocused: Boolean
val isBFocused: Boolean val isBFocused: Boolean
val requestFocusA: FocusRequester val requestFocusA: FocusRequester
@@ -40,12 +37,12 @@ interface IKeyBoardViewModel {
fun digitClicked(digit: String) fun digitClicked(digit: String)
fun negateClicked() fun negateClicked()
fun addSub100Clicked(toAdd: Int) fun addSub100Clicked(toAdd: Int)
fun deleteClicked()
fun updateFocusStateA(state: Boolean) fun updateFocusStateA(state: Boolean)
fun updateFocusStateB(state: Boolean) fun updateFocusStateB(state: Boolean)
fun swapInputScores() fun swapInputScores()
fun hideKeyboard() fun hideKeyboard()
fun showKeyboard() fun showKeyboard()
fun deleteState(pressed: Boolean)
} }
@@ -87,7 +84,7 @@ class CounterViewModel @Inject constructor(
override var currentScoreB by mutableStateOf("") override var currentScoreB by mutableStateOf("")
private set private set
override var isValidRound by mutableStateOf(false) override var enableSubmit by mutableStateOf(false)
private set private set
override var isAFocused by mutableStateOf(false) override var isAFocused by mutableStateOf(false)
@@ -140,10 +137,6 @@ class CounterViewModel @Inject constructor(
private var lastFocused = Focused.TEAM_A private var lastFocused = Focused.TEAM_A
private var deletePressed = false
private var deleteJob: Job? = null
init { init {
viewModelScope.launch { viewModelScope.launch {
gameRepository.getActiveGameFlow().collect { gameRepository.getActiveGameFlow().collect {
@@ -194,7 +187,7 @@ class CounterViewModel @Inject constructor(
} }
override fun updateSubmitButton() { override fun updateSubmitButton() {
isValidRound = isValidTichuRound() enableSubmit = isValidTichuRound()
} }
override fun submitClicked() { override fun submitClicked() {
@@ -203,25 +196,13 @@ class CounterViewModel @Inject constructor(
} }
currentScoreA = "" currentScoreA = ""
currentScoreB = "" currentScoreB = ""
isValidRound = false enableSubmit = false
} }
override fun digitClicked(digit: String) { override fun digitClicked(digit: String) {
focusLastInput() focusLastInput()
activeValue += digit
if (activeValue.digitCount() >= 5) {
// 5 digits is enough
return
}
val newValue = activeValue + digit
try {
activeValue = newValue.toInt().toString()
} catch (_: NumberFormatException) {
}
updateOtherScore() updateOtherScore()
updateSubmitButton() updateSubmitButton()
} }
@@ -254,15 +235,27 @@ class CounterViewModel @Inject constructor(
updateSubmitButton() updateSubmitButton()
} }
override fun deleteClicked() {
if (activeValue != "") {
activeValue = activeValue.dropLast(1)
}
updateOtherScore()
updateSubmitButton()
}
override fun updateNameA(value: String) { override fun updateNameA(value: String) {
viewModelScope.launch { viewModelScope.launch {
gameRepository.updateActiveTeamName(nameA = value) val game = gameRepository.activeGame
game.nameA = value
gameRepository.updateGame(game)
} }
} }
override fun updateNameB(value: String) { override fun updateNameB(value: String) {
viewModelScope.launch { viewModelScope.launch {
gameRepository.updateActiveTeamName(nameB = value) val game = gameRepository.activeGame
game.nameB = value
gameRepository.updateGame(game)
} }
} }
@@ -293,35 +286,4 @@ class CounterViewModel @Inject constructor(
override fun showKeyboard() { override fun showKeyboard() {
keyboardHidden = false keyboardHidden = false
} }
override fun deleteState(pressed: Boolean) {
deletePressed = pressed
if (deletePressed) {
if (deleteJob?.isActive != true) {
deleteJob = deleteRepeatedlyUntilRelease()
}
} else {
deleteJob?.cancel()
}
}
private fun deleteLastDigitActive() {
if (activeValue != "") {
activeValue = activeValue.dropLast(1)
}
updateOtherScore()
updateSubmitButton()
}
private fun deleteRepeatedlyUntilRelease(): Job {
return viewModelScope.launch {
deleteLastDigitActive()
delay(500)
while (deletePressed) {
deleteLastDigitActive()
delay(100)
}
}
}
} }

View File

@@ -2,8 +2,6 @@ package me.zobrist.tichucounter.ui.counter
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.animation.core.* import androidx.compose.animation.core.*
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Backspace import androidx.compose.material.icons.outlined.Backspace
@@ -32,18 +30,18 @@ fun KeyBoardView(viewModel: IKeyBoardViewModel) {
viewModel.currentScoreB, viewModel.currentScoreB,
viewModel.requestFocusA, viewModel.requestFocusA,
viewModel.requestFocusB, viewModel.requestFocusB,
viewModel.isValidRound, viewModel.enableSubmit,
viewModel.isAFocused, viewModel.isAFocused,
viewModel.isBFocused, viewModel.isBFocused,
{ viewModel.updateFocusStateA(it) }, { viewModel.updateFocusStateA(it) },
{ viewModel.updateFocusStateB(it) }, { viewModel.updateFocusStateB(it) },
{ viewModel.digitClicked(it) }, { viewModel.digitClicked(it) },
{ viewModel.addSub100Clicked(it) }, { viewModel.addSub100Clicked(it) },
{ viewModel.deleteClicked() },
{ viewModel.negateClicked() }, { viewModel.negateClicked() },
{ viewModel.submitClicked() }, { viewModel.submitClicked() },
{ viewModel.hideKeyboard() }, { viewModel.hideKeyboard() },
{ viewModel.swapInputScores() }, { viewModel.swapInputScores() }
{ viewModel.deleteState(it) }
) )
} }
@@ -53,18 +51,18 @@ fun KeyboardView(
scoreB: String, scoreB: String,
requestFocusA: FocusRequester, requestFocusA: FocusRequester,
requestFocusB: FocusRequester, requestFocusB: FocusRequester,
isValidScore: Boolean, enableSubmit: Boolean,
focusStateA: Boolean, focusStateA: Boolean,
focusStateB: Boolean, focusStateB: Boolean,
updateFocusStateA: (Boolean) -> Unit, updateFocusStateA: (Boolean) -> Unit,
updateFocusStateB: (Boolean) -> Unit, updateFocusStateB: (Boolean) -> Unit,
digitClicked: (String) -> Unit, digitClicked: (String) -> Unit,
addSub100Clicked: (Int) -> Unit, addSub100Clicked: (Int) -> Unit,
deleteClicked: () -> Unit,
negateClicked: () -> Unit, negateClicked: () -> Unit,
submitClicked: () -> Unit, submitClicked: () -> Unit,
hideKeyboardClicked: () -> Unit, hideKeyboardClicked: () -> Unit,
onSwapClicked: () -> Unit, onSwapClicked: () -> Unit
deleteButtonPressedState: (Boolean) -> Unit
) { ) {
Column { Column {
Row(Modifier.height(IntrinsicSize.Max)) { Row(Modifier.height(IntrinsicSize.Max)) {
@@ -85,7 +83,7 @@ fun KeyboardView(
shape = MaterialTheme.shapes.extraSmall shape = MaterialTheme.shapes.extraSmall
) { ) {
Column { Column {
IconButton(onClick = onSwapClicked, enabled = isValidScore) { IconButton(onClick = onSwapClicked) {
Icon(Icons.Outlined.SwapHoriz, null) Icon(Icons.Outlined.SwapHoriz, null)
} }
} }
@@ -166,16 +164,9 @@ fun KeyboardView(
} }
} }
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
KeyboardIconButton(Icons.Outlined.Backspace) {
val interactionSource = remember { MutableInteractionSource() } deleteClicked()
val deletePressed by interactionSource.collectIsPressedAsState() }
deleteButtonPressedState(deletePressed)
KeyboardIconButton(
icon = Icons.Outlined.Backspace,
interactionSource = interactionSource
) {}
} }
} }
@@ -197,7 +188,7 @@ fun KeyboardView(
} }
} }
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
KeyboardIconButton(Icons.Outlined.Check, isValidScore) { KeyboardIconButton(Icons.Outlined.Check, enableSubmit) {
submitClicked() submitClicked()
} }
} }
@@ -228,12 +219,7 @@ fun KeyboardTextButton(text: String, onClicked: () -> Unit) {
} }
@Composable @Composable
fun KeyboardIconButton( fun KeyboardIconButton(icon: ImageVector, enabled: Boolean = true, onClicked: () -> Unit) {
icon: ImageVector,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
onClicked: () -> Unit
) {
ElevatedButton( ElevatedButton(
onClick = { onClicked() }, onClick = { onClicked() },
@@ -242,7 +228,6 @@ fun KeyboardIconButton(
.height(50.dp) .height(50.dp)
.padding(2.dp), .padding(2.dp),
enabled = enabled, enabled = enabled,
interactionSource = interactionSource
) { ) {
Icon( Icon(
icon, icon,
@@ -323,22 +308,22 @@ fun KeyboardViewPreview() {
AppTheme { AppTheme {
Surface { Surface {
KeyboardView( KeyboardView(
"10", "1",
"190", "3511",
FocusRequester(), FocusRequester(),
FocusRequester(), FocusRequester(),
isValidScore = false, enableSubmit = false,
focusStateA = true, focusStateA = true,
focusStateB = false, focusStateB = false,
updateFocusStateA = {}, updateFocusStateA = {},
updateFocusStateB = {}, updateFocusStateB = {},
digitClicked = {}, digitClicked = {},
addSub100Clicked = {}, addSub100Clicked = {},
deleteClicked = {},
negateClicked = {}, negateClicked = {},
submitClicked = {}, submitClicked = {},
hideKeyboardClicked = {}, hideKeyboardClicked = {},
onSwapClicked = {}, onSwapClicked = {})
deleteButtonPressedState = {})
} }
} }
} }

View File

@@ -22,6 +22,5 @@
<string name="active">Aktives Spiel</string> <string name="active">Aktives Spiel</string>
<string name="inactive">Vergangene Spiele</string> <string name="inactive">Vergangene Spiele</string>
<string name="menu_counter">Counter</string> <string name="menu_counter">Counter</string>
<string name="menu_about">About</string>
</resources> </resources>

View File

@@ -1,22 +0,0 @@
package me.zobrist.tichucounter
import me.zobrist.tichucounter.domain.digitCount
import org.junit.Assert.assertEquals
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class StringExtensionTest {
@Test
fun calculation_isCorrect() {
assertEquals(0, "-".digitCount())
assertEquals(0, "".digitCount())
assertEquals(2, "-10".digitCount())
assertEquals(2, "10".digitCount())
assertEquals(10, "1234567890".digitCount())
assertEquals(10, "-1234567890".digitCount())
}
}

View File

@@ -9,7 +9,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.4.2' classpath 'com.android.tools.build:gradle:7.4.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong