new version
This commit is contained in:
parent
74bb305253
commit
a7c9957512
8 changed files with 362 additions and 205 deletions
|
@ -179,17 +179,6 @@
|
||||||
<option name="screenX" value="1080" />
|
<option name="screenX" value="1080" />
|
||||||
<option name="screenY" value="2400" />
|
<option name="screenY" value="2400" />
|
||||||
</PersistentDeviceSelectionData>
|
</PersistentDeviceSelectionData>
|
||||||
<PersistentDeviceSelectionData>
|
|
||||||
<option name="api" value="31" />
|
|
||||||
<option name="brand" value="samsung" />
|
|
||||||
<option name="codename" value="q2q" />
|
|
||||||
<option name="id" value="q2q" />
|
|
||||||
<option name="manufacturer" value="Samsung" />
|
|
||||||
<option name="name" value="Galaxy Z Fold3" />
|
|
||||||
<option name="screenDensity" value="420" />
|
|
||||||
<option name="screenX" value="1768" />
|
|
||||||
<option name="screenY" value="2208" />
|
|
||||||
</PersistentDeviceSelectionData>
|
|
||||||
<PersistentDeviceSelectionData>
|
<PersistentDeviceSelectionData>
|
||||||
<option name="api" value="34" />
|
<option name="api" value="34" />
|
||||||
<option name="brand" value="samsung" />
|
<option name="brand" value="samsung" />
|
||||||
|
|
|
@ -5,14 +5,14 @@ plugins {
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "eu.m724.coincounter"
|
namespace = "eu.m724.coincounter"
|
||||||
compileSdk = 34
|
compileSdk = 35
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "eu.m724.coincounter"
|
applicationId = "eu.m724.coincounter"
|
||||||
minSdk = 34
|
minSdk = 30
|
||||||
targetSdk = 34
|
targetSdk = 35
|
||||||
versionCode = 3
|
versionCode = 3
|
||||||
versionName = "1.1.1"
|
versionName = "2.0"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
@ -59,7 +59,7 @@ dependencies {
|
||||||
implementation(libs.androidx.ui.graphics)
|
implementation(libs.androidx.ui.graphics)
|
||||||
implementation(libs.androidx.ui.tooling.preview)
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
implementation(libs.androidx.material3)
|
implementation(libs.androidx.material3)
|
||||||
implementation(libs.androidx.datastore.preferences)
|
implementation(libs.androidx.datastore)
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
|
|
@ -5,18 +5,18 @@
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
android:enableOnBackInvokedCallback="true"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.CoinCounter"
|
android:theme="@style/Theme.CoinCounter"
|
||||||
android:enableOnBackInvokedCallback="true"
|
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/app_name"
|
android:label="@string/title_activity_main"
|
||||||
android:theme="@style/Theme.CoinCounter">
|
android:theme="@style/Theme.CoinCounter">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
|
@ -1,237 +1,297 @@
|
||||||
package eu.m724.coincounter
|
package eu.m724.coincounter
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonColors
|
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableIntState
|
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.asIntState
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.unit.FontScaling
|
|
||||||
import androidx.compose.ui.unit.FontScalingLinear
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.datastore.core.DataStore
|
|
||||||
import androidx.datastore.preferences.core.Preferences
|
|
||||||
import androidx.datastore.preferences.core.doublePreferencesKey
|
|
||||||
import androidx.datastore.preferences.core.edit
|
|
||||||
import androidx.datastore.preferences.core.intPreferencesKey
|
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
|
||||||
import eu.m724.coincounter.ui.theme.CoinCounterTheme
|
import eu.m724.coincounter.ui.theme.CoinCounterTheme
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
val Context.dataStore by preferencesDataStore(name = "preferences")
|
private lateinit var viewModel: MainViewModel
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val increments = listOf(1, 2, 5, 10, 20, 50, 100, 200, 500)
|
viewModel = MainViewModel(application)
|
||||||
|
viewModel.init()
|
||||||
|
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
AppView(increments, dataStore)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun AppView(increments: List<Int>, dataStore: DataStore<Preferences>) {
|
|
||||||
val BALANCE_KEY = intPreferencesKey("balance")
|
|
||||||
|
|
||||||
val balanceFlow: Flow<Int> =
|
|
||||||
dataStore.data.map { preferences ->
|
|
||||||
preferences[BALANCE_KEY] ?: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
val balance = balanceFlow.collectAsState(initial = 0).value
|
|
||||||
println("Loading balance: $balance")
|
|
||||||
val balanceState = rememberSaveable { mutableIntStateOf(0) }
|
|
||||||
balanceState.intValue = balance // this is required for some reason
|
|
||||||
|
|
||||||
CoinCounterTheme {
|
CoinCounterTheme {
|
||||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||||
Column(modifier = Modifier
|
Box(
|
||||||
.fillMaxSize()
|
modifier = Modifier.padding(innerPadding)
|
||||||
.padding(innerPadding)) {
|
) {
|
||||||
Box(modifier = Modifier
|
App(viewModel)
|
||||||
.fillMaxWidth()
|
}
|
||||||
.weight(.25f),
|
}
|
||||||
contentAlignment = Alignment.Center) {
|
|
||||||
StatusPart(balanceState)
|
|
||||||
}
|
}
|
||||||
Column(modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.weight(1f),
|
|
||||||
verticalArrangement = Arrangement.Center) {
|
|
||||||
ControlPart(increments, balanceState, dataStore)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
viewModel.saveAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun StatusPart(balanceState: MutableState<Int>) {
|
fun App(viewModel: MainViewModel) {
|
||||||
val value by balanceState.asIntState()
|
val total by viewModel.totalBalance.collectAsState()
|
||||||
Row(verticalAlignment = Alignment.Bottom) {
|
val wallets by viewModel.wallets.collectAsState()
|
||||||
|
|
||||||
|
Column {
|
||||||
|
BalanceView(total)
|
||||||
|
WalletList(
|
||||||
|
wallets = wallets,
|
||||||
|
onAdd = { viewModel.addWallet(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BalanceView(balance: Int) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(150.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.Bottom
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "%.2f".format(value / 100.0),
|
text = "%.2f".format(balance / 100.0),
|
||||||
fontSize = 48.sp
|
fontSize = 32.sp
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "zł",
|
text = "zł",
|
||||||
fontSize = 32.sp,
|
fontSize = 14.sp
|
||||||
modifier = Modifier.alpha(.7f))
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ControlPart(increments: List<Int>, balanceState: MutableIntState, dataStore: DataStore<Preferences>) {
|
fun AddWalletBox(
|
||||||
val multiplier = remember { mutableIntStateOf(1) }
|
onAdd: (String) -> Unit
|
||||||
val scope = rememberCoroutineScope()
|
) {
|
||||||
val BALANCE_KEY = intPreferencesKey("balance")
|
var name by remember { mutableStateOf("") }
|
||||||
|
|
||||||
suspend fun setBalance(value: Int) {
|
Box(
|
||||||
dataStore.edit { preferences ->
|
modifier = Modifier.fillMaxWidth(),
|
||||||
preferences[BALANCE_KEY] = value
|
contentAlignment = Alignment.CenterEnd
|
||||||
}
|
) {
|
||||||
}
|
Card(
|
||||||
|
|
||||||
Row {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxWidth()
|
||||||
.weight(0.5f),
|
.padding(16.dp, 2.dp),
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Row(
|
||||||
modifier = Modifier.clip(CircleShape).background(if (multiplier.intValue == 1) Color.Green else Color.Red)
|
|
||||||
) {
|
|
||||||
Button(
|
|
||||||
onClick = { multiplier.intValue = -1 },
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(64.dp)
|
.fillMaxWidth(),
|
||||||
.aspectRatio(1f)
|
horizontalArrangement = Arrangement.End,
|
||||||
.alpha(if (multiplier.intValue == -1) 1f else 0.7f),
|
verticalAlignment = Alignment.CenterVertically
|
||||||
contentPadding = PaddingValues(0.dp),
|
|
||||||
colors = ButtonDefaults.buttonColors(
|
|
||||||
containerColor = Color.Red,
|
|
||||||
contentColor = Color.White
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Text(text = "-", fontSize = 24.sp)
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
}
|
BasicTextField(
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
value = name,
|
||||||
Button(
|
onValueChange = {
|
||||||
onClick = { multiplier.intValue = 1 },
|
name = it
|
||||||
modifier = Modifier
|
|
||||||
.size(64.dp)
|
|
||||||
.aspectRatio(1f)
|
|
||||||
.alpha(if (multiplier.intValue == 1) 1f else 0.7f),
|
|
||||||
contentPadding = PaddingValues(0.dp),
|
|
||||||
colors = ButtonDefaults.buttonColors(
|
|
||||||
containerColor = Color.Green,
|
|
||||||
contentColor = Color.Black
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Text(text = "+", fontSize = 24.sp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxHeight()
|
|
||||||
.weight(1f),
|
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
LazyVerticalGrid(
|
|
||||||
columns = GridCells.Adaptive(minSize = 64.dp),
|
|
||||||
contentPadding = PaddingValues(16.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
|
||||||
) {
|
|
||||||
items(increments) { increment ->
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
balanceState.intValue += increment * multiplier.intValue
|
|
||||||
scope.launch {
|
|
||||||
println("Saved")
|
|
||||||
setBalance(balanceState.intValue)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.aspectRatio(1f),
|
.fillMaxWidth()
|
||||||
contentPadding = PaddingValues(0.dp),
|
.weight(1f),
|
||||||
colors = ButtonDefaults.buttonColors(containerColor = currencyColor(increment), contentColor = Color.Black),
|
singleLine = true,
|
||||||
|
textStyle = TextStyle.Default
|
||||||
|
)
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
if (name.isNotBlank()) {
|
||||||
|
onAdd(name)
|
||||||
|
name = ""
|
||||||
|
}
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
CurrencyLabel(value = increment)
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Add,
|
||||||
|
contentDescription = "Add wallet"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun currencyColor(value: Int): Color {
|
|
||||||
return if (value < 10) {
|
|
||||||
Color(244,164,96)
|
|
||||||
} else if (value < 200) {
|
|
||||||
Color(226, 226, 255)
|
|
||||||
} else
|
|
||||||
Color(240, 230, 140)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CurrencyLabel(value: Int) {
|
fun WalletList(
|
||||||
Row(verticalAlignment = Alignment.Bottom) {
|
wallets: List<Wallet>,
|
||||||
Text(text = (if (value >= 100) value / 100 else value).toString(), fontSize = 18.sp)
|
onAdd: (String) -> Unit
|
||||||
Text(text = if (value < 100) "gr" else "zł", fontSize = 10.sp, lineHeight = 4.sp)
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.width(500.dp)
|
||||||
|
) {
|
||||||
|
items(wallets) { wallet ->
|
||||||
|
WalletCard(wallet)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
AddWalletBox(onAdd)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WalletCard(wallet: Wallet) {
|
||||||
|
var expanded by rememberSaveable { mutableStateOf(false) }
|
||||||
|
val balance by wallet.balance.collectAsState()
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
expanded = !expanded
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp, 2.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors().copy(
|
||||||
|
containerColor = CardDefaults.cardColors().containerColor,
|
||||||
|
contentColor = CardDefaults.cardColors().contentColor
|
||||||
|
),
|
||||||
|
contentPadding = PaddingValues(8.dp, 2.dp)
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
AnimatedVisibility(visible = !expanded) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp, 6.dp)
|
||||||
|
) {
|
||||||
|
Text(wallet.name)
|
||||||
|
Spacer(modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f))
|
||||||
|
Text("%.2f zł".format(balance / 100.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimatedVisibility(visible = expanded) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(20.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(wallet.name)
|
||||||
|
Text(
|
||||||
|
text = "%.2f zł".format(balance / 100.0),
|
||||||
|
fontSize = 32.sp
|
||||||
|
)
|
||||||
|
UnitButtons(
|
||||||
|
onClick = {
|
||||||
|
wallet.fund(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
fun UnitButtons(
|
||||||
|
onClick: (Int) -> Unit
|
||||||
|
) {
|
||||||
|
val increments = listOf(1, 2, 5, 10, 20, 50, 100, 200, 500)
|
||||||
|
var add by rememberSaveable { mutableStateOf(true) }
|
||||||
|
|
||||||
|
Row {
|
||||||
|
TextButton(
|
||||||
|
onClick = { add = false },
|
||||||
|
modifier = Modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.aspectRatio(1f),
|
||||||
|
border = if (!add) BorderStroke(1.dp, Color.Red) else null,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "-",
|
||||||
|
fontSize = 24.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.width(5.dp))
|
||||||
|
TextButton(
|
||||||
|
onClick = { add = true },
|
||||||
|
modifier = Modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.aspectRatio(1f),
|
||||||
|
border = if (add) BorderStroke(1.dp, Color.Green) else null,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "+",
|
||||||
|
fontSize = 24.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FlowRow {
|
||||||
|
increments.forEach {
|
||||||
|
TextButton(onClick = {
|
||||||
|
onClick(if (add) it else -it)
|
||||||
|
}) {
|
||||||
|
Text(formatCurrency(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun formatCurrency(units: Int): String {
|
||||||
|
if (units < 100) {
|
||||||
|
return "$units gr"
|
||||||
|
}
|
||||||
|
if (units % 100 == 0) {
|
||||||
|
return "%d zł".format(units / 100)
|
||||||
|
}
|
||||||
|
return "%.2f zł".format(units / 100.0)
|
||||||
|
}
|
66
app/src/main/java/eu/m724/coincounter/MainViewModel.kt
Normal file
66
app/src/main/java/eu/m724/coincounter/MainViewModel.kt
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package eu.m724.coincounter
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
|
class MainViewModel(
|
||||||
|
application: Application
|
||||||
|
) : AndroidViewModel(application) {
|
||||||
|
private val _totalBalance: MutableStateFlow<Int> = MutableStateFlow(0)
|
||||||
|
val totalBalance: StateFlow<Int> = _totalBalance.asStateFlow()
|
||||||
|
|
||||||
|
private val _wallets: MutableStateFlow<List<Wallet>> = MutableStateFlow(listOf())
|
||||||
|
val wallets: StateFlow<List<Wallet>> = _wallets.asStateFlow()
|
||||||
|
|
||||||
|
private val dataPreferences = application.getSharedPreferences("data", Application.MODE_PRIVATE)
|
||||||
|
private val walletsPreferences = application.getSharedPreferences("wallets", Application.MODE_PRIVATE)
|
||||||
|
|
||||||
|
private var highestId = 0
|
||||||
|
|
||||||
|
fun init() {
|
||||||
|
val savedWallets = dataPreferences.getStringSet("walletIds", null)
|
||||||
|
|
||||||
|
if (savedWallets != null) {
|
||||||
|
val savedWalletIds = savedWallets.map { it.toInt() }.sorted()
|
||||||
|
val w = arrayListOf<Wallet>()
|
||||||
|
|
||||||
|
savedWalletIds.forEach { id ->
|
||||||
|
val wallet = Wallet.fromPreferences(id, walletsPreferences) { wa, mo -> onWalletUpdate(wa, mo) }
|
||||||
|
w.add(wallet)
|
||||||
|
_totalBalance.value += wallet.balance.value
|
||||||
|
if (wallet.id > highestId)
|
||||||
|
highestId = wallet.id
|
||||||
|
}
|
||||||
|
|
||||||
|
_wallets.value = w
|
||||||
|
} else {
|
||||||
|
_wallets.value = listOf(Wallet.default { wa, mo -> onWalletUpdate(wa, mo) })
|
||||||
|
_totalBalance.value = 2000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveAll() {
|
||||||
|
dataPreferences.edit(true) {
|
||||||
|
putStringSet("walletIds", wallets.value.map { it.id.toString() }.toSet())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
walletsPreferences.edit(true) {
|
||||||
|
wallets.value.forEach {
|
||||||
|
it.toPreferences(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addWallet(name: String) {
|
||||||
|
_wallets.value += Wallet(++highestId, name, 0) { wa, mo -> onWalletUpdate(wa, mo) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onWalletUpdate(wallet: Wallet, money: Int) {
|
||||||
|
_totalBalance.value += money
|
||||||
|
}
|
||||||
|
}
|
41
app/src/main/java/eu/m724/coincounter/Wallet.kt
Normal file
41
app/src/main/java/eu/m724/coincounter/Wallet.kt
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package eu.m724.coincounter
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
|
class Wallet(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
|
balance: Int,
|
||||||
|
val onUpdate: (Wallet, Int) -> Unit
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun default(onUpdate: (Wallet, Int) -> Unit): Wallet {
|
||||||
|
return Wallet(0, "Primary", 2000, onUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fromPreferences(id: Int, sharedPreferences: SharedPreferences, onUpdate: (Wallet, Int) -> Unit): Wallet {
|
||||||
|
return Wallet(
|
||||||
|
id,
|
||||||
|
sharedPreferences.getString("${id}_name", "Wallet")!!,
|
||||||
|
sharedPreferences.getInt("${id}_balance", 0),
|
||||||
|
onUpdate
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toPreferences(editor: SharedPreferences.Editor) {
|
||||||
|
editor.putString("${id}_name", name)
|
||||||
|
editor.putInt("${id}_balance", balance.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _balance: MutableStateFlow<Int> = MutableStateFlow(balance)
|
||||||
|
val balance: StateFlow<Int> = _balance.asStateFlow()
|
||||||
|
|
||||||
|
fun fund(money: Int) {
|
||||||
|
_balance.value += money
|
||||||
|
onUpdate(this, money)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Coin Counter</string>
|
<string name="app_name">Coin Counter</string>
|
||||||
|
<string name="title_activity_main">MainActivity</string>
|
||||||
</resources>
|
</resources>
|
|
@ -1,6 +1,6 @@
|
||||||
[versions]
|
[versions]
|
||||||
agp = "8.5.1"
|
agp = "8.5.1"
|
||||||
datastorePreferences = "1.1.1"
|
datastore = "1.1.1"
|
||||||
kotlin = "1.9.0"
|
kotlin = "1.9.0"
|
||||||
coreKtx = "1.10.1"
|
coreKtx = "1.10.1"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
|
@ -12,7 +12,7 @@ composeBom = "2024.04.01"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
|
androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastore" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||||
|
|
Loading…
Reference in a new issue