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
59 changed files with 300 additions and 1070 deletions

74
.drone.yml Normal file
View File

@@ -0,0 +1,74 @@
---
kind: pipeline
type: docker
name: Android
steps:
- name: prepare signing
image: busybox
environment:
STOREPASSWORD:
from_secret: StorePassword
KEYPASSWORD:
from_secret: KeyPassword
commands:
- touch keystore.properties
- echo "storePassword=$STOREPASSWORD" >> keystore.properties
- echo "keyPassword=$KEYPASSWORD" >> keystore.properties
- echo "keyAlias=key0" >> keystore.properties
- echo "storeFile=../AndroidKey" >> keystore.properties
- name: generate versionCode
image: busybox
commands:
- touch version.properties
- let timestamp=$(date +%s)/10
- echo "versionCode=$timestamp" >> version.properties
- name: build
image: mingc/android-build-box
commands:
- ./gradlew test
- ./gradlew assembleRelease
- ./gradlew bundleRelease
- name: deploy latest build
image: curlimages/curl
environment:
SEAFILE_API_KEY:
from_secret: SeafileApiKey
APK_FILE: app/build/outputs/apk/release/app-release.apk
BUNDLE_FILE: app/build/outputs/bundle/release/app-release.aab
SEAFILE_REPO: daffda8b-5840-4a65-b6d0-73b991facfb6
commands:
- '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=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"'
- name: deploy tagged build
image: curlimages/curl
environment:
SEAFILE_API_KEY:
from_secret: SeafileApiKey
APK_FILE: app/build/outputs/apk/release/app-release.apk
BUNDLE_FILE: app/build/outputs/bundle/release/app-release.aab
SEAFILE_REPO: daffda8b-5840-4a65-b6d0-73b991facfb6
commands:
- '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=@$BUNDLE_FILE -F parent_dir=/ -F relative_path=tagged/$DRONE_TAG/ -F replace=1 "$UPLOAD_URL"'
- 'curl -d "operation=rename&newname=app-release$DRONE_TAG.apk" -H "Authorization: Token $SEAFILE_API_KEY" https://seafile.zobrist.me/api2/repos/$SEAFILE_REPO/file/?p=/tagged/$DRONE_TAG/app-release.apk'
- 'curl -d "operation=rename&newname=app-release$DRONE_TAG.aab" -H "Authorization: Token $SEAFILE_API_KEY" https://seafile.zobrist.me/api2/repos/$SEAFILE_REPO/file/?p=/tagged/$DRONE_TAG/app-release.aab'
when:
event:
- tag
- name: slack notification
image: plugins/slack
settings:
webhook:
from_secret: SlackWebhook
when:
status:
- failure
- success

View File

@@ -1,48 +0,0 @@
name: Build Android
on: [pull_request, push]
jobs:
build:
runs-on: ubuntu-latest-android
steps:
- name: Checkout the code
uses: actions/checkout@v2
- name: Prepare signing
run: |
touch keystore.properties
echo "storePassword=${{ secrets.STOREPASSWORD }}" >> keystore.properties
echo "keyPassword=${{ secrets.KEYPASSWORD }}" >> keystore.properties
echo "keyAlias=key0" >> keystore.properties
echo "storeFile=../AndroidKey" >> keystore.properties
- name: Generate versionCode
run: |
touch version.properties
let timestamp=$(date +%s)/10
echo "versionCode=$timestamp" >> version.properties
- name: Test the app
run: ./gradlew test
- name: Build apk
run: ./gradlew assembleRelease
- name: Build abb
run: ./gradlew bundleRelease
- name: Deploy latest to Nextcloud
run: |
curl -k -u "${{ secrets.NEXTCLOUD_USERNAME }}:${{ secrets.NEXTCLOUD_PASSWORD }}" -T "app/build/outputs/apk/release/app-release.apk" "https://nextcloud.zobrist.me/remote.php/dav/files/deploy/TichuCounter/latest/app-release.apk"
curl -k -u "${{ secrets.NEXTCLOUD_USERNAME }}:${{ secrets.NEXTCLOUD_PASSWORD }}" -T "app/build/outputs/bundle/release/app-release.aab" "https://nextcloud.zobrist.me/remote.php/dav/files/deploy/TichuCounter/latest/app-release.aab"
- name: Deploy tagged build to Nextcloud
run: |
curl -k -u "${{ secrets.NEXTCLOUD_USERNAME }}:${{ secrets.NEXTCLOUD_PASSWORD }}" -T "app/build/outputs/apk/release/app-release.apk" "https://nextcloud.zobrist.me/remote.php/dav/files/deploy/TichuCounter/tagged/app-release-${GITHUB_REF_NAME##*/}.apk"
curl -k -u "${{ secrets.NEXTCLOUD_USERNAME }}:${{ secrets.NEXTCLOUD_PASSWORD }}" -T "app/build/outputs/bundle/release/app-release.aab" "https://nextcloud.zobrist.me/remote.php/dav/files/deploy/TichuCounter/tagged/app-release-${GITHUB_REF_NAME##*/}.aab"
- uses: https://github.com/ravsamhq/notify-slack-action@v2
if: always()
with:
status: ${{ job.status }} # required
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # required

View File

@@ -16,7 +16,7 @@ def keystoreProperties = new Properties()
def versionProperties = new Properties()
def versionMajor = 2
def versionMinor = 2
def versionMinor = 0
// Load your keystore.properties file into the keystoreProperties object.
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
@@ -32,7 +32,7 @@ android {
targetSdkVersion 33
versionCode versionProperties["versionCode"].toInteger()
versionName "${versionMajor}.${versionMinor}.${versionProperties["versionCode"].toInteger()}"
resourceConfigurations += ['de', 'en']
resConfigs 'de', 'en'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
vectorDrawables {
@@ -68,15 +68,15 @@ android {
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.7"
kotlinCompilerExtensionVersion = "1.3.2"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '17'
jvmTarget = '1.8'
}
namespace 'me.zobrist.tichucounter'
packagingOptions {
@@ -89,9 +89,9 @@ android {
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation "androidx.compose.material3:material3:1.1.0"
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.6.0-rc01'
implementation "androidx.compose.material3:material3:1.0.1"
implementation 'com.google.android.play:core-ktx:1.8.1'
implementation 'com.google.android.play:core-ktx:1.8.1'
implementation 'com.google.code.gson:gson:2.9.0'
@@ -99,32 +99,32 @@ dependencies {
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3'
implementation 'androidx.navigation:navigation-ui-ktx:2.5.3'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
implementation 'androidx.fragment:fragment-ktx:1.5.7'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
implementation 'androidx.fragment:fragment-ktx:1.5.5'
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
implementation 'androidx.compose.material:material-icons-extended:1.4.3'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
implementation 'androidx.compose.material:material-icons-extended:1.3.1'
implementation "com.google.accompanist:accompanist-systemuicontroller:0.27.0"
implementation 'androidx.activity:activity-compose:1.7.2'
implementation "androidx.compose.ui:ui:1.4.3"
implementation "androidx.compose.ui:ui-tooling-preview:1.4.3"
implementation "androidx.compose.runtime:runtime-livedata:1.4.3"
implementation 'androidx.activity:activity-compose:1.6.1'
implementation "androidx.compose.ui:ui:1.3.3"
implementation "androidx.compose.ui:ui-tooling-preview:1.3.3"
implementation "androidx.compose.runtime:runtime-livedata:1.3.3"
implementation "androidx.navigation:navigation-compose:2.5.3"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1"
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation "com.google.dagger:hilt-android:2.44"
androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.4.3"
debugImplementation "androidx.compose.ui:ui-tooling:1.4.3"
debugImplementation "androidx.compose.ui:ui-test-manifest:1.4.3"
androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.3.3"
debugImplementation "androidx.compose.ui:ui-tooling:1.3.3"
debugImplementation "androidx.compose.ui:ui-test-manifest:1.3.3"
kapt "com.google.dagger:hilt-compiler:2.44"
implementation "androidx.room:room-runtime:2.5.1"
annotationProcessor "androidx.room:room-compiler:2.5.1"
kapt "androidx.room:room-compiler:2.5.1"
implementation "androidx.room:room-ktx:2.5.1"
implementation "androidx.room:room-runtime:2.5.0"
annotationProcessor "androidx.room:room-compiler:2.5.0"
kapt "androidx.room:room-compiler:2.5.0"
implementation "androidx.room:room-ktx:2.5.0"
implementation "androidx.multidex:multidex:2.0.1"
api "androidx.navigation:navigation-fragment-ktx:2.5.3"
}

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

@@ -9,6 +9,7 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:localeConfig="@xml/locales_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -37,7 +37,7 @@ import me.zobrist.tichucounter.ui.settings.SettingsViewModel
import javax.inject.Inject
@AndroidEntryPoint
class MainActivity : AppCompatActivity(), ISystemSettingsChangeListener {
class MainActivity : AppCompatActivity(), ISettingsChangeListener {
@Inject
lateinit var settingsAdapter: SettingsAdapter
@@ -88,6 +88,7 @@ class MainActivity : AppCompatActivity(), ISystemSettingsChangeListener {
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun NavigationDrawer() {
val drawerState = rememberDrawerState(DrawerValue.Closed)
@@ -141,6 +142,7 @@ class MainActivity : AppCompatActivity(), ISystemSettingsChangeListener {
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MyScaffoldLayout(
drawerState: DrawerState,
@@ -203,12 +205,7 @@ class MainActivity : AppCompatActivity(), ISystemSettingsChangeListener {
},
))
) {
scope.launch {
currentFocus?.clearFocus()
drawerState.open()
}
}
) { scope.launch { drawerState.open() } }
Counter(counterViewModel)
}

View File

@@ -2,7 +2,7 @@ package me.zobrist.tichucounter.data
import androidx.room.ProvidedTypeConverter
import androidx.room.TypeConverter
import java.util.Date
import java.util.*
@ProvidedTypeConverter
object DateConverter {

View File

@@ -12,7 +12,7 @@ interface GameDao : DaoBase<Game> {
fun getAll(): Flow<List<Game>>
@Transaction
@Query("SELECT * FROM game")
@Query("SELECT * FROM game where uid ")
fun getGamesWithRounds(): Flow<List<GameWithScores>>
@Transaction
@@ -20,13 +20,10 @@ interface GameDao : DaoBase<Game> {
fun getActiveWithRounds(): Flow<GameWithScores?>
@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")
fun getActiveAsFlow(): Flow<Game?>
@Query("SELECT * FROM game WHERE active is 1")
fun getActive(): Game?
fun getActive(): Flow<Game?>
@Query("UPDATE game SET active = 1 WHERE uid is :gameId;")
@@ -35,7 +32,4 @@ interface GameDao : DaoBase<Game> {
@Query("UPDATE game SET active = 0 WHERE uid is not :gameId;")
fun setOthersInactive(gameId: Long)
@Query("SELECT names FROM (SELECT nameA AS names FROM game UNION ALL SELECT nameB AS names FROM game) GROUP BY names")
fun getDistinctTeamNames(): Flow<List<String>>
}

View File

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

View File

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

View File

@@ -1,15 +1,11 @@
package me.zobrist.tichucounter.domain
import androidx.compose.runtime.Composable
import androidx.navigation.NamedNavArgument
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
import androidx.navigation.NavDeepLink
import androidx.navigation.NavGraphBuilder
import androidx.navigation.*
import androidx.navigation.compose.composable
fun NavController.navigate(route: Route) {
this.navigate(route.name) {
this.navigate(route.name){
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items

View File

@@ -16,26 +16,17 @@ enum class Language(val value: LocaleListCompat) {
enum class KeepScreenOn(val value: Boolean) { ON(true), OFF(false) }
enum class CounterListView { BY_ROUND, CONTINUOUS }
interface ISystemSettingsChangeListener {
interface ISettingsChangeListener {
fun onLanguageChanged(language: Language)
fun onThemeChanged(theme: Theme)
fun onScreenOnChanged(keepOn: KeepScreenOn)
}
interface IDisplaySettingsChangeListener {
fun onCounterListViewChanged(listView: CounterListView)
}
@Singleton
class SettingsAdapter @Inject constructor(@ApplicationContext private val context: Context) {
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
private var systemListenerList = mutableListOf<ISystemSettingsChangeListener>()
private var displayListenerList = mutableListOf<IDisplaySettingsChangeListener>()
private var listenerList = mutableListOf<ISettingsChangeListener>()
var language: Language
private set
@@ -46,9 +37,6 @@ class SettingsAdapter @Inject constructor(@ApplicationContext private val contex
var keepScreenOn: KeepScreenOn
private set
var counterListView: CounterListView
private set
init {
language = try {
enumValueOf(sharedPreferences.getString(Language::class.simpleName, null)!!)
@@ -67,37 +55,19 @@ class SettingsAdapter @Inject constructor(@ApplicationContext private val contex
} catch (_: java.lang.Exception) {
KeepScreenOn.OFF
}
counterListView = try {
enumValueOf(sharedPreferences.getString(CounterListView::class.simpleName, null)!!)
} catch (_: java.lang.Exception) {
CounterListView.BY_ROUND
}
}
fun registerOnChangeListener(listener: ISystemSettingsChangeListener) {
systemListenerList.add(listener)
fun registerOnChangeListener(listener: ISettingsChangeListener) {
listenerList.add(listener)
listener.onThemeChanged(theme)
listener.onLanguageChanged(language)
listener.onScreenOnChanged(keepScreenOn)
}
fun registerOnChangeListener(listener: IDisplaySettingsChangeListener) {
displayListenerList.add(listener)
listener.onCounterListViewChanged(counterListView)
}
fun unregisterOnChangeListener(listener: ISystemSettingsChangeListener?) {
fun unregisterOnChangeListener(listener: ISettingsChangeListener?) {
if (listener != null) {
systemListenerList.remove(listener)
}
}
fun unregisterOnChangeListener(listener: IDisplaySettingsChangeListener?) {
if (listener != null) {
displayListenerList.remove(listener)
listenerList.remove(listener)
}
}
@@ -119,12 +89,6 @@ class SettingsAdapter @Inject constructor(@ApplicationContext private val contex
notifyListeners(setting)
}
fun setCounterViewList(setting: CounterListView) {
this.counterListView = setting
updatePreference(CounterListView::class.simpleName, setting.name)
notifyListeners(setting)
}
private fun updatePreference(name: String?, value: String) {
val editor = sharedPreferences.edit()
editor.putString(name, value)
@@ -132,27 +96,21 @@ class SettingsAdapter @Inject constructor(@ApplicationContext private val contex
}
private fun notifyListeners(language: Language) {
systemListenerList.forEach {
listenerList.forEach {
it.onLanguageChanged(language)
}
}
private fun notifyListeners(theme: Theme) {
systemListenerList.forEach {
listenerList.forEach {
it.onThemeChanged(theme)
}
}
private fun notifyListeners(keepScreenOn: KeepScreenOn) {
systemListenerList.forEach {
listenerList.forEach {
it.onScreenOnChanged(keepScreenOn)
}
}
private fun notifyListeners(counterListView: CounterListView) {
displayListenerList.forEach {
it.onCounterListViewChanged(counterListView)
}
}
}

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.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -13,7 +11,7 @@ import me.zobrist.tichucounter.data.GameWithScores
import me.zobrist.tichucounter.data.RoundDao
import me.zobrist.tichucounter.data.entity.Game
import me.zobrist.tichucounter.data.entity.Round
import java.util.Date
import java.util.*
import javax.inject.Inject
class GameRepository @Inject constructor(
@@ -21,15 +19,20 @@ class GameRepository @Inject constructor(
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 {
CoroutineScope(Dispatchers.IO).launch {
gameDao.getActiveAsFlow().collect {
gameDao.getActive().collect {
if (it == null) {
newGame()
gameDao.insert(Game(true, "TeamA", "TeamB", Date(), Date()))
} else {
activeGame = it
_activeGame = it
}
}
}
@@ -37,25 +40,16 @@ class GameRepository @Inject constructor(
suspend fun newGame() {
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)
}
}
suspend fun updateActiveTeamName(nameA: String? = null, nameB: String? = null) {
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
suspend fun updateGame(game: Game) {
game.modified = Date()
withContext(Dispatchers.IO) {
gameDao.update(activeGame)
gameDao.update(game)
}
}
@@ -98,10 +92,11 @@ class GameRepository @Inject constructor(
suspend fun deleteGame(uid: Long) {
withContext(Dispatchers.IO) {
try {
val game = gameDao.getGameById(uid)
gameDao.delete(game)
val rounds = roundDao.getAllForGame(game.uid)
roundDao.delete(rounds)
gameDao.getGameById(uid).take(1).collect {
gameDao.delete(it)
val rounds = roundDao.getAllForGame(it.uid)
roundDao.delete(rounds)
}
} catch (_: NullPointerException) {
}
}
@@ -112,8 +107,9 @@ class GameRepository @Inject constructor(
try {
gameDao.getAll().take(1).collect { games ->
val gamesToDelete = games.filter { it.uid != activeGame.uid }
val roundsToDelete = roundDao.getAll().filter { it.gameId != activeGame.uid }
val activeId = games.first { it.active }.uid
val gamesToDelete = games.filter { !it.active }
val roundsToDelete = roundDao.getAll().filter { it.gameId != activeId }
gameDao.delete(gamesToDelete)
roundDao.delete(roundsToDelete)
@@ -123,15 +119,11 @@ class GameRepository @Inject constructor(
}
}
fun getActiveGameFlow(): Flow<GameWithScores> {
return gameDao.getActiveWithRounds().filter { it != null }.map { it!! }
fun getActiveGameFlow(): Flow<GameWithScores?> {
return gameDao.getActiveWithRounds()
}
fun getAllWithRoundFlow(): Flow<List<GameWithScores>> {
return gameDao.getGamesWithRounds()
}
fun getDistinctTeamNames(): Flow<List<String>> {
return gameDao.getDistinctTeamNames()
}
}

View File

@@ -34,15 +34,17 @@ class MainViewModel @Inject constructor(
gameRepository.getActiveGameFlow().collect {
activeGameHasRounds = it.rounds.isNotEmpty() == true
activeGameHasRounds = it?.rounds?.isNotEmpty() == true
isUndoActionActive = it.rounds.isNotEmpty()
if (it != null) {
isUndoActionActive = it.rounds.isNotEmpty()
if (expectedRoundCount != it.rounds.count()) {
redoRounds.clear()
if (expectedRoundCount != it.rounds.count()) {
redoRounds.clear()
}
expectedRoundCount = it.rounds.count()
}
expectedRoundCount = it.rounds.count()
}
}
}

View File

@@ -2,11 +2,7 @@ package me.zobrist.tichucounter.ui
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext

View File

@@ -2,27 +2,17 @@ package me.zobrist.tichucounter.ui.about
import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Mail
import androidx.compose.material.icons.outlined.Shop
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ShapeDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment.Companion.Top
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
@@ -34,66 +24,24 @@ import me.zobrist.tichucounter.ui.AppTheme
@Composable
fun AboutView() {
Row(Modifier.padding(20.dp)) {
Image(
modifier = Modifier
.height(80.dp)
.padding(end = 10.dp)
.align(Top),
painter = painterResource(R.drawable.app_logo),
contentDescription = null,
contentScale = ContentScale.Fit
)
val uriHandler = LocalUriHandler.current
Column(
modifier = Modifier
.padding(
top = 20.dp,
start = 20.dp,
end = 20.dp,
bottom = 40.dp
),
) {
Row {
Image(
modifier = Modifier
.padding(end = 10.dp)
.align(Top)
.size(75.dp)
.background(
color = Color.Red,
shape = ShapeDefaults.Medium
),
painter = painterResource(R.drawable.tichu_logo),
contentDescription = null,
contentScale = ContentScale.None
Column {
Text(
text = stringResource(id = R.string.app_name),
style = MaterialTheme.typography.headlineMedium
)
Column {
Text(
text = stringResource(id = R.string.app_name),
style = MaterialTheme.typography.headlineMedium
)
Text(text = "V" + BuildConfig.VERSION_NAME)
}
Text(text = "V" + BuildConfig.VERSION_NAME)
}
Button(
modifier = Modifier
.fillMaxWidth()
.padding(top = 30.dp),
onClick = { uriHandler.openUri("market://details?id=me.zobrist.tichucounter") }
) {
Icon(imageVector = Icons.Outlined.Shop, contentDescription = null)
Text(stringResource(id = R.string.play_store))
}
Button(
modifier = Modifier
.fillMaxWidth()
.padding(top = 30.dp),
onClick = { uriHandler.openUri("mailto:app@zobrist.me") }
) {
Icon(imageVector = Icons.Outlined.Mail, contentDescription = null)
Text(stringResource(id = R.string.contact_us))
}
}
}
@@ -101,7 +49,7 @@ fun AboutView() {
@Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true)
@Composable
fun AboutViewPreview() {
AppTheme {
AppTheme() {
Surface {
AboutView()
}

View File

@@ -1,100 +0,0 @@
package me.zobrist.tichucounter.ui.composables
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
fun TypeaheadTextField(
value: String,
items: List<String>,
onValueChange: (String) -> Unit,
modifier: Modifier,
colors: TextFieldColors,
textStyle: TextStyle
) {
var isFocused by remember { mutableStateOf(false) }
val focusManager = LocalFocusManager.current
ExposedDropdownMenuBox(
expanded = isFocused,
modifier = modifier,
onExpandedChange = {}
) {
var dropDownWidth by remember { mutableStateOf(0) }
TextField(
value = value,
textStyle = textStyle,
onValueChange = {
onValueChange(it)
},
singleLine = true,
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done,
),
keyboardActions = KeyboardActions(
onDone = {
focusManager.clearFocus()
}
),
modifier = Modifier
.menuAnchor()
.onFocusChanged {
isFocused = it.isFocused
}
.onSizeChanged { dropDownWidth = it.width }
.onKeyEvent { event ->
if (event.key == Key.Back || event.key == Key.Enter) {
focusManager.clearFocus()
true
} else {
false
}
},
colors = colors
)
ExposedDropdownMenu(
expanded = isFocused && items.isNotEmpty(),
modifier = Modifier
.width(with(LocalDensity.current) { dropDownWidth.toDp() }),
onDismissRequest = { }
) {
items.forEach {
DropdownMenuItem(
onClick = {
onValueChange(it)
focusManager.clearFocus()
},
text = { Text(it) },
)
}
}
}
}

View File

@@ -4,11 +4,7 @@ import android.content.res.Configuration
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.platform.LocalConfiguration
@@ -39,8 +35,6 @@ fun Landscape(viewModel: ICounterViewModel) {
TeamNamesView(
viewModel.teamNameA,
viewModel.teamNameB,
viewModel.teamNameSuggestionsA,
viewModel.teamNameSuggestionsB,
{ viewModel.updateNameA(it) },
{ viewModel.updateNameB(it) }
)
@@ -52,7 +46,6 @@ fun Landscape(viewModel: ICounterViewModel) {
RoundListView(
viewModel.roundScoreList,
viewModel.sumUpScores,
Modifier.weight(1f)
)
}
@@ -71,8 +64,6 @@ fun Portrait(viewModel: ICounterViewModel) {
TeamNamesView(
viewModel.teamNameA,
viewModel.teamNameB,
viewModel.teamNameSuggestionsA,
viewModel.teamNameSuggestionsB,
{ viewModel.updateNameA(it) },
{ viewModel.updateNameB(it) }
)
@@ -84,7 +75,6 @@ fun Portrait(viewModel: ICounterViewModel) {
RoundListView(
viewModel.roundScoreList,
viewModel.sumUpScores,
Modifier.weight(1f)
)
@@ -111,10 +101,9 @@ internal class PreviewViewModel : ICounterViewModel {
override var totalScoreB: Int = 750
override var teamNameA: String = "Team A"
override var teamNameB: String = "Team B"
override val sumUpScores: Boolean = false
override var currentScoreA: String = ""
override var currentScoreB: String = "45"
override var isValidRound: Boolean = false
override var enableSubmit: Boolean = false
override var isAFocused: Boolean = false
override var isBFocused: Boolean = false
override var requestFocusA: FocusRequester = FocusRequester()
@@ -122,10 +111,6 @@ internal class PreviewViewModel : ICounterViewModel {
override var activeValue: String = currentScoreA
override var inactiveValue: String = currentScoreB
override var keyboardHidden: Boolean = false
override val teamNameSuggestionsA: List<String> =
listOf("TeamA", "asdffd", "TeamB", "really really long Team Name that is way too long")
override val teamNameSuggestionsB: List<String> =
listOf("TeamA", "asdffd", "TeamB", "really really long Team Name that is way too long")
override fun focusLastInput() {
}
@@ -152,6 +137,9 @@ internal class PreviewViewModel : ICounterViewModel {
override fun addSub100Clicked(toAdd: Int) {
}
override fun deleteClicked() {
}
override fun updateNameA(value: String) {
}
@@ -173,7 +161,4 @@ internal class PreviewViewModel : ICounterViewModel {
override fun showKeyboard() {
}
override fun deleteState(pressed: Boolean) {
}
}

View File

@@ -7,11 +7,10 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import me.zobrist.tichucounter.data.entity.Round
import me.zobrist.tichucounter.domain.*
import me.zobrist.tichucounter.domain.Tichu
import me.zobrist.tichucounter.domain.getTotalPoints
import me.zobrist.tichucounter.repository.GameRepository
import javax.inject.Inject
@@ -21,7 +20,7 @@ interface IKeyBoardViewModel {
val currentScoreA: String
val currentScoreB: String
val isValidRound: Boolean
val enableSubmit: Boolean
val isAFocused: Boolean
val isBFocused: Boolean
val requestFocusA: FocusRequester
@@ -38,12 +37,12 @@ interface IKeyBoardViewModel {
fun digitClicked(digit: String)
fun negateClicked()
fun addSub100Clicked(toAdd: Int)
fun deleteClicked()
fun updateFocusStateA(state: Boolean)
fun updateFocusStateB(state: Boolean)
fun swapInputScores()
fun hideKeyboard()
fun showKeyboard()
fun deleteState(pressed: Boolean)
}
@@ -51,11 +50,8 @@ interface ICounterViewModel : IKeyBoardViewModel {
val roundScoreList: List<Round>
val totalScoreA: Int
val totalScoreB: Int
val sumUpScores: Boolean
val teamNameA: String
val teamNameB: String
val teamNameSuggestionsA: List<String>
val teamNameSuggestionsB: List<String>
fun updateNameA(value: String)
fun updateNameB(value: String)
@@ -63,10 +59,9 @@ interface ICounterViewModel : IKeyBoardViewModel {
@HiltViewModel
class CounterViewModel @Inject constructor(
private val gameRepository: GameRepository,
private val settingsAdapter: SettingsAdapter
private val gameRepository: GameRepository
) :
ViewModel(), ICounterViewModel, IDisplaySettingsChangeListener {
ViewModel(), ICounterViewModel {
override var roundScoreList by mutableStateOf(emptyList<Round>())
private set
@@ -80,9 +75,6 @@ class CounterViewModel @Inject constructor(
override var teamNameA by mutableStateOf("")
private set
override var sumUpScores by mutableStateOf(false)
private set
override var teamNameB by mutableStateOf("")
private set
@@ -92,7 +84,7 @@ class CounterViewModel @Inject constructor(
override var currentScoreB by mutableStateOf("")
private set
override var isValidRound by mutableStateOf(false)
override var enableSubmit by mutableStateOf(false)
private set
override var isAFocused by mutableStateOf(false)
@@ -110,12 +102,6 @@ class CounterViewModel @Inject constructor(
override var keyboardHidden by mutableStateOf(false)
private set
override var teamNameSuggestionsA by mutableStateOf(listOf<String>())
private set
override var teamNameSuggestionsB by mutableStateOf(listOf<String>())
private set
override var activeValue: String
get() {
return if (isBFocused) {
@@ -151,12 +137,6 @@ class CounterViewModel @Inject constructor(
private var lastFocused = Focused.TEAM_A
private var deletePressed = false
private var deleteJob: Job? = null
private var distinctTeamNames = listOf<String>()
init {
viewModelScope.launch {
gameRepository.getActiveGameFlow().collect {
@@ -170,26 +150,9 @@ class CounterViewModel @Inject constructor(
teamNameA = it.game.nameA
teamNameB = it.game.nameB
buildTeamNameSuggestions()
}
}
}
viewModelScope.launch {
gameRepository.getDistinctTeamNames().collect {
distinctTeamNames = it
buildTeamNameSuggestions()
}
}
settingsAdapter.registerOnChangeListener(this)
}
override fun onCleared() {
settingsAdapter.unregisterOnChangeListener(this)
}
override fun focusLastInput() {
@@ -224,7 +187,7 @@ class CounterViewModel @Inject constructor(
}
override fun updateSubmitButton() {
isValidRound = isValidTichuRound()
enableSubmit = isValidTichuRound()
}
override fun submitClicked() {
@@ -233,25 +196,13 @@ class CounterViewModel @Inject constructor(
}
currentScoreA = ""
currentScoreB = ""
isValidRound = false
enableSubmit = false
}
override fun digitClicked(digit: String) {
focusLastInput()
if (activeValue.digitCount() >= 5) {
// 5 digits is enough
return
}
val newValue = activeValue + digit
try {
activeValue = newValue.toInt().toString()
} catch (_: NumberFormatException) {
}
activeValue += digit
updateOtherScore()
updateSubmitButton()
}
@@ -284,17 +235,27 @@ class CounterViewModel @Inject constructor(
updateSubmitButton()
}
override fun deleteClicked() {
if (activeValue != "") {
activeValue = activeValue.dropLast(1)
}
updateOtherScore()
updateSubmitButton()
}
override fun updateNameA(value: String) {
teamNameA = value
viewModelScope.launch {
gameRepository.updateActiveTeamName(nameA = value)
val game = gameRepository.activeGame
game.nameA = value
gameRepository.updateGame(game)
}
}
override fun updateNameB(value: String) {
teamNameB = value
viewModelScope.launch {
gameRepository.updateActiveTeamName(nameB = value)
val game = gameRepository.activeGame
game.nameB = value
gameRepository.updateGame(game)
}
}
@@ -325,54 +286,4 @@ class CounterViewModel @Inject constructor(
override fun showKeyboard() {
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)
}
}
}
private fun buildTeamNameSuggestions() {
teamNameSuggestionsA = buildTypeaheadList(distinctTeamNames, teamNameA)
teamNameSuggestionsB = buildTypeaheadList(distinctTeamNames, teamNameB)
}
private fun buildTypeaheadList(rawList: List<String>, currentInput: String): List<String> {
var filtered = rawList.filter { it.isNotEmpty() && it != currentInput }
if (currentInput.isNotEmpty()) {
filtered = filtered.filter { it.contains(currentInput, ignoreCase = true) }
}
return filtered.sorted().sortedBy { it.length }.take(10)
}
override fun onCounterListViewChanged(listView: CounterListView) {
sumUpScores = listView == CounterListView.CONTINUOUS
}
}

View File

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

View File

@@ -21,22 +21,13 @@ import me.zobrist.tichucounter.data.entity.Round
import me.zobrist.tichucounter.ui.AppTheme
@Composable
fun RoundListView(rounds: List<Round>, sumUpRounds: Boolean = false, modifier: Modifier) {
fun RoundListView(rounds: List<Round>, modifier: Modifier) {
val lazyListState = rememberLazyListState()
val scope = rememberCoroutineScope()
var sumA = 0
var sumB = 0
LazyColumn(state = lazyListState, modifier = modifier) {
itemsIndexed(rounds) { index, item ->
if (sumUpRounds) {
sumA += item.scoreA
sumB += item.scoreB
RoundListItem(sumA, sumB, index)
} else {
RoundListItem(item.scoreA, item.scoreB, index)
}
RoundListItem(item, index)
}
scope.launch {
@@ -46,14 +37,14 @@ fun RoundListView(rounds: List<Round>, sumUpRounds: Boolean = false, modifier: M
}
@Composable
private fun RoundListItem(scoreA: Int, scoreB: Int, index: Int) {
private fun RoundListItem(round: Round, index: Int) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(all = 4.dp)
) {
Text(
text = scoreA.toString(),
text = round.scoreA.toString(),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.weight(5f),
textAlign = TextAlign.Center
@@ -65,7 +56,7 @@ private fun RoundListItem(scoreA: Int, scoreB: Int, index: Int) {
textAlign = TextAlign.Center
)
Text(
text = scoreB.toString(),
text = round.scoreB.toString(),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.weight(5f),
textAlign = TextAlign.Center
@@ -87,7 +78,7 @@ fun RoundListViewPreview() {
AppTheme {
Surface {
RoundListView(rounds, true, Modifier)
RoundListView(rounds, Modifier)
}
}
}

View File

@@ -2,53 +2,46 @@ package me.zobrist.tichucounter.ui.counter
import android.content.res.Configuration
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import me.zobrist.tichucounter.ui.AppTheme
import me.zobrist.tichucounter.ui.composables.TypeaheadTextField
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TeamNamesView(
nameA: String,
nameB: String,
nameSuggestionsA: List<String>,
nameSuggestionsB: List<String>,
updateA: (String) -> Unit,
updateB: (String) -> Unit
) {
val containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)
val color = TextFieldDefaults.colors(
focusedContainerColor = containerColor,
unfocusedContainerColor = containerColor,
disabledContainerColor = containerColor,
val color = TextFieldDefaults.textFieldColors(
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)
)
Row {
TypeaheadTextField(
TextField(
value = nameA,
items = nameSuggestionsA,
textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
onValueChange = { updateA(it) },
singleLine = true,
modifier = Modifier.weight(1f),
colors = color,
textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center)
colors = color
)
TypeaheadTextField(
TextField(
value = nameB,
items = nameSuggestionsB,
textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
onValueChange = { updateB(it) },
singleLine = true,
modifier = Modifier.weight(1f),
colors = color,
textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center)
colors = color
)
}
}
@@ -57,6 +50,6 @@ fun TeamNamesView(
@Composable
private fun TeamNamesViewPreview() {
AppTheme {
TeamNamesView("TeamA", "TeamB", listOf("Test1", "Test3"), listOf("Test3", "Test5"), {}, {})
TeamNamesView("TeamA", "TeamB", {}, {})
}
}

View File

@@ -4,11 +4,7 @@ import android.content.res.Configuration
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign

View File

@@ -2,31 +2,14 @@ package me.zobrist.tichucounter.ui.history
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.DeleteForever
import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@@ -40,8 +23,7 @@ import me.zobrist.tichucounter.data.entity.Round
import me.zobrist.tichucounter.domain.getTotalPoints
import me.zobrist.tichucounter.ui.composables.DropDownMenu
import java.text.DateFormat
import java.util.Date
import java.util.Locale
import java.util.*
@Composable

View File

@@ -16,6 +16,7 @@ import me.zobrist.tichucounter.domain.*
import me.zobrist.tichucounter.ui.AppTheme
import me.zobrist.tichucounter.ui.counter.*
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DrawerContent(
drawerItems: List<DrawerItem>,

View File

@@ -8,16 +8,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowDropDown
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment.Companion.End
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@@ -25,7 +17,6 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import me.zobrist.tichucounter.R
import me.zobrist.tichucounter.domain.CounterListView
import me.zobrist.tichucounter.domain.KeepScreenOn
import me.zobrist.tichucounter.domain.Language
import me.zobrist.tichucounter.domain.Theme
@@ -45,11 +36,6 @@ val themeMap = mapOf(
Theme.LIGHT to R.string.light
)
val counterListViewMap = mapOf(
CounterListView.BY_ROUND to R.string.by_round,
CounterListView.CONTINUOUS to R.string.continuous,
)
@Composable
fun SettingsView(viewModel: SettingsViewModel) {
@@ -57,11 +43,9 @@ fun SettingsView(viewModel: SettingsViewModel) {
viewModel.screenOn.value,
viewModel.language,
viewModel.theme,
viewModel.counterListView,
{ viewModel.updateScreenOn(it) },
{ viewModel.updateLanguage(it) },
{ viewModel.updateTheme(it) },
{ viewModel.updateCounterListView(it) })
{ viewModel.updateTheme(it) })
}
@Composable
@@ -69,11 +53,9 @@ fun SettingsView(
valueScreenOn: Boolean = true,
valueLanguage: Language = Language.ENGLISH,
valueTheme: Theme = Theme.DARK,
valueCounterListView: CounterListView = CounterListView.BY_ROUND,
updateScreenOn: (KeepScreenOn) -> Unit = {},
updateLanguage: (Language) -> Unit = {},
updateTheme: (Theme) -> Unit = {},
updateCounterListView: (CounterListView) -> Unit = {}
updateTheme: (Theme) -> Unit = {}
) {
Column {
BooleanSetting(
@@ -92,14 +74,6 @@ fun SettingsView(
themeMap,
valueTheme,
) { updateTheme(it) }
StringSetting(
stringResource(R.string.counter_mode),
counterListViewMap,
valueCounterListView,
) { updateCounterListView(it) }
}
}

View File

@@ -5,7 +5,10 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import me.zobrist.tichucounter.domain.*
import me.zobrist.tichucounter.domain.KeepScreenOn
import me.zobrist.tichucounter.domain.Language
import me.zobrist.tichucounter.domain.SettingsAdapter
import me.zobrist.tichucounter.domain.Theme
import javax.inject.Inject
@HiltViewModel
@@ -21,9 +24,6 @@ class SettingsViewModel @Inject constructor(private val settings: SettingsAdapte
var screenOn by mutableStateOf(settings.keepScreenOn)
private set
var counterListView by mutableStateOf(settings.counterListView)
private set
fun updateLanguage(language: Language) {
settings.setLanguage(language)
this.language = settings.language
@@ -39,9 +39,4 @@ class SettingsViewModel @Inject constructor(private val settings: SettingsAdapte
screenOn = settings.keepScreenOn
}
fun updateCounterListView(value: CounterListView) {
settings.setCounterViewList(value)
counterListView = settings.counterListView
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -1,54 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="50.684"
android:viewportHeight="39.77">
<group
android:scaleX="0.46444446"
android:scaleY="0.36443365"
android:translateX="13.572049"
android:translateY="12.638237">
<path
android:fillColor="#ffff00"
android:pathData="m25.885,0.371 l-25.752,0.047 0.094,9.167 8.174,0.094 -0.094,29.957 9.592,-0.094 -0.047,-29.957h8.08z"
android:strokeWidth="0.26458300000000001"
android:strokeColor="#000000"
android:strokeLineCap="butt"
android:strokeLineJoin="miter" />
<path
android:fillColor="#ffff00"
android:pathData="m50.503,39.494 l-0.047,-9.214h-10.773c-0.738,-0.009 -2.147,-0.64 -2.139,-1.95 0.009,-1.415 0.012,-16.903 0.012,-16.903 -0.001,-1.41 1.227,-2.055 1.89,-2.079l11.009,-0.142 0.094,-9.072 -11.86,0.189c-2.609,0.059 -10.536,2.804 -10.537,13.23 -0,10.079 0.218,15.921 0.218,15.921 0.223,4.386 5.741,9.979 10.125,9.99l12.006,0.03z"
android:strokeWidth="0.26458300000000001"
android:strokeColor="#000000"
android:strokeLineCap="butt"
android:strokeLineJoin="miter" />
<path
android:fillColor="#000000"
android:pathData="m8.403,10.083c0.049,-0.713 -0.112,-0.88 -0.029,-2.095 0.366,-1.278 9.609,-5.62 16.985,-2.65 0.403,0.162 0.5,-0.375 0.299,-0.467 -2.964,-2.692 -13.318,-8.128 -25.314,0.286l8.059,4.521"
android:strokeWidth="0.264583"
android:strokeColor="#000000"
android:strokeLineCap="butt"
android:strokeLineJoin="miter" />
<path
android:fillColor="#000000"
android:pathData="m13.09,39.45 l-4.651,-7.072 1.436,-0.155c1.716,-3.55 8.698,-15.304 2.46,-24.75 -0.307,-0.496 0.802,-2.297 1.512,-0.563 5.772,10.547 5.161,23.028 -0.757,32.54z"
android:strokeWidth="0.264583"
android:strokeColor="#000000"
android:strokeLineCap="butt"
android:strokeLineJoin="miter" />
<path
android:fillColor="#000000"
android:pathData="m42.812,9.194 l7.51,-4.144c-4.938,-5.973 -14.39,-7.27 -20.302,4.385 -0.2,0.447 0.901,0.888 1.106,0.649 5.387,-7.535 11.325,-3.968 11.686,-2.476 0.158,0.461 0.006,1.054 0.001,1.585z"
android:strokeWidth="0.264583"
android:strokeColor="#000000"
android:strokeLineCap="butt"
android:strokeLineJoin="miter" />
<path
android:fillColor="#000000"
android:pathData="m42.429,30.41 l7.935,4.743c-13.982,13.838 -26.059,-8.724 -21.01,-24.194 0.143,-0.438 1.306,-0.12 1.201,0.408 -1.411,6.97 1.621,22.234 11.726,20.975z"
android:strokeWidth="0.264583"
android:strokeColor="#000000"
android:strokeLineCap="butt"
android:strokeLineJoin="miter" />
</group>
</vector>

View File

@@ -1,48 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="50.684dp"
android:height="39.77dp"
android:viewportWidth="50.684"
android:viewportHeight="39.77">
<path
android:fillColor="#ffff00"
android:pathData="m25.885,0.371 l-25.752,0.047 0.094,9.167 8.174,0.094 -0.094,29.957 9.592,-0.094 -0.047,-29.957h8.08z"
android:strokeWidth="0.26458300000000001"
android:strokeColor="#000000"
android:strokeLineCap="butt"
android:strokeLineJoin="miter" />
<path
android:fillColor="#ffff00"
android:pathData="m50.503,39.494 l-0.047,-9.214h-10.773c-0.738,-0.009 -2.147,-0.64 -2.139,-1.95 0.009,-1.415 0.012,-16.903 0.012,-16.903 -0.001,-1.41 1.227,-2.055 1.89,-2.079l11.009,-0.142 0.094,-9.072 -11.86,0.189c-2.609,0.059 -10.536,2.804 -10.537,13.23 -0,10.079 0.218,15.921 0.218,15.921 0.223,4.386 5.741,9.979 10.125,9.99l12.006,0.03z"
android:strokeWidth="0.26458300000000001"
android:strokeColor="#000000"
android:strokeLineCap="butt"
android:strokeLineJoin="miter" />
<path
android:fillColor="#000000"
android:pathData="m8.403,10.083c0.049,-0.713 -0.112,-0.88 -0.029,-2.095 0.366,-1.278 9.609,-5.62 16.985,-2.65 0.403,0.162 0.5,-0.375 0.299,-0.467 -2.964,-2.692 -13.318,-8.128 -25.314,0.286l8.059,4.521"
android:strokeWidth="0.264583"
android:strokeColor="#000000"
android:strokeLineCap="butt"
android:strokeLineJoin="miter" />
<path
android:fillColor="#000000"
android:pathData="m13.09,39.45 l-4.651,-7.072 1.436,-0.155c1.716,-3.55 8.698,-15.304 2.46,-24.75 -0.307,-0.496 0.802,-2.297 1.512,-0.563 5.772,10.547 5.161,23.028 -0.757,32.54z"
android:strokeWidth="0.264583"
android:strokeColor="#000000"
android:strokeLineCap="butt"
android:strokeLineJoin="miter" />
<path
android:fillColor="#000000"
android:pathData="m42.812,9.194 l7.51,-4.144c-4.938,-5.973 -14.39,-7.27 -20.302,4.385 -0.2,0.447 0.901,0.888 1.106,0.649 5.387,-7.535 11.325,-3.968 11.686,-2.476 0.158,0.461 0.006,1.054 0.001,1.585z"
android:strokeWidth="0.264583"
android:strokeColor="#000000"
android:strokeLineCap="butt"
android:strokeLineJoin="miter" />
<path
android:fillColor="#000000"
android:pathData="m42.429,30.41 l7.935,4.743c-13.982,13.838 -26.059,-8.724 -21.01,-24.194 0.143,-0.438 1.306,-0.12 1.201,0.408 -1.411,6.97 1.621,22.234 11.726,20.975z"
android:strokeWidth="0.264583"
android:strokeColor="#000000"
android:strokeLineCap="butt"
android:strokeLineJoin="miter" />
</vector>

View File

@@ -1,6 +1,5 @@
<?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" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -1,5 +1,5 @@
<?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" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -22,7 +22,5 @@
<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>
</resources>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="primaryColor">#d50000</color>
</resources>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#DC0E00</color>
<color name="ic_launcher_background">@color/primaryColor</color>
</resources>

View File

@@ -19,16 +19,11 @@
<string name="delete_inactive_title">Delete history</string>
<string name="delete_inactive_text">You really want to delete the the history? This action can\'t be undone.</string>
<string name="cancel">Cancel</string>
<string name="ok">OK</string>
<string name="ok">Ok</string>
<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="by_round">Display Single Round</string>
<string name="continuous">Sum up rounds</string>
<string name="counter_mode">Counter Mode</string>
<string name="contact_us">Contact us</string>
<string name="play_store" translatable="false">Play Store</string>
</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

@@ -1,9 +1,7 @@
package me.zobrist.tichucounter
import me.zobrist.tichucounter.domain.Tichu
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Assert.*
import org.junit.Test
/**

View File

@@ -1,12 +1,15 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.8.21"
ext {
compose_version = '1.1.1'
}
ext.kotlin_version = "1.7.20"
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.0.2'
classpath 'com.android.tools.build:gradle:7.4.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong

View File

@@ -18,7 +18,4 @@ android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
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
kotlin.code.style=official

View File

@@ -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.0-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip