6 Commits

Author SHA1 Message Date
6977fc45d6 Animate keyboard fadein/fadeout.
All checks were successful
Build Android / build (push) Successful in 3m32s
Closes #27
2026-03-30 19:23:20 +02:00
c6e8d093cb Update more dependencies and remove RepositoryInstrumentedTest 2026-03-30 18:54:03 +02:00
0e041b79cf Update dependencies and Gradle, Minimum SDK bumped to 23
All checks were successful
Build Android / build (push) Successful in 5m7s
2026-03-30 18:24:52 +02:00
ea84eeffc2 Update gradle 2026-01-29 21:57:01 +01:00
8f27412108 Merge pull request 'main' (#59) from main into develop
All checks were successful
Build Android / build (push) Successful in 2m48s
Reviewed-on: #59
2025-07-29 07:37:09 +02:00
994908be9a Merge pull request 'release/2.3.4' (#58) from release/2.3.4 into main
All checks were successful
Build Android / build (push) Successful in 2m46s
Build Android / build (pull_request) Successful in 2m44s
Reviewed-on: #58
2025-07-29 07:36:22 +02:00
22 changed files with 145 additions and 403 deletions

View File

@@ -1,9 +1,8 @@
plugins { plugins {
id 'com.android.application' id 'com.android.application'
id 'kotlin-android'
id 'com.google.dagger.hilt.android' id 'com.google.dagger.hilt.android'
id 'kotlin-kapt'
id 'com.google.devtools.ksp' id 'com.google.devtools.ksp'
id 'org.jetbrains.kotlin.plugin.compose'
} }
// Create a variable called keystorePropertiesFile, and initialize it to your // Create a variable called keystorePropertiesFile, and initialize it to your
@@ -29,7 +28,7 @@ android {
defaultConfig { defaultConfig {
applicationId "me.zobrist.tichucounter" applicationId "me.zobrist.tichucounter"
minSdkVersion 21 minSdkVersion 23
targetSdkVersion 36 targetSdkVersion 36
versionCode versionProperties["versionCode"].toInteger() versionCode versionProperties["versionCode"].toInteger()
versionName "${versionMajor}.${versionMinor}.${versionProperties["versionCode"].toInteger()}" versionName "${versionMajor}.${versionMinor}.${versionProperties["versionCode"].toInteger()}"
@@ -47,7 +46,7 @@ android {
} }
} }
signingConfigs { signingConfigs {
create("release") { register("release") {
keyAlias = keystoreProperties["keyAlias"] keyAlias = keystoreProperties["keyAlias"]
keyPassword = keystoreProperties["keyPassword"] keyPassword = keystoreProperties["keyPassword"]
storeFile = file(keystoreProperties["storeFile"]) storeFile = file(keystoreProperties["storeFile"])
@@ -65,19 +64,14 @@ android {
buildFeatures { buildFeatures {
compose = true compose = true
buildConfig true
} }
composeOptions {
kotlinCompilerExtensionVersion = "1.5.14"
}
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_17 sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17
} }
kotlinOptions {
jvmTarget = '17'
}
namespace 'me.zobrist.tichucounter' namespace 'me.zobrist.tichucounter'
packagingOptions { packagingOptions {
resources { resources {
@@ -88,48 +82,42 @@ android {
dependencies { dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"]) implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:2.3.20"
implementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.core:core-ktx:1.18.0'
implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.appcompat:appcompat:1.7.1'
implementation "androidx.compose.material3:material3:1.2.1" implementation "androidx.compose.material3:material3:1.4.0"
implementation 'com.google.android.play:review:2.0.1' implementation 'com.google.android.play:review:2.0.2'
implementation 'com.google.android.play:review-ktx:2.0.1' implementation 'com.google.android.play:review-ktx:2.0.2'
implementation 'com.google.code.gson:gson:2.10.1' implementation 'com.google.code.gson:gson:2.13.2'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7' implementation 'androidx.navigation:navigation-fragment-ktx:2.9.7'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7' implementation 'androidx.navigation:navigation-ui-ktx:2.9.7'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.4' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.10.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.10.0'
implementation 'androidx.fragment:fragment-ktx:1.8.2' implementation 'androidx.fragment:fragment-ktx:1.8.9'
implementation 'androidx.preference:preference-ktx:1.2.1' implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.recyclerview:recyclerview:1.3.2' implementation 'androidx.recyclerview:recyclerview:1.4.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.4' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.10.0'
implementation 'androidx.compose.material:material-icons-extended:1.6.8' implementation 'androidx.compose.material:material-icons-extended:1.7.8'
implementation "com.google.accompanist:accompanist-systemuicontroller:0.27.0" implementation "com.google.accompanist:accompanist-systemuicontroller:0.36.0"
implementation 'androidx.activity:activity-compose:1.9.1' implementation 'androidx.activity:activity-compose:1.13.0'
implementation "androidx.compose.ui:ui:1.6.8" implementation "androidx.compose.ui:ui:1.10.6"
implementation "androidx.compose.ui:ui-tooling-preview:1.6.8" implementation "androidx.compose.ui:ui-tooling-preview:1.10.6"
implementation "androidx.compose.runtime:runtime-livedata:1.6.8" implementation "androidx.compose.runtime:runtime-livedata:1.10.6"
implementation "androidx.navigation:navigation-compose:2.7.7" implementation "androidx.navigation:navigation-compose:2.9.7"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.8.4" implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0"
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1' androidTestImplementation 'androidx.test.ext:junit:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0'
implementation "com.google.dagger:hilt-android:2.51.1" implementation "com.google.dagger:hilt-android:2.59.2"
androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.6.8" androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.10.6"
debugImplementation "androidx.compose.ui:ui-tooling:1.6.8" debugImplementation "androidx.compose.ui:ui-tooling:1.10.6"
debugImplementation "androidx.compose.ui:ui-test-manifest:1.6.8" debugImplementation "androidx.compose.ui:ui-test-manifest:1.10.6"
kapt "com.google.dagger:hilt-compiler:2.51.1" ksp "com.google.dagger:hilt-compiler:2.59.2"
annotationProcessor "androidx.room:room-compiler:2.6.1" annotationProcessor "androidx.room:room-compiler:2.8.4"
implementation "androidx.room:room-runtime:2.6.1" implementation "androidx.room:room-runtime:2.8.4"
ksp "androidx.room:room-compiler:2.6.1" ksp "androidx.room:room-compiler:2.8.4"
implementation "androidx.room:room-ktx:2.6.1" implementation "androidx.room:room-ktx:2.8.4"
implementation "androidx.multidex:multidex:2.0.1" api "androidx.navigation:navigation-fragment-ktx:2.9.7"
api "androidx.navigation:navigation-fragment-ktx:2.7.7"
}
// Allow references to generated code
kapt {
correctErrorTypes true
} }

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

@@ -53,7 +53,6 @@ import me.zobrist.tichucounter.domain.SettingsAdapter
import me.zobrist.tichucounter.domain.Theme import me.zobrist.tichucounter.domain.Theme
import me.zobrist.tichucounter.domain.TopBarAction import me.zobrist.tichucounter.domain.TopBarAction
import me.zobrist.tichucounter.domain.TopBarState import me.zobrist.tichucounter.domain.TopBarState
import me.zobrist.tichucounter.domain.navigate
import me.zobrist.tichucounter.repository.GameRepository import me.zobrist.tichucounter.repository.GameRepository
import me.zobrist.tichucounter.ui.AppTheme import me.zobrist.tichucounter.ui.AppTheme
import me.zobrist.tichucounter.ui.MainViewModel import me.zobrist.tichucounter.ui.MainViewModel
@@ -198,7 +197,7 @@ class MainActivity : AppCompatActivity() {
drawerState.close() drawerState.close()
} }
navController.navigate(it) navController.navigate(it.name)
} }
} }
) { ) {
@@ -292,7 +291,7 @@ class MainActivity : AppCompatActivity() {
HistoryList( HistoryList(
historyViewModel, historyViewModel,
snackbarHostState snackbarHostState
) { navController.navigate(Route.COUNTER) } ) { navController.navigate(Route.COUNTER.name) }
} }
composable(Route.SETTINGS.name) { composable(Route.SETTINGS.name) {
topBarState = topBarState =

View File

@@ -7,27 +7,28 @@ import com.google.android.play.core.review.ReviewManagerFactory
import dagger.hilt.android.qualifiers.ActivityContext import dagger.hilt.android.qualifiers.ActivityContext
import java.util.Date import java.util.Date
import javax.inject.Inject import javax.inject.Inject
import androidx.core.content.edit
class ReviewService @Inject constructor(@ActivityContext private val appContext: Context) { class ReviewService @Inject constructor(@ActivityContext private val appContext: Context) {
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext) private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext)
private val THREE_MONTHS: Long = 7776000000 private val threeMonths: Long = 7776000000
private var requestCalled: Int private var requestCalled: Int
get() = sharedPreferences.getInt("requestCalled", 0) get() = sharedPreferences.getInt("requestCalled", 0)
set(value) { set(value) {
val editor = sharedPreferences.edit() sharedPreferences.edit {
editor.putInt("requestCalled", value) putInt("requestCalled", value)
editor.apply() }
} }
private var nextReviewedDate: Date private var nextReviewedDate: Date
get() = Date(sharedPreferences.getLong("lastReviewedDate", 0)) get() = Date(sharedPreferences.getLong("lastReviewedDate", 0))
set(value) { set(value) {
val editor = sharedPreferences.edit() sharedPreferences.edit {
editor.putLong("lastReviewedDate", value.time) putLong("lastReviewedDate", value.time)
editor.apply() }
} }
fun request() { fun request() {
@@ -36,7 +37,7 @@ class ReviewService @Inject constructor(@ActivityContext private val appContext:
if (requestCalled >= 3) { if (requestCalled >= 3) {
if (nextReviewedDate.time < System.currentTimeMillis()) { if (nextReviewedDate.time < System.currentTimeMillis()) {
requestCalled = 0 requestCalled = 0
nextReviewedDate = Date(System.currentTimeMillis() + THREE_MONTHS) nextReviewedDate = Date(System.currentTimeMillis() + threeMonths)
val manager = ReviewManagerFactory.create(appContext) val manager = ReviewManagerFactory.create(appContext)

View File

@@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import androidx.core.content.edit
enum class Theme { DEFAULT, DARK, LIGHT } enum class Theme { DEFAULT, DARK, LIGHT }
enum class Language(val value: LocaleListCompat) { enum class Language(val value: LocaleListCompat) {
@@ -93,20 +94,20 @@ class SettingsAdapter @Inject constructor(@ApplicationContext private val contex
} }
private fun updatePreference(name: String?, value: String) { private fun updatePreference(name: String?, value: String) {
val editor = sharedPreferences.edit() sharedPreferences.edit {
editor.putString(name, value) putString(name, value)
editor.apply() }
} }
private fun updatePreference(name: String?, value: Boolean) { private fun updatePreference(name: String?, value: Boolean) {
val editor = sharedPreferences.edit() sharedPreferences.edit {
editor.putBoolean(name, value) putBoolean(name, value)
editor.apply() }
} }
private fun updatePreference(name: String?, value: Int) { private fun updatePreference(name: String?, value: Int) {
val editor = sharedPreferences.edit() sharedPreferences.edit {
editor.putInt(name, value) putInt(name, value)
editor.apply() }
} }
} }

View File

@@ -117,17 +117,15 @@ class GameRepository @Inject constructor(
} }
suspend fun restoreLastDeletedGame() { suspend fun restoreLastDeletedGame() {
if (deletedGame == null) {
return val snapshot = deletedGame ?: return
}
val revert = deletedGame!!
deletedGame = null deletedGame = null
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
gameDao.insert(revert.game) val uid = gameDao.insert(snapshot.game.copy(uid = 0))
revert.rounds.forEach { snapshot.rounds.forEach {
roundDao.insert(it) roundDao.insert(Round(uid, it.scoreA, it.scoreB))
} }
} }
} }

View File

@@ -1,6 +1,12 @@
package me.zobrist.tichucounter.ui.counter package me.zobrist.tichucounter.ui.counter
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -17,6 +23,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@@ -75,10 +82,21 @@ fun Landscape(viewModel: ICounterViewModel) {
Modifier.weight(1f) Modifier.weight(1f)
) )
} }
if (!viewModel.keyboardHidden) { AnimatedVisibility(
Column(Modifier.weight(1f)) { visible = !viewModel.keyboardHidden,
KeyBoardView(viewModel = viewModel) enter = fadeIn(animationSpec = tween(100)) + scaleIn(
} animationSpec = tween(100),
initialScale = 0.8f,
transformOrigin = TransformOrigin(1f, 1f)
),
exit = fadeOut(animationSpec = tween(100)) + scaleOut(
animationSpec = tween(100),
targetScale = 0.8f,
transformOrigin = TransformOrigin(1f, 1f)
),
modifier = Modifier.weight(1f)
) {
KeyBoardView(viewModel = viewModel)
} }
} }
} }
@@ -106,7 +124,19 @@ fun Portrait(viewModel: ICounterViewModel) {
Modifier.weight(1f) Modifier.weight(1f)
) )
if (!viewModel.keyboardHidden) { AnimatedVisibility(
visible = !viewModel.keyboardHidden,
enter = fadeIn(animationSpec = tween(100)) + scaleIn(
animationSpec = tween(100),
initialScale = 0.8f,
transformOrigin = TransformOrigin(1f, 1f)
),
exit = fadeOut(animationSpec = tween(100)) + scaleOut(
animationSpec = tween(100),
targetScale = 0.8f,
transformOrigin = TransformOrigin(1f, 1f)
)
) {
KeyBoardView(viewModel = viewModel) KeyBoardView(viewModel = viewModel)
} }
} }

View File

@@ -1,5 +1,6 @@
package me.zobrist.tichucounter.ui.counter package me.zobrist.tichucounter.ui.counter
import android.annotation.SuppressLint
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.animateFloat
@@ -343,6 +344,7 @@ fun CenteredTextField(
} }
@SuppressLint("RememberInComposition")
@Preview(name = "Light Mode") @Preview(name = "Light Mode")
@Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true) @Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true)
@Composable @Composable

View File

@@ -3,7 +3,6 @@ package me.zobrist.tichucounter.ui.history
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -26,7 +25,6 @@ import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Badge import androidx.compose.material3.Badge
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarDuration
@@ -153,7 +151,6 @@ fun DeleteConfirmDialog(show: Boolean = true, onExecuted: (Boolean) -> Unit = {}
} }
} }
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun HistoryList( fun HistoryList(
games: List<GameWithScores>, games: List<GameWithScores>,
@@ -166,18 +163,18 @@ fun HistoryList(
LazyColumn(state = lazyListState) { LazyColumn(state = lazyListState) {
items( items(
items = games, items = games,
key = { it.hashCode() }) { key = { it.game.uid} ) {
if (it.game.active) { if (it.game.active) {
HistoryListItem( HistoryListItem(
it, it,
Modifier Modifier
.animateItemPlacement() .animateItem()
.padding(2.dp) .padding(2.dp)
) )
} else { } else {
DismissibleHistoryListItem( DismissibleHistoryListItem(
it, it,
Modifier.animateItemPlacement(), Modifier.animateItem(),
onOpenClicked, onOpenClicked,
onDeleteClicked onDeleteClicked
) )
@@ -190,7 +187,7 @@ fun HistoryList(
.padding(start = 4.dp, end = 4.dp, top = 10.dp) .padding(start = 4.dp, end = 4.dp, top = 10.dp)
.align(CenterVertically) .align(CenterVertically)
.fillMaxWidth() .fillMaxWidth()
.animateItemPlacement(), .animateItem(),
onClick = { onDeleteAllClicked() }) { onClick = { onDeleteAllClicked() }) {
Icon(imageVector = Icons.Outlined.DeleteForever, contentDescription = null) Icon(imageVector = Icons.Outlined.DeleteForever, contentDescription = null)
Text(text = stringResource(id = R.string.deleteAll)) Text(text = stringResource(id = R.string.deleteAll))
@@ -202,7 +199,6 @@ fun HistoryList(
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun DismissibleHistoryListItem( fun DismissibleHistoryListItem(
game: GameWithScores, game: GameWithScores,
@@ -212,32 +208,25 @@ fun DismissibleHistoryListItem(
) { ) {
val density = LocalDensity.current val density = LocalDensity.current
val dismissState = val dismissState = rememberSwipeToDismissBoxState(positionalThreshold = { with(density) { 100.dp.toPx() } })
rememberSwipeToDismissBoxState(positionalThreshold = { with(density) { 100.dp.toPx() } },
confirmValueChange = {
if (it == SwipeToDismissBoxValue.EndToStart) {
onDeleteClicked(game.game.uid)
}
if (it == SwipeToDismissBoxValue.StartToEnd) {
onOpenClicked(game.game.uid)
}
true
})
SwipeToDismissBox( SwipeToDismissBox(
modifier = modifier, modifier = modifier,
state = dismissState, state = dismissState,
enableDismissFromEndToStart = true, enableDismissFromEndToStart = true,
enableDismissFromStartToEnd = true, enableDismissFromStartToEnd = true,
backgroundContent = { backgroundContent = { ItemBackground(dismissState.targetValue) },
ItemBackground(dismissState.targetValue) content = { HistoryListItem(game = game, modifier = Modifier.padding(2.dp)) },
}, content = { onDismiss = {
HistoryListItem(game = game, modifier = Modifier.padding(2.dp)) when(it)
{
SwipeToDismissBoxValue.EndToStart -> onDeleteClicked(game.game.uid)
SwipeToDismissBoxValue.StartToEnd -> onOpenClicked(game.game.uid)
else -> {}
}
}) })
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun ItemBackground( fun ItemBackground(
dismissBoxValue: SwipeToDismissBoxValue dismissBoxValue: SwipeToDismissBoxValue
@@ -248,7 +237,7 @@ fun ItemBackground(
SwipeToDismissBoxValue.StartToEnd -> MaterialTheme.colorScheme.primary SwipeToDismissBoxValue.StartToEnd -> MaterialTheme.colorScheme.primary
else -> MaterialTheme.colorScheme.background else -> MaterialTheme.colorScheme.background
}, label = "" }
) )
val textColor by animateColorAsState( val textColor by animateColorAsState(
when (dismissBoxValue) { when (dismissBoxValue) {
@@ -256,10 +245,10 @@ fun ItemBackground(
SwipeToDismissBoxValue.StartToEnd -> MaterialTheme.colorScheme.onPrimary SwipeToDismissBoxValue.StartToEnd -> MaterialTheme.colorScheme.onPrimary
else -> MaterialTheme.colorScheme.onBackground else -> MaterialTheme.colorScheme.onBackground
}, label = "" }
) )
val scale by animateFloatAsState( val scale by animateFloatAsState(
if (dismissBoxValue == SwipeToDismissBoxValue.Settled) 0.75f else 1f, label = "" if (dismissBoxValue == SwipeToDismissBoxValue.Settled) 0.75f else 1f
) )
Box( Box(
@@ -299,7 +288,6 @@ fun ItemBackground(
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Preview @Preview
@Composable @Composable
private fun BackgroundPreview() { private fun BackgroundPreview() {

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -20,7 +20,6 @@
<string name="delete">Löschen</string> <string name="delete">Löschen</string>
<string name="deleteAll">Alle löschen</string> <string name="deleteAll">Alle löschen</string>
<string name="active">Aktives Spiel</string> <string name="active">Aktives Spiel</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> <string name="menu_about">About</string>
<string name="contact_us">Schreib uns</string> <string name="contact_us">Schreib uns</string>

View File

@@ -23,7 +23,6 @@
<string name="delete">Delete</string> <string name="delete">Delete</string>
<string name="deleteAll">Delete all</string> <string name="deleteAll">Delete all</string>
<string name="active">Current Game</string> <string name="active">Current Game</string>
<string name="inactive">Old Games</string>
<string name="menu_counter">Counter</string> <string name="menu_counter">Counter</string>
<string name="menu_about">About</string> <string name="menu_about">About</string>
<string name="contact_us">Contact us</string> <string name="contact_us">Contact us</string>

View File

@@ -1,12 +1,11 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = "1.9.24" ext.kotlin_version = '2.2.10'
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:8.11.0' classpath 'com.android.tools.build:gradle:9.1.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
@@ -15,9 +14,10 @@ buildscript {
} }
plugins { plugins {
id 'com.google.dagger.hilt.android' version '2.51.1' apply false id 'com.google.dagger.hilt.android' version '2.59' apply false
id 'org.jetbrains.kotlin.android' version '1.7.20' apply false id 'org.jetbrains.kotlin.android' version '2.3.20' apply false
id 'com.google.devtools.ksp' version '1.9.24-1.0.20' apply false id 'com.google.devtools.ksp' version '2.3.5' apply false
id 'org.jetbrains.kotlin.plugin.compose' version '2.3.20' apply false
} }
allprojects { allprojects {

View File

@@ -19,6 +19,7 @@ android.useAndroidX=true
android.enableJetifier=false android.enableJetifier=false
# Kotlin code style for this project: "official" or "obsolete": # Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official kotlin.code.style=official
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
android.nonFinalResIds=false android.uniquePackageNames=false
android.dependency.useConstraints=true
android.r8.strictFullModeForKeepRules=false

View File

@@ -0,0 +1,13 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e99bae143b75f9a10ead10248f02055e/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/04e088f8677de3b384108493cc9481d0/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/248ffb1098f61659502d0c09aa348294/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/056dc25d3b9d168ede8b94d3d2f99942/redirect
toolchainVendor=JETBRAINS
toolchainVersion=21

View File

@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-all.zip

View File

@@ -1,2 +1,5 @@
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
}
include ':app' include ':app'
rootProject.name = "Tichu Counter" rootProject.name = "Tichu Counter"