newer version
This commit is contained in:
parent
a7c9957512
commit
b5610ae7b0
19 changed files with 757 additions and 273 deletions
|
@ -4,6 +4,14 @@
|
|||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2024-08-09T09:07:46.136113340Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="LocalEmulator" identifier="path=/home/user/.android/avd/Pixel_Fold_API_34.avd" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Kotlin2JvmCompilerArguments">
|
||||
<option name="jvmTarget" value="1.8" />
|
||||
</component>
|
||||
<component name="KotlinCommonCompilerArguments">
|
||||
<option name="apiVersion" value="2.0" />
|
||||
<option name="languageVersion" value="2.0" />
|
||||
</component>
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.9.0" />
|
||||
<option name="version" value="2.0.10" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,6 +1,11 @@
|
|||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.jetbrains.kotlin.android)
|
||||
alias(libs.plugins.devtools.ksp)
|
||||
alias(libs.plugins.compose.compiler)
|
||||
|
||||
id("kotlin-kapt")
|
||||
alias(libs.plugins.hilt.android)
|
||||
}
|
||||
|
||||
android {
|
||||
|
@ -40,7 +45,7 @@ android {
|
|||
compose = true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.5.1"
|
||||
kotlinCompilerExtensionVersion = "1.5.15"
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
|
@ -50,7 +55,11 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.hilt.android)
|
||||
kapt(libs.hilt.android.compiler)
|
||||
|
||||
|
||||
ksp(libs.androidx.room.compiler)
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
|
@ -59,7 +68,8 @@ dependencies {
|
|||
implementation(libs.androidx.ui.graphics)
|
||||
implementation(libs.androidx.ui.tooling.preview)
|
||||
implementation(libs.androidx.material3)
|
||||
implementation(libs.androidx.datastore)
|
||||
implementation(libs.androidx.room.runtime)
|
||||
implementation(libs.androidx.room.ktx)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
|
@ -68,3 +78,8 @@ dependencies {
|
|||
debugImplementation(libs.androidx.ui.tooling)
|
||||
debugImplementation(libs.androidx.ui.test.manifest)
|
||||
}
|
||||
|
||||
// Allow references to generated code
|
||||
kapt {
|
||||
correctErrorTypes = true
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application
|
||||
android:name=".MyApplication"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
|
@ -12,7 +13,12 @@
|
|||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.CoinCounter"
|
||||
tools:targetApi="31">
|
||||
tools:targetApi="35">
|
||||
<activity
|
||||
android:name=".WalletActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/title_activity_wallet"
|
||||
android:theme="@style/Theme.CoinCounter" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package eu.m724.coincounter
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
@ -13,17 +15,15 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
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.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material3.Button
|
||||
|
@ -31,33 +31,42 @@ import androidx.compose.material3.ButtonDefaults
|
|||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import eu.m724.coincounter.data.entity.Wallet
|
||||
import eu.m724.coincounter.ui.theme.CoinCounterTheme
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
private lateinit var viewModel: MainViewModel
|
||||
private val viewModel: MainViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
viewModel = MainViewModel(application)
|
||||
viewModel.init()
|
||||
lifecycleScope.launch {
|
||||
viewModel.openEvent.collect {
|
||||
openWallet(it)
|
||||
}
|
||||
}
|
||||
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
|
@ -66,29 +75,43 @@ class MainActivity : ComponentActivity() {
|
|||
Box(
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
App(viewModel)
|
||||
App(
|
||||
viewModel = viewModel,
|
||||
onClick = {
|
||||
openWallet(it.id)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
viewModel.saveAll()
|
||||
private fun openWallet(walletId: Long) {
|
||||
val intent = Intent(application.applicationContext, WalletActivity::class.java)
|
||||
intent.putExtra("walletId", walletId)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun App(viewModel: MainViewModel) {
|
||||
val total by viewModel.totalBalance.collectAsState()
|
||||
val wallets by viewModel.wallets.collectAsState()
|
||||
fun App(
|
||||
viewModel: MainViewModel,
|
||||
onClick: (Wallet) -> Unit
|
||||
) {
|
||||
val total by viewModel.totalBalance.collectAsState(initial = 0)
|
||||
val wallets by viewModel.wallets.collectAsState(initial = listOf())
|
||||
|
||||
Column {
|
||||
BalanceView(total)
|
||||
WalletList(
|
||||
wallets = wallets,
|
||||
onAdd = { viewModel.addWallet(it) }
|
||||
onClick = {
|
||||
onClick(it)
|
||||
},
|
||||
onAdd = {
|
||||
viewModel.addWallet(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -117,170 +140,129 @@ fun BalanceView(balance: Int) {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AddWalletBox(
|
||||
onAdd: (String) -> Unit
|
||||
) {
|
||||
var name by remember { mutableStateOf("") }
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.CenterEnd
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp, 2.dp),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
BasicTextField(
|
||||
value = name,
|
||||
onValueChange = {
|
||||
name = it
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
singleLine = true,
|
||||
textStyle = TextStyle.Default
|
||||
)
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (name.isNotBlank()) {
|
||||
onAdd(name)
|
||||
name = ""
|
||||
}
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Add,
|
||||
contentDescription = "Add wallet"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun WalletList(
|
||||
wallets: List<Wallet>,
|
||||
onClick: (Wallet) -> Unit,
|
||||
onAdd: (String) -> Unit
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.width(500.dp)
|
||||
FlowRow(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
maxItemsInEachRow = 3
|
||||
) {
|
||||
items(wallets) { wallet ->
|
||||
WalletCard(wallet)
|
||||
wallets.forEach { wallet ->
|
||||
WalletCard(
|
||||
wallet = wallet,
|
||||
onClick = {
|
||||
onClick(wallet)
|
||||
}
|
||||
item {
|
||||
AddWalletBox(onAdd)
|
||||
)
|
||||
}
|
||||
AddWalletButton(
|
||||
onAdd = onAdd
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WalletCard(wallet: Wallet) {
|
||||
var expanded by rememberSaveable { mutableStateOf(false) }
|
||||
val balance by wallet.balance.collectAsState()
|
||||
|
||||
fun WalletCard(
|
||||
wallet: Wallet,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
expanded = !expanded
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp, 2.dp),
|
||||
modifier = Modifier.padding(8.dp),
|
||||
onClick = onClick,
|
||||
colors = ButtonDefaults.buttonColors().copy(
|
||||
containerColor = CardDefaults.cardColors().containerColor,
|
||||
contentColor = CardDefaults.cardColors().contentColor
|
||||
),
|
||||
contentPadding = PaddingValues(8.dp, 2.dp)
|
||||
shape = RoundedCornerShape(30),
|
||||
contentPadding = PaddingValues(16.dp, 4.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)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(wallet.label)
|
||||
Text(formatCurrency(wallet.balance))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun UnitButtons(
|
||||
onClick: (Int) -> Unit
|
||||
fun AddWalletButton(
|
||||
onAdd: (String) -> Unit
|
||||
) {
|
||||
val increments = listOf(1, 2, 5, 10, 20, 50, 100, 200, 500)
|
||||
var add by rememberSaveable { mutableStateOf(true) }
|
||||
var label by remember { mutableStateOf("") }
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
val angle by animateFloatAsState(
|
||||
targetValue = if (expanded && label.isBlank()) 45f else 0f,
|
||||
label = "Add button rotation"
|
||||
)
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
Row {
|
||||
TextButton(
|
||||
onClick = { add = false },
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.aspectRatio(1f),
|
||||
border = if (!add) BorderStroke(1.dp, Color.Red) else null,
|
||||
.padding(8.dp)
|
||||
.height(48.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "-",
|
||||
fontSize = 24.sp
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.width(5.dp))
|
||||
TextButton(
|
||||
onClick = { add = true },
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = expanded
|
||||
) {
|
||||
BasicTextField(
|
||||
value = label,
|
||||
onValueChange = { label = it },
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.aspectRatio(1f),
|
||||
border = if (add) BorderStroke(1.dp, Color.Green) else null,
|
||||
) {
|
||||
Text(
|
||||
text = "+",
|
||||
fontSize = 24.sp
|
||||
.padding(start = 16.dp)
|
||||
.focusRequester(focusRequester),
|
||||
textStyle = LocalTextStyle.current,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = {
|
||||
expanded = false
|
||||
onAdd(label)
|
||||
label = ""
|
||||
}
|
||||
),
|
||||
singleLine = true
|
||||
)
|
||||
LaunchedEffect(expanded) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
FlowRow {
|
||||
increments.forEach {
|
||||
TextButton(onClick = {
|
||||
onClick(if (add) it else -it)
|
||||
}) {
|
||||
Text(formatCurrency(it))
|
||||
Button(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
onClick = {
|
||||
if (!expanded) {
|
||||
expanded = true
|
||||
} else {
|
||||
if (label.isBlank()) {
|
||||
expanded = false
|
||||
} else {
|
||||
onAdd(label)
|
||||
expanded = false
|
||||
}
|
||||
label = ""
|
||||
}
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors().copy(
|
||||
containerColor = CardDefaults.cardColors().containerColor,
|
||||
contentColor = CardDefaults.cardColors().contentColor
|
||||
),
|
||||
shape = RoundedCornerShape(30),
|
||||
contentPadding = PaddingValues(16.dp, 4.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Add,
|
||||
contentDescription = "Add wallet",
|
||||
modifier = Modifier.rotate(angle)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,66 +1,34 @@
|
|||
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
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import eu.m724.coincounter.data.entity.Wallet
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class MainViewModel(
|
||||
application: Application
|
||||
) : AndroidViewModel(application) {
|
||||
private val _totalBalance: MutableStateFlow<Int> = MutableStateFlow(0)
|
||||
val totalBalance: StateFlow<Int> = _totalBalance.asStateFlow()
|
||||
@HiltViewModel
|
||||
class MainViewModel @Inject constructor(
|
||||
private val repository: WalletRepository
|
||||
) : ViewModel() {
|
||||
val wallets: Flow<List<Wallet>> = repository.getAllWallets()
|
||||
|
||||
private val _wallets: MutableStateFlow<List<Wallet>> = MutableStateFlow(listOf())
|
||||
val wallets: StateFlow<List<Wallet>> = _wallets.asStateFlow()
|
||||
private val _openEvent = MutableSharedFlow<Long>()
|
||||
val openEvent = _openEvent.asSharedFlow()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
val totalBalance: Flow<Int> = wallets.map { wallets ->
|
||||
wallets.sumOf { it.balance }
|
||||
}
|
||||
|
||||
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
|
||||
val wallet = Wallet(label = name)
|
||||
viewModelScope.launch {
|
||||
val id = repository.insertWallet(wallet)
|
||||
_openEvent.emit(id)
|
||||
}
|
||||
}
|
||||
}
|
12
app/src/main/java/eu/m724/coincounter/MyApplication.kt
Normal file
12
app/src/main/java/eu/m724/coincounter/MyApplication.kt
Normal file
|
@ -0,0 +1,12 @@
|
|||
package eu.m724.coincounter
|
||||
|
||||
import android.app.Application
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
|
||||
@HiltAndroidApp
|
||||
class MyApplication : Application() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
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)
|
||||
}
|
||||
}
|
291
app/src/main/java/eu/m724/coincounter/WalletActivity.kt
Normal file
291
app/src/main/java/eu/m724/coincounter/WalletActivity.kt
Normal file
|
@ -0,0 +1,291 @@
|
|||
package eu.m724.coincounter
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Create
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import eu.m724.coincounter.ui.theme.CoinCounterTheme
|
||||
import java.text.DateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
@AndroidEntryPoint
|
||||
class WalletActivity : ComponentActivity() {
|
||||
private val viewModel: WalletViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val walletId = intent.getLongExtra("walletId", 0)
|
||||
viewModel.init(walletId)
|
||||
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
CoinCounterTheme {
|
||||
var transactionDialogShown by rememberSaveable { mutableStateOf(false) }
|
||||
if (transactionDialogShown) {
|
||||
TransactionDialog(
|
||||
onDismiss = { transactionDialogShown = false },
|
||||
onConfirm = { label, value ->
|
||||
transactionDialogShown = false
|
||||
viewModel.transact(value, label)
|
||||
}
|
||||
)
|
||||
}
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(onClick = {
|
||||
transactionDialogShown = true
|
||||
}) {
|
||||
Icon(Icons.Filled.Create, "New transaction")
|
||||
}
|
||||
}
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
App(
|
||||
viewModel = viewModel,
|
||||
finish = { finish() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun App(
|
||||
viewModel: WalletViewModel,
|
||||
finish: () -> Unit
|
||||
) {
|
||||
val walletState by viewModel.wallet.collectAsState(initial = null)
|
||||
val wallet = walletState
|
||||
|
||||
var actionsVisible by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
if (actionsVisible) {
|
||||
ActionsDialog(
|
||||
onDismiss = { actionsVisible = false },
|
||||
onDelete = {
|
||||
viewModel.delete()
|
||||
finish()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (wallet != null) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(50.dp))
|
||||
Text(
|
||||
text = wallet.label,
|
||||
modifier = Modifier.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onLongPress = {
|
||||
actionsVisible = true
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Card {
|
||||
Row(
|
||||
modifier = Modifier.padding(16.dp, 8.dp),
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
) {
|
||||
Text(
|
||||
text = "%.2f".format(wallet.balance / 100.0),
|
||||
fontSize = 32.sp
|
||||
)
|
||||
Text(
|
||||
text = "zł",
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(50.dp))
|
||||
|
||||
TransactionList(viewModel = viewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TransactionList(viewModel: WalletViewModel) {
|
||||
val transactions by viewModel.transactions.collectAsState(initial = listOf())
|
||||
|
||||
LazyColumn {
|
||||
items(transactions) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.width(200.dp)
|
||||
.padding(8.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp, 2.dp),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(it.label ?: it.id.toString())
|
||||
|
||||
Spacer(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f))
|
||||
|
||||
Text(
|
||||
text = "%.2f".format(it.value / 100.0),
|
||||
color = if (it.value < 0) MaterialTheme.colorScheme.error else Color.Unspecified
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault())
|
||||
.format(Date(it.timestamp))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun TransactionDialog(
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: (String, Int) -> Unit
|
||||
) {
|
||||
var label by rememberSaveable { mutableStateOf("") }
|
||||
var value by rememberSaveable { mutableStateOf("0") }
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = {
|
||||
label = ""
|
||||
onDismiss()
|
||||
}
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Row {
|
||||
TextField(
|
||||
value = label,
|
||||
onValueChange = { label = it },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
supportingText = { Text("Label") },
|
||||
singleLine = true
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
TextField(
|
||||
value = value,
|
||||
onValueChange = { value = it },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
supportingText = { Text("Value") },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
singleLine = true
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
TextButton(onClick = {
|
||||
label = ""
|
||||
onDismiss()
|
||||
}) {
|
||||
Text(text = "Cancel")
|
||||
}
|
||||
TextButton(onClick = {
|
||||
onConfirm(label, (value.toFloat() * 100).toInt())
|
||||
}) {
|
||||
Text("Create transaction")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ActionsDialog(
|
||||
onDismiss: () -> Unit,
|
||||
onDelete: () -> Unit
|
||||
) {
|
||||
Dialog(
|
||||
onDismissRequest = {
|
||||
onDismiss()
|
||||
}
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
TextButton(onClick = onDelete) {
|
||||
Text("Delete wallet")
|
||||
}
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text("Cancel")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
app/src/main/java/eu/m724/coincounter/WalletRepository.kt
Normal file
37
app/src/main/java/eu/m724/coincounter/WalletRepository.kt
Normal file
|
@ -0,0 +1,37 @@
|
|||
package eu.m724.coincounter
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.m724.coincounter.data.AppDatabase
|
||||
import eu.m724.coincounter.data.dao.TransactionDao
|
||||
import eu.m724.coincounter.data.dao.WalletDao
|
||||
import eu.m724.coincounter.data.entity.Transaction
|
||||
import eu.m724.coincounter.data.entity.Wallet
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class WalletRepository @Inject constructor(
|
||||
@ApplicationContext private val applicationContext: Context
|
||||
) {
|
||||
private val database: AppDatabase = Room.databaseBuilder(
|
||||
applicationContext,
|
||||
AppDatabase::class.java,
|
||||
"database-v1"
|
||||
).build()
|
||||
private val walletDao: WalletDao = database.walletDao()
|
||||
private val transactionDao: TransactionDao = database.transactionDao()
|
||||
|
||||
fun getAllWallets(): Flow<List<Wallet>> = walletDao.getAllWallets()
|
||||
fun getWalletById(id: Long): Flow<Wallet> = walletDao.getWalletById(id)
|
||||
|
||||
suspend fun insertWallet(wallet: Wallet): Long = walletDao.insertWallet(wallet)
|
||||
fun updateWallet(wallet: Wallet) = walletDao.updateWallet(wallet)
|
||||
fun deleteWallet(wallet: Wallet) = walletDao.deleteWallet(wallet)
|
||||
|
||||
fun getWalletTransactions(walletId: Long) = transactionDao.getWalletTransactions(walletId)
|
||||
|
||||
fun insertTransaction(transaction: Transaction) = transactionDao.insertTransaction(transaction)
|
||||
}
|
53
app/src/main/java/eu/m724/coincounter/WalletViewModel.kt
Normal file
53
app/src/main/java/eu/m724/coincounter/WalletViewModel.kt
Normal file
|
@ -0,0 +1,53 @@
|
|||
package eu.m724.coincounter
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import eu.m724.coincounter.data.entity.Transaction
|
||||
import eu.m724.coincounter.data.entity.Wallet
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
@HiltViewModel
|
||||
class WalletViewModel @Inject constructor(
|
||||
private val repository: WalletRepository
|
||||
) : ViewModel() {
|
||||
private var walletId by Delegates.notNull<Long>()
|
||||
|
||||
lateinit var wallet: Flow<Wallet>
|
||||
lateinit var transactions: Flow<List<Transaction>>
|
||||
|
||||
fun init(walletId: Long) {
|
||||
this.walletId = walletId
|
||||
wallet = repository.getWalletById(walletId)
|
||||
transactions = repository.getWalletTransactions(walletId)
|
||||
}
|
||||
|
||||
fun delete() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.deleteWallet(wallet.first())
|
||||
}
|
||||
}
|
||||
|
||||
fun transact(value: Int, label: String?) {
|
||||
val transaction = Transaction(
|
||||
walletId = walletId,
|
||||
value = value,
|
||||
label = label
|
||||
)
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val wallet = wallet.first()
|
||||
wallet.let {
|
||||
repository.updateWallet(
|
||||
wallet.copy(balance = wallet.balance + value)
|
||||
)
|
||||
}
|
||||
repository.insertTransaction(transaction)
|
||||
}
|
||||
}
|
||||
}
|
14
app/src/main/java/eu/m724/coincounter/data/AppDatabase.kt
Normal file
14
app/src/main/java/eu/m724/coincounter/data/AppDatabase.kt
Normal file
|
@ -0,0 +1,14 @@
|
|||
package eu.m724.coincounter.data
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import eu.m724.coincounter.data.dao.TransactionDao
|
||||
import eu.m724.coincounter.data.dao.WalletDao
|
||||
import eu.m724.coincounter.data.entity.Transaction
|
||||
import eu.m724.coincounter.data.entity.Wallet
|
||||
|
||||
@Database(entities = [Wallet::class, Transaction::class], version = 1)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun walletDao(): WalletDao
|
||||
abstract fun transactionDao(): TransactionDao
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package eu.m724.coincounter.data.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import eu.m724.coincounter.data.entity.Transaction
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface TransactionDao {
|
||||
@Query("SELECT * FROM transactions WHERE walletId = :walletId AND timestamp > :after ORDER BY timestamp DESC LIMIT :limit")
|
||||
fun getWalletTransactions(walletId: Long, after: Long = 0, limit: Int = 10): Flow<List<Transaction>>
|
||||
|
||||
@Insert
|
||||
fun insertTransaction(transaction: Transaction)
|
||||
|
||||
@Insert
|
||||
fun insertTransactions(transactions: List<Transaction>)
|
||||
|
||||
@Delete
|
||||
fun deleteTransaction(transaction: Transaction)
|
||||
|
||||
@Insert
|
||||
fun deleteTransactions(transactions: List<Transaction>)
|
||||
}
|
36
app/src/main/java/eu/m724/coincounter/data/dao/WalletDao.kt
Normal file
36
app/src/main/java/eu/m724/coincounter/data/dao/WalletDao.kt
Normal file
|
@ -0,0 +1,36 @@
|
|||
package eu.m724.coincounter.data.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import eu.m724.coincounter.data.entity.Wallet
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface WalletDao {
|
||||
@Query("SELECT * FROM wallets")
|
||||
fun getAllWallets(): Flow<List<Wallet>>
|
||||
|
||||
@Query("SELECT * FROM wallets WHERE id = (:id)")
|
||||
fun getWalletById(id: Long): Flow<Wallet>
|
||||
|
||||
@Insert
|
||||
suspend fun insertWallet(wallet: Wallet): Long
|
||||
|
||||
@Insert
|
||||
suspend fun insertWallets(wallets: List<Wallet>): LongArray
|
||||
|
||||
@Update
|
||||
fun updateWallet(wallet: Wallet)
|
||||
|
||||
@Update
|
||||
fun updateWallets(wallets: List<Wallet>)
|
||||
|
||||
@Delete
|
||||
fun deleteWallet(wallet: Wallet)
|
||||
|
||||
@Delete
|
||||
fun deleteWallets(wallet: List<Wallet>)
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package eu.m724.coincounter.data.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
/**
|
||||
* represents adding or removing money from a wallet,
|
||||
* with optional metadata
|
||||
*/
|
||||
@Entity(
|
||||
tableName = "transactions",
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = Wallet::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["walletId"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)
|
||||
]
|
||||
)
|
||||
data class Transaction(
|
||||
/** used for internal reference */
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
val id: Long = 0,
|
||||
|
||||
/**
|
||||
* the wallet id that money was used from
|
||||
*/
|
||||
val walletId: Long,
|
||||
|
||||
/** how much cents added (positive) or removed (negative) */
|
||||
val value: Int,
|
||||
|
||||
/** label of the transaction, set by user */
|
||||
val label: String? = null,
|
||||
|
||||
/** timestamp in unix milllis when the transaction was performed */
|
||||
val timestamp: Long = System.currentTimeMillis()
|
||||
)
|
17
app/src/main/java/eu/m724/coincounter/data/entity/Wallet.kt
Normal file
17
app/src/main/java/eu/m724/coincounter/data/entity/Wallet.kt
Normal file
|
@ -0,0 +1,17 @@
|
|||
package eu.m724.coincounter.data.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "wallets")
|
||||
data class Wallet(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
val id: Long = 0,
|
||||
|
||||
/** the wallet label set by user */
|
||||
var label: String = "Wallet",
|
||||
|
||||
/** balance in cents
|
||||
* for example 123 cents = 1.23 euro or any other currency */
|
||||
var balance: Int = 0
|
||||
)
|
|
@ -1,4 +1,5 @@
|
|||
<resources>
|
||||
<string name="app_name">Coin Counter</string>
|
||||
<string name="title_activity_main">MainActivity</string>
|
||||
<string name="title_activity_wallet">WalletActivity</string>
|
||||
</resources>
|
|
@ -2,4 +2,7 @@
|
|||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.jetbrains.kotlin.android) apply false
|
||||
alias(libs.plugins.devtools.ksp) apply false
|
||||
alias(libs.plugins.compose.compiler) apply false
|
||||
alias(libs.plugins.hilt.android) apply false
|
||||
}
|
|
@ -1,18 +1,22 @@
|
|||
[versions]
|
||||
agp = "8.5.1"
|
||||
datastore = "1.1.1"
|
||||
kotlin = "1.9.0"
|
||||
coreKtx = "1.10.1"
|
||||
agp = "8.5.2"
|
||||
kotlin = "2.0.10"
|
||||
coreKtx = "1.13.1"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.1.5"
|
||||
espressoCore = "3.5.1"
|
||||
lifecycleRuntimeKtx = "2.6.1"
|
||||
activityCompose = "1.8.0"
|
||||
composeBom = "2024.04.01"
|
||||
junitVersion = "1.2.1"
|
||||
espressoCore = "3.6.1"
|
||||
lifecycleRuntimeKtx = "2.8.4"
|
||||
activityCompose = "1.9.1"
|
||||
composeBom = "2024.06.00"
|
||||
room = "2.6.1"
|
||||
ksp = "2.0.10-1.0.24"
|
||||
hilt = "2.44"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastore" }
|
||||
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
|
||||
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
|
||||
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||
|
@ -26,8 +30,13 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
|
|||
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
||||
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt"}
|
||||
hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt"}
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
devtools-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
||||
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt"}
|
||||
|
||||
|
|
Loading…
Reference in a new issue