Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6977fc45d6 | |||
| c6e8d093cb | |||
| 0e041b79cf | |||
| ea84eeffc2 | |||
| 8f27412108 |
@@ -1,9 +1,8 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.dagger.hilt.android'
|
||||
id 'kotlin-kapt'
|
||||
id 'com.google.devtools.ksp'
|
||||
id 'org.jetbrains.kotlin.plugin.compose'
|
||||
}
|
||||
|
||||
// Create a variable called keystorePropertiesFile, and initialize it to your
|
||||
@@ -29,7 +28,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "me.zobrist.tichucounter"
|
||||
minSdkVersion 21
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 36
|
||||
versionCode versionProperties["versionCode"].toInteger()
|
||||
versionName "${versionMajor}.${versionMinor}.${versionProperties["versionCode"].toInteger()}"
|
||||
@@ -47,7 +46,7 @@ android {
|
||||
}
|
||||
}
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
register("release") {
|
||||
keyAlias = keystoreProperties["keyAlias"]
|
||||
keyPassword = keystoreProperties["keyPassword"]
|
||||
storeFile = file(keystoreProperties["storeFile"])
|
||||
@@ -65,19 +64,14 @@ android {
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
buildConfig true
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.5.14"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '17'
|
||||
}
|
||||
namespace 'me.zobrist.tichucounter'
|
||||
packagingOptions {
|
||||
resources {
|
||||
@@ -88,48 +82,42 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'androidx.core:core-ktx:1.13.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation "androidx.compose.material3:material3:1.2.1"
|
||||
implementation 'com.google.android.play:review:2.0.1'
|
||||
implementation 'com.google.android.play:review-ktx:2.0.1'
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:2.3.20"
|
||||
implementation 'androidx.core:core-ktx:1.18.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.1'
|
||||
implementation "androidx.compose.material3:material3:1.4.0"
|
||||
implementation 'com.google.android.play:review:2.0.2'
|
||||
implementation 'com.google.android.play:review-ktx:2.0.2'
|
||||
implementation 'com.google.code.gson:gson:2.13.2'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.9.7'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.9.7'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.4'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.8.2'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.10.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.10.0'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.8.9'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.4'
|
||||
implementation 'androidx.compose.material:material-icons-extended:1.6.8'
|
||||
implementation "com.google.accompanist:accompanist-systemuicontroller:0.27.0"
|
||||
implementation 'androidx.activity:activity-compose:1.9.1'
|
||||
implementation "androidx.compose.ui:ui:1.6.8"
|
||||
implementation "androidx.compose.ui:ui-tooling-preview:1.6.8"
|
||||
implementation "androidx.compose.runtime:runtime-livedata:1.6.8"
|
||||
implementation "androidx.navigation:navigation-compose:2.7.7"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.8.4"
|
||||
implementation 'androidx.recyclerview:recyclerview:1.4.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.10.0'
|
||||
implementation 'androidx.compose.material:material-icons-extended:1.7.8'
|
||||
implementation "com.google.accompanist:accompanist-systemuicontroller:0.36.0"
|
||||
implementation 'androidx.activity:activity-compose:1.13.0'
|
||||
implementation "androidx.compose.ui:ui:1.10.6"
|
||||
implementation "androidx.compose.ui:ui-tooling-preview:1.10.6"
|
||||
implementation "androidx.compose.runtime:runtime-livedata:1.10.6"
|
||||
implementation "androidx.navigation:navigation-compose:2.9.7"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0"
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||
implementation "com.google.dagger:hilt-android:2.51.1"
|
||||
androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.6.8"
|
||||
debugImplementation "androidx.compose.ui:ui-tooling:1.6.8"
|
||||
debugImplementation "androidx.compose.ui:ui-test-manifest:1.6.8"
|
||||
kapt "com.google.dagger:hilt-compiler:2.51.1"
|
||||
annotationProcessor "androidx.room:room-compiler:2.6.1"
|
||||
implementation "androidx.room:room-runtime:2.6.1"
|
||||
ksp "androidx.room:room-compiler:2.6.1"
|
||||
implementation "androidx.room:room-ktx:2.6.1"
|
||||
implementation "androidx.multidex:multidex:2.0.1"
|
||||
api "androidx.navigation:navigation-fragment-ktx:2.7.7"
|
||||
}
|
||||
|
||||
// Allow references to generated code
|
||||
kapt {
|
||||
correctErrorTypes true
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.3.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0'
|
||||
implementation "com.google.dagger:hilt-android:2.59.2"
|
||||
androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.10.6"
|
||||
debugImplementation "androidx.compose.ui:ui-tooling:1.10.6"
|
||||
debugImplementation "androidx.compose.ui:ui-test-manifest:1.10.6"
|
||||
ksp "com.google.dagger:hilt-compiler:2.59.2"
|
||||
annotationProcessor "androidx.room:room-compiler:2.8.4"
|
||||
implementation "androidx.room:room-runtime:2.8.4"
|
||||
ksp "androidx.room:room-compiler:2.8.4"
|
||||
implementation "androidx.room:room-ktx:2.8.4"
|
||||
api "androidx.navigation:navigation-fragment-ktx:2.9.7"
|
||||
}
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,6 @@ import me.zobrist.tichucounter.domain.SettingsAdapter
|
||||
import me.zobrist.tichucounter.domain.Theme
|
||||
import me.zobrist.tichucounter.domain.TopBarAction
|
||||
import me.zobrist.tichucounter.domain.TopBarState
|
||||
import me.zobrist.tichucounter.domain.navigate
|
||||
import me.zobrist.tichucounter.repository.GameRepository
|
||||
import me.zobrist.tichucounter.ui.AppTheme
|
||||
import me.zobrist.tichucounter.ui.MainViewModel
|
||||
@@ -198,7 +197,7 @@ class MainActivity : AppCompatActivity() {
|
||||
drawerState.close()
|
||||
|
||||
}
|
||||
navController.navigate(it)
|
||||
navController.navigate(it.name)
|
||||
}
|
||||
}
|
||||
) {
|
||||
@@ -292,7 +291,7 @@ class MainActivity : AppCompatActivity() {
|
||||
HistoryList(
|
||||
historyViewModel,
|
||||
snackbarHostState
|
||||
) { navController.navigate(Route.COUNTER) }
|
||||
) { navController.navigate(Route.COUNTER.name) }
|
||||
}
|
||||
composable(Route.SETTINGS.name) {
|
||||
topBarState =
|
||||
|
||||
@@ -7,27 +7,28 @@ import com.google.android.play.core.review.ReviewManagerFactory
|
||||
import dagger.hilt.android.qualifiers.ActivityContext
|
||||
import java.util.Date
|
||||
import javax.inject.Inject
|
||||
import androidx.core.content.edit
|
||||
|
||||
class ReviewService @Inject constructor(@ActivityContext private val appContext: Context) {
|
||||
|
||||
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext)
|
||||
|
||||
private val THREE_MONTHS: Long = 7776000000
|
||||
private val threeMonths: Long = 7776000000
|
||||
|
||||
private var requestCalled: Int
|
||||
get() = sharedPreferences.getInt("requestCalled", 0)
|
||||
set(value) {
|
||||
val editor = sharedPreferences.edit()
|
||||
editor.putInt("requestCalled", value)
|
||||
editor.apply()
|
||||
sharedPreferences.edit {
|
||||
putInt("requestCalled", value)
|
||||
}
|
||||
}
|
||||
|
||||
private var nextReviewedDate: Date
|
||||
get() = Date(sharedPreferences.getLong("lastReviewedDate", 0))
|
||||
set(value) {
|
||||
val editor = sharedPreferences.edit()
|
||||
editor.putLong("lastReviewedDate", value.time)
|
||||
editor.apply()
|
||||
sharedPreferences.edit {
|
||||
putLong("lastReviewedDate", value.time)
|
||||
}
|
||||
}
|
||||
|
||||
fun request() {
|
||||
@@ -36,7 +37,7 @@ class ReviewService @Inject constructor(@ActivityContext private val appContext:
|
||||
if (requestCalled >= 3) {
|
||||
if (nextReviewedDate.time < System.currentTimeMillis()) {
|
||||
requestCalled = 0
|
||||
nextReviewedDate = Date(System.currentTimeMillis() + THREE_MONTHS)
|
||||
nextReviewedDate = Date(System.currentTimeMillis() + threeMonths)
|
||||
|
||||
val manager = ReviewManagerFactory.create(appContext)
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import androidx.core.content.edit
|
||||
|
||||
enum class Theme { DEFAULT, DARK, LIGHT }
|
||||
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) {
|
||||
val editor = sharedPreferences.edit()
|
||||
editor.putString(name, value)
|
||||
editor.apply()
|
||||
sharedPreferences.edit {
|
||||
putString(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePreference(name: String?, value: Boolean) {
|
||||
val editor = sharedPreferences.edit()
|
||||
editor.putBoolean(name, value)
|
||||
editor.apply()
|
||||
sharedPreferences.edit {
|
||||
putBoolean(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePreference(name: String?, value: Int) {
|
||||
val editor = sharedPreferences.edit()
|
||||
editor.putInt(name, value)
|
||||
editor.apply()
|
||||
sharedPreferences.edit {
|
||||
putInt(name, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,17 +117,15 @@ class GameRepository @Inject constructor(
|
||||
}
|
||||
|
||||
suspend fun restoreLastDeletedGame() {
|
||||
if (deletedGame == null) {
|
||||
return
|
||||
}
|
||||
val revert = deletedGame!!
|
||||
|
||||
val snapshot = deletedGame ?: return
|
||||
deletedGame = null
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
gameDao.insert(revert.game)
|
||||
val uid = gameDao.insert(snapshot.game.copy(uid = 0))
|
||||
|
||||
revert.rounds.forEach {
|
||||
roundDao.insert(it)
|
||||
snapshot.rounds.forEach {
|
||||
roundDao.insert(Round(uid, it.scoreA, it.scoreB))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
package me.zobrist.tichucounter.ui.counter
|
||||
|
||||
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.Row
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -17,6 +23,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.graphics.TransformOrigin
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
@@ -75,13 +82,24 @@ fun Landscape(viewModel: ICounterViewModel) {
|
||||
Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
if (!viewModel.keyboardHidden) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
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)
|
||||
),
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
KeyBoardView(viewModel = viewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Portrait(viewModel: ICounterViewModel) {
|
||||
@@ -106,7 +124,19 @@ fun Portrait(viewModel: ICounterViewModel) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package me.zobrist.tichucounter.ui.counter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.core.RepeatMode
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
@@ -343,6 +344,7 @@ fun CenteredTextField(
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("RememberInComposition")
|
||||
@Preview(name = "Light Mode")
|
||||
@Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true)
|
||||
@Composable
|
||||
|
||||
@@ -3,7 +3,6 @@ package me.zobrist.tichucounter.ui.history
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -26,7 +25,6 @@ import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Badge
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
@@ -153,7 +151,6 @@ fun DeleteConfirmDialog(show: Boolean = true, onExecuted: (Boolean) -> Unit = {}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun HistoryList(
|
||||
games: List<GameWithScores>,
|
||||
@@ -166,18 +163,18 @@ fun HistoryList(
|
||||
LazyColumn(state = lazyListState) {
|
||||
items(
|
||||
items = games,
|
||||
key = { it.hashCode() }) {
|
||||
key = { it.game.uid} ) {
|
||||
if (it.game.active) {
|
||||
HistoryListItem(
|
||||
it,
|
||||
Modifier
|
||||
.animateItemPlacement()
|
||||
.animateItem()
|
||||
.padding(2.dp)
|
||||
)
|
||||
} else {
|
||||
DismissibleHistoryListItem(
|
||||
it,
|
||||
Modifier.animateItemPlacement(),
|
||||
Modifier.animateItem(),
|
||||
onOpenClicked,
|
||||
onDeleteClicked
|
||||
)
|
||||
@@ -190,7 +187,7 @@ fun HistoryList(
|
||||
.padding(start = 4.dp, end = 4.dp, top = 10.dp)
|
||||
.align(CenterVertically)
|
||||
.fillMaxWidth()
|
||||
.animateItemPlacement(),
|
||||
.animateItem(),
|
||||
onClick = { onDeleteAllClicked() }) {
|
||||
Icon(imageVector = Icons.Outlined.DeleteForever, contentDescription = null)
|
||||
Text(text = stringResource(id = R.string.deleteAll))
|
||||
@@ -202,7 +199,6 @@ fun HistoryList(
|
||||
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DismissibleHistoryListItem(
|
||||
game: GameWithScores,
|
||||
@@ -212,32 +208,25 @@ fun DismissibleHistoryListItem(
|
||||
) {
|
||||
|
||||
val density = LocalDensity.current
|
||||
val dismissState =
|
||||
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
|
||||
})
|
||||
val dismissState = rememberSwipeToDismissBoxState(positionalThreshold = { with(density) { 100.dp.toPx() } })
|
||||
|
||||
SwipeToDismissBox(
|
||||
modifier = modifier,
|
||||
state = dismissState,
|
||||
enableDismissFromEndToStart = true,
|
||||
enableDismissFromStartToEnd = true,
|
||||
backgroundContent = {
|
||||
ItemBackground(dismissState.targetValue)
|
||||
}, content = {
|
||||
HistoryListItem(game = game, modifier = Modifier.padding(2.dp))
|
||||
backgroundContent = { ItemBackground(dismissState.targetValue) },
|
||||
content = { HistoryListItem(game = game, modifier = Modifier.padding(2.dp)) },
|
||||
onDismiss = {
|
||||
when(it)
|
||||
{
|
||||
SwipeToDismissBoxValue.EndToStart -> onDeleteClicked(game.game.uid)
|
||||
SwipeToDismissBoxValue.StartToEnd -> onOpenClicked(game.game.uid)
|
||||
else -> {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ItemBackground(
|
||||
dismissBoxValue: SwipeToDismissBoxValue
|
||||
@@ -248,7 +237,7 @@ fun ItemBackground(
|
||||
SwipeToDismissBoxValue.StartToEnd -> MaterialTheme.colorScheme.primary
|
||||
else -> MaterialTheme.colorScheme.background
|
||||
|
||||
}, label = ""
|
||||
}
|
||||
)
|
||||
val textColor by animateColorAsState(
|
||||
when (dismissBoxValue) {
|
||||
@@ -256,10 +245,10 @@ fun ItemBackground(
|
||||
SwipeToDismissBoxValue.StartToEnd -> MaterialTheme.colorScheme.onPrimary
|
||||
else -> MaterialTheme.colorScheme.onBackground
|
||||
|
||||
}, label = ""
|
||||
}
|
||||
)
|
||||
val scale by animateFloatAsState(
|
||||
if (dismissBoxValue == SwipeToDismissBoxValue.Settled) 0.75f else 1f, label = ""
|
||||
if (dismissBoxValue == SwipeToDismissBoxValue.Settled) 0.75f else 1f
|
||||
)
|
||||
|
||||
Box(
|
||||
@@ -299,7 +288,6 @@ fun ItemBackground(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Preview
|
||||
@Composable
|
||||
private fun BackgroundPreview() {
|
||||
|
||||
@@ -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 |
@@ -20,7 +20,6 @@
|
||||
<string name="delete">Löschen</string>
|
||||
<string name="deleteAll">Alle löschen</string>
|
||||
<string name="active">Aktives Spiel</string>
|
||||
<string name="inactive">Vergangene Spiele</string>
|
||||
<string name="menu_counter">Counter</string>
|
||||
<string name="menu_about">About</string>
|
||||
<string name="contact_us">Schreib uns</string>
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
<string name="delete">Delete</string>
|
||||
<string name="deleteAll">Delete all</string>
|
||||
<string name="active">Current Game</string>
|
||||
<string name="inactive">Old Games</string>
|
||||
<string name="menu_counter">Counter</string>
|
||||
<string name="menu_about">About</string>
|
||||
<string name="contact_us">Contact us</string>
|
||||
|
||||
12
build.gradle
12
build.gradle
@@ -1,12 +1,11 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
ext.kotlin_version = "1.9.24"
|
||||
ext.kotlin_version = '2.2.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
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"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
@@ -15,9 +14,10 @@ buildscript {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'com.google.dagger.hilt.android' version '2.51.1' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
|
||||
id 'com.google.devtools.ksp' version '1.9.24-1.0.20' apply false
|
||||
id 'com.google.dagger.hilt.android' version '2.59' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '2.3.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 {
|
||||
|
||||
@@ -19,6 +19,7 @@ android.useAndroidX=true
|
||||
android.enableJetifier=false
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
android.nonTransitiveRClass=true
|
||||
android.nonFinalResIds=false
|
||||
android.uniquePackageNames=false
|
||||
android.dependency.useConstraints=true
|
||||
android.r8.strictFullModeForKeepRules=false
|
||||
13
gradle/gradle-daemon-jvm.properties
Normal file
13
gradle/gradle-daemon-jvm.properties
Normal 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
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
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
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
plugins {
|
||||
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
|
||||
}
|
||||
include ':app'
|
||||
rootProject.name = "Tichu Counter"
|
||||
Reference in New Issue
Block a user