diff --git a/.idea/other.xml b/.idea/other.xml
index 4604c44..94c96f6 100644
--- a/.idea/other.xml
+++ b/.idea/other.xml
@@ -69,6 +69,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -91,6 +113,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -102,6 +135,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -146,6 +190,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -235,6 +290,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f0dd42a..54797ce 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -15,7 +15,7 @@
android:theme="@style/Theme.CoinCounter"
tools:targetApi="35">
diff --git a/app/src/main/java/eu/m724/coincounter/MainActivity.kt b/app/src/main/java/eu/m724/coincounter/MainActivity.kt
index 243fdea..4dfa6b8 100644
--- a/app/src/main/java/eu/m724/coincounter/MainActivity.kt
+++ b/app/src/main/java/eu/m724/coincounter/MainActivity.kt
@@ -53,8 +53,10 @@ import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import eu.m724.coincounter.data.entity.Wallet
import eu.m724.coincounter.ui.theme.CoinCounterTheme
+import eu.m724.coincounter.wallet.WalletActivity
import kotlinx.coroutines.launch
+// TODO modularize
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
private val viewModel: MainViewModel by viewModels()
diff --git a/app/src/main/java/eu/m724/coincounter/MainViewModel.kt b/app/src/main/java/eu/m724/coincounter/MainViewModel.kt
index eeb11ec..aed40de 100644
--- a/app/src/main/java/eu/m724/coincounter/MainViewModel.kt
+++ b/app/src/main/java/eu/m724/coincounter/MainViewModel.kt
@@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.m724.coincounter.data.entity.Wallet
+import eu.m724.coincounter.wallet.WalletRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
diff --git a/app/src/main/java/eu/m724/coincounter/MyApplication.kt b/app/src/main/java/eu/m724/coincounter/MyApplication.kt
index e2080de..9cfba01 100644
--- a/app/src/main/java/eu/m724/coincounter/MyApplication.kt
+++ b/app/src/main/java/eu/m724/coincounter/MyApplication.kt
@@ -5,7 +5,6 @@ import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class MyApplication : Application() {
-
override fun onCreate() {
super.onCreate()
}
diff --git a/app/src/main/java/eu/m724/coincounter/WalletActivity.kt b/app/src/main/java/eu/m724/coincounter/WalletActivity.kt
deleted file mode 100644
index 864fb6f..0000000
--- a/app/src/main/java/eu/m724/coincounter/WalletActivity.kt
+++ /dev/null
@@ -1,399 +0,0 @@
-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.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.ExperimentalComposeUiApi
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.focusProperties
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.text.input.ImeAction
-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) }
- var renameVisible by rememberSaveable { mutableStateOf(false) }
-
- if (actionsVisible) {
- ActionsDialog(
- onRename = {
- actionsVisible = false
- renameVisible = true
- },
- onDismiss = {
- actionsVisible = false
- },
- onDelete = {
- viewModel.delete()
- finish()
- }
- )
- }
-
- if (renameVisible) {
- RenameDialog(
- name = wallet!!.label,
- onDismiss = { renameVisible = false },
- onRename = {
- viewModel.rename(it)
- wallet.label = it
- renameVisible = false
- }
- )
- }
-
- 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 = BuildConfig.CURRENCY_UNIT,
- 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(250.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 %s".format(it.value / 100.0, BuildConfig.CURRENCY_UNIT),
- 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))
- )
- }
- }
- }
- }
-}
-
-
-@OptIn(ExperimentalComposeUiApi::class)
-@Composable
-fun TransactionDialog(
- onDismiss: () -> Unit,
- onConfirm: (String, Int) -> Unit
-) {
- var label by rememberSaveable { mutableStateOf("") }
- var value by rememberSaveable { mutableStateOf("") }
- val (firstFocus, secondFocus) = remember { FocusRequester.createRefs() }
-
- LaunchedEffect(Unit) {
- firstFocus.requestFocus()
- }
-
- 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)
- .focusRequester(firstFocus)
- .focusProperties { next = secondFocus },
- supportingText = { Text("Label") },
- keyboardOptions = KeyboardOptions(
- imeAction = ImeAction.Next
- ),
- singleLine = true
- )
- Spacer(modifier = Modifier.width(6.dp))
- TextField(
- value = value,
- onValueChange = { value = it },
- modifier = Modifier
- .fillMaxWidth()
- .weight(1f)
- .focusRequester(secondFocus),
- supportingText = { Text("Value") },
- keyboardOptions = KeyboardOptions(
- keyboardType = KeyboardType.Number,
- imeAction = ImeAction.Done
- ),
- singleLine = true
- )
- }
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.End
- ) {
- TextButton(
- onClick = {
- label = ""
- onDismiss()
- }
- ) {
- Text(text = "Cancel")
- }
- TextButton(
- onClick = {
- onConfirm(label, (value.toDouble() * 100).toInt())
- }
- ) {
- Text("Create transaction")
- }
- }
- }
- }
- }
-}
-
-@Composable
-fun RenameDialog(
- name: String,
- onDismiss: () -> Unit,
- onRename: (String) -> Unit
-) {
- var value by rememberSaveable { mutableStateOf(name) }
- val focusRequester = remember { FocusRequester() }
-
- LaunchedEffect(Unit) {
- focusRequester.requestFocus()
- }
-
- Dialog(
- onDismissRequest = {
- onDismiss()
- }
- ) {
- Card {
- Column(
- modifier = Modifier.padding(16.dp)
- ) {
- Row {
- TextField(
- value = value,
- onValueChange = { value = it },
- modifier = Modifier
- .fillMaxWidth()
- .weight(1f)
- .focusRequester(focusRequester),
- supportingText = { Text("New name") },
- singleLine = true
- )
- }
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.End
- ) {
- TextButton(
- onClick = {
- value = name
- onDismiss()
- }
- ) {
- Text(text = "Cancel")
- }
- TextButton(
- onClick = {
- onRename(value)
- }
- ) {
- Text("Rename")
- }
- }
- }
- }
- }
-}
-
-@Composable
-fun ActionsDialog(
- onRename: () -> Unit,
- onDismiss: () -> Unit,
- onDelete: () -> Unit
-) {
- Dialog(
- onDismissRequest = {
- onDismiss()
- }
- ) {
- Card(
- modifier = Modifier
- .fillMaxWidth(),
- ) {
- Column(
- modifier = Modifier.padding(16.dp)
- ) {
- TextButton(onClick = onRename) {
- Text("Rename wallet")
- }
- TextButton(onClick = onDelete) {
- Text("Delete wallet")
- }
- TextButton(onClick = onDismiss) {
- Text("Cancel")
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/eu/m724/coincounter/data/dao/TransactionDao.kt b/app/src/main/java/eu/m724/coincounter/data/dao/TransactionDao.kt
index 2855d3d..2d06cce 100644
--- a/app/src/main/java/eu/m724/coincounter/data/dao/TransactionDao.kt
+++ b/app/src/main/java/eu/m724/coincounter/data/dao/TransactionDao.kt
@@ -13,14 +13,14 @@ interface TransactionDao {
fun getWalletTransactions(walletId: Long, after: Long = 0, limit: Int = 10): Flow>
@Insert
- fun insertTransaction(transaction: Transaction)
+ suspend fun insertTransaction(transaction: Transaction)
@Insert
- fun insertTransactions(transactions: List)
+ suspend fun insertTransactions(transactions: List)
@Delete
- fun deleteTransaction(transaction: Transaction)
+ suspend fun deleteTransaction(transaction: Transaction)
@Insert
- fun deleteTransactions(transactions: List)
+ suspend fun deleteTransactions(transactions: List)
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/m724/coincounter/data/dao/WalletDao.kt b/app/src/main/java/eu/m724/coincounter/data/dao/WalletDao.kt
index 5218838..872dd8b 100644
--- a/app/src/main/java/eu/m724/coincounter/data/dao/WalletDao.kt
+++ b/app/src/main/java/eu/m724/coincounter/data/dao/WalletDao.kt
@@ -23,14 +23,14 @@ interface WalletDao {
suspend fun insertWallets(wallets: List): LongArray
@Update
- fun updateWallet(wallet: Wallet)
+ suspend fun updateWallet(wallet: Wallet)
@Update
- fun updateWallets(wallets: List)
+ suspend fun updateWallets(wallets: List)
@Delete
- fun deleteWallet(wallet: Wallet)
+ suspend fun deleteWallet(wallet: Wallet)
@Delete
- fun deleteWallets(wallet: List)
+ suspend fun deleteWallets(wallet: List)
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/m724/coincounter/wallet/WalletActivity.kt b/app/src/main/java/eu/m724/coincounter/wallet/WalletActivity.kt
new file mode 100644
index 0000000..b7cfa04
--- /dev/null
+++ b/app/src/main/java/eu/m724/coincounter/wallet/WalletActivity.kt
@@ -0,0 +1,71 @@
+package eu.m724.coincounter.wallet
+
+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.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Create
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Scaffold
+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.Modifier
+import dagger.hilt.android.AndroidEntryPoint
+import eu.m724.coincounter.ui.theme.CoinCounterTheme
+import eu.m724.coincounter.wallet.compose.TransactionDialog
+import eu.m724.coincounter.wallet.compose.WalletActivityView
+
+@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, absolute ->
+ transactionDialogShown = false
+ viewModel.transact(value, absolute, label)
+ }
+ )
+ }
+ Scaffold(
+ modifier = Modifier.fillMaxSize(),
+ floatingActionButton = {
+ FloatingActionButton(onClick = {
+ transactionDialogShown = true
+ }) {
+ Icon(Icons.Filled.Create, "New transaction")
+ }
+ }
+ ) { innerPadding ->
+ Column(
+ modifier = Modifier.padding(innerPadding)
+ ) {
+ WalletActivityView(
+ viewModel = viewModel,
+ finish = { finish() }
+ )
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/m724/coincounter/WalletRepository.kt b/app/src/main/java/eu/m724/coincounter/wallet/WalletRepository.kt
similarity index 80%
rename from app/src/main/java/eu/m724/coincounter/WalletRepository.kt
rename to app/src/main/java/eu/m724/coincounter/wallet/WalletRepository.kt
index aaf8ec2..404397e 100644
--- a/app/src/main/java/eu/m724/coincounter/WalletRepository.kt
+++ b/app/src/main/java/eu/m724/coincounter/wallet/WalletRepository.kt
@@ -1,4 +1,4 @@
-package eu.m724.coincounter
+package eu.m724.coincounter.wallet
import android.content.Context
import androidx.room.Room
@@ -28,10 +28,10 @@ class WalletRepository @Inject constructor(
fun getWalletById(id: Long): Flow = 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)
+ suspend fun updateWallet(wallet: Wallet) = walletDao.updateWallet(wallet)
+ suspend fun deleteWallet(wallet: Wallet) = walletDao.deleteWallet(wallet)
fun getWalletTransactions(walletId: Long) = transactionDao.getWalletTransactions(walletId)
- fun insertTransaction(transaction: Transaction) = transactionDao.insertTransaction(transaction)
+ suspend fun insertTransaction(transaction: Transaction) = transactionDao.insertTransaction(transaction)
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/m724/coincounter/WalletViewModel.kt b/app/src/main/java/eu/m724/coincounter/wallet/WalletViewModel.kt
similarity index 80%
rename from app/src/main/java/eu/m724/coincounter/WalletViewModel.kt
rename to app/src/main/java/eu/m724/coincounter/wallet/WalletViewModel.kt
index d691a71..d290e60 100644
--- a/app/src/main/java/eu/m724/coincounter/WalletViewModel.kt
+++ b/app/src/main/java/eu/m724/coincounter/wallet/WalletViewModel.kt
@@ -1,4 +1,4 @@
-package eu.m724.coincounter
+package eu.m724.coincounter.wallet
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@@ -45,21 +45,24 @@ class WalletViewModel @Inject constructor(
}
}
- fun transact(value: Int, label: String?) {
- val transaction = Transaction(
- walletId = walletId,
- value = value,
- label = label
- )
+ fun transact(value: Int, absolute: Boolean, label: String?) {
viewModelScope.launch(Dispatchers.IO) {
val wallet = wallet.first()
+
+ val transaction = Transaction(
+ walletId = walletId,
+ value = if (!absolute) value else value - wallet.balance,
+ label = label
+ )
+ repository.insertTransaction(transaction)
+
wallet.let {
repository.updateWallet(
- wallet.copy(balance = wallet.balance + value)
+ wallet.copy(balance = wallet.balance + transaction.value)
)
}
- repository.insertTransaction(transaction)
+
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/m724/coincounter/wallet/compose/TransactionDialog.kt b/app/src/main/java/eu/m724/coincounter/wallet/compose/TransactionDialog.kt
new file mode 100644
index 0000000..c7c4de2
--- /dev/null
+++ b/app/src/main/java/eu/m724/coincounter/wallet/compose/TransactionDialog.kt
@@ -0,0 +1,169 @@
+package eu.m724.coincounter.wallet.compose
+
+import android.widget.Toast
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+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.text.KeyboardOptions
+import androidx.compose.material3.Card
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Switch
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+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.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusProperties
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import eu.m724.coincounter.R
+
+/**
+ * A dialog that's basically transaction creator
+ *
+ * @param onDismiss called on dialog dismiss
+ * @param onConfirm on confirmation, contains transaction name and value in cents, and if the value is absolute
+ */
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+fun TransactionDialog(
+ onDismiss: () -> Unit,
+ onConfirm: (String, Int, Boolean) -> Unit
+) {
+ var label by rememberSaveable { mutableStateOf("") }
+ var value by rememberSaveable { mutableStateOf("0.00") }
+ var absoluteChecked by rememberSaveable { mutableStateOf(false) }
+ val (firstFocus, secondFocus) = remember { FocusRequester.createRefs() }
+ val context = LocalContext.current
+ var valueValid by rememberSaveable { mutableStateOf(true) }
+
+ LaunchedEffect(Unit) {
+ firstFocus.requestFocus()
+ }
+
+ Dialog(
+ onDismissRequest = {
+ label = ""
+ onDismiss()
+ }
+ ) {
+ Card(
+ modifier = Modifier
+ .fillMaxWidth(),
+ ) {
+ Column(
+ modifier = Modifier
+ .padding(16.dp)
+ .height(IntrinsicSize.Min)
+ ) {
+ TextField(
+ value = label,
+ onValueChange = { label = it },
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f)
+ .focusRequester(firstFocus)
+ .focusProperties { next = secondFocus },
+ supportingText = { Text("Label") },
+ keyboardOptions = KeyboardOptions.Default.copy(
+ imeAction = ImeAction.Next
+ ),
+ singleLine = true
+ )
+ Row {
+ TextField(
+ value = value,
+ onValueChange = {
+ value = it
+ valueValid = value.toDoubleOrNull() != null
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f)
+ .focusRequester(secondFocus),
+ supportingText = {
+ Text(
+ text = "Value",
+ color = if (!valueValid) MaterialTheme.colorScheme.error else Color.Unspecified
+ )
+ },
+ keyboardOptions = KeyboardOptions.Default.copy(
+ keyboardType = KeyboardType.Decimal,
+ imeAction = ImeAction.Done
+ ),
+ isError = !valueValid,
+ trailingIcon = {
+ if (!valueValid)
+ Icon(painterResource(id = R.drawable.baseline_error_24), "error", tint = MaterialTheme.colorScheme.error)
+ },
+ singleLine = true
+ )
+ Spacer(modifier = Modifier.width(10.dp))
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Switch(
+ checked = absoluteChecked,
+ onCheckedChange = { absoluteChecked = it }
+ )
+ Text(text = "Absolute")
+ }
+ }
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.End
+ ) {
+ TextButton(
+ onClick = {
+ label = ""
+ onDismiss()
+ }
+ ) {
+ Text(text = "Cancel")
+ }
+ TextButton(
+ onClick = {
+ val doubleValue = value.toDoubleOrNull()
+ if (doubleValue != null) {
+ onConfirm(label, (doubleValue * 100).toInt(), absoluteChecked) // TODO handle fixed point
+ } else {
+ Toast.makeText(context, "Value is not a number", Toast.LENGTH_SHORT).show()
+ }
+ }
+ ) {
+ Text("Create transaction")
+ }
+ }
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+fun TransactionDialogPreview() {
+ TransactionDialog(onDismiss = { }, onConfirm = { _, _, _ -> })
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/m724/coincounter/wallet/compose/TransactionList.kt b/app/src/main/java/eu/m724/coincounter/wallet/compose/TransactionList.kt
new file mode 100644
index 0000000..d3bb029
--- /dev/null
+++ b/app/src/main/java/eu/m724/coincounter/wallet/compose/TransactionList.kt
@@ -0,0 +1,73 @@
+package eu.m724.coincounter.wallet.compose
+
+import androidx.compose.foundation.layout.Arrangement
+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.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.Card
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import eu.m724.coincounter.BuildConfig
+import eu.m724.coincounter.wallet.WalletViewModel
+import java.text.DateFormat
+import java.util.Date
+import java.util.Locale
+
+/**
+ * A list of transactions for this wallet
+ *
+ * @param viewModel the view model
+ */
+@Composable
+fun TransactionList(
+ viewModel: WalletViewModel
+) {
+ val transactions by viewModel.transactions.collectAsState(initial = listOf())
+
+ LazyColumn {
+ items(transactions) { transaction ->
+ Card(
+ modifier = Modifier
+ .width(250.dp)
+ .padding(8.dp)
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(8.dp, 2.dp),
+ horizontalArrangement = Arrangement.Center
+ ) {
+ Text(transaction.label ?: transaction.id.toString())
+
+ Spacer(modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f))
+
+ Text(
+ text = "%.2f %s".format(transaction.value / 100.0, BuildConfig.CURRENCY_UNIT),
+ color = if (transaction.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(transaction.timestamp))
+ )
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/m724/coincounter/wallet/compose/WalletActionsDialog.kt b/app/src/main/java/eu/m724/coincounter/wallet/compose/WalletActionsDialog.kt
new file mode 100644
index 0000000..5c372d1
--- /dev/null
+++ b/app/src/main/java/eu/m724/coincounter/wallet/compose/WalletActionsDialog.kt
@@ -0,0 +1,51 @@
+package eu.m724.coincounter.wallet.compose
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Card
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+
+/**
+ * A dialog that's shown when you long click on wallet name
+ *
+ * @param onDismiss called on dismiss
+ * @param onRename called on wallet rename request
+ * @param onDelete called on wallet delete request
+ */
+@Composable
+fun ActionsDialog(
+ onDismiss: () -> Unit,
+ onRename: () -> Unit,
+ onDelete: () -> Unit
+) {
+ Dialog(
+ onDismissRequest = {
+ onDismiss()
+ }
+ ) {
+ Card(
+ modifier = Modifier
+ .fillMaxWidth(),
+ ) {
+ Column(
+ modifier = Modifier.padding(16.dp)
+ ) {
+ TextButton(onClick = onRename) {
+ Text("Rename wallet")
+ }
+ TextButton(onClick = onDelete) {
+ Text("Delete wallet")
+ }
+ TextButton(onClick = onDismiss) {
+ Text("Cancel")
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/m724/coincounter/wallet/compose/WalletActivityView.kt b/app/src/main/java/eu/m724/coincounter/wallet/compose/WalletActivityView.kt
new file mode 100644
index 0000000..35fb3be
--- /dev/null
+++ b/app/src/main/java/eu/m724/coincounter/wallet/compose/WalletActivityView.kt
@@ -0,0 +1,106 @@
+package eu.m724.coincounter.wallet.compose
+
+import androidx.compose.foundation.gestures.detectTapGestures
+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.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Card
+import androidx.compose.material3.Text
+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.input.pointer.pointerInput
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import eu.m724.coincounter.BuildConfig
+import eu.m724.coincounter.wallet.WalletViewModel
+
+@Composable
+fun WalletActivityView(
+ viewModel: WalletViewModel,
+ finish: () -> Unit
+) {
+ val walletState by viewModel.wallet.collectAsState(initial = null)
+ val wallet = walletState
+
+ var actionsVisible by rememberSaveable { mutableStateOf(false) }
+ var renameVisible by rememberSaveable { mutableStateOf(false) }
+
+ if (actionsVisible) {
+ ActionsDialog(
+ onRename = {
+ actionsVisible = false
+ renameVisible = true
+ },
+ onDismiss = {
+ actionsVisible = false
+ },
+ onDelete = {
+ viewModel.delete()
+ finish()
+ }
+ )
+ }
+
+ if (renameVisible) {
+ RenameDialog(
+ name = wallet!!.label,
+ onDismiss = { renameVisible = false },
+ onRename = {
+ viewModel.rename(it)
+ wallet.label = it
+ renameVisible = false
+ }
+ )
+ }
+
+ 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 = BuildConfig.CURRENCY_UNIT,
+ fontSize = 14.sp
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(50.dp))
+
+ TransactionList(
+ viewModel = viewModel
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/m724/coincounter/wallet/compose/WalletRenameDialog.kt b/app/src/main/java/eu/m724/coincounter/wallet/compose/WalletRenameDialog.kt
new file mode 100644
index 0000000..fe79d98
--- /dev/null
+++ b/app/src/main/java/eu/m724/coincounter/wallet/compose/WalletRenameDialog.kt
@@ -0,0 +1,82 @@
+package eu.m724.coincounter.wallet.compose
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Card
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+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.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+
+@Composable
+fun RenameDialog(
+ name: String,
+ onDismiss: () -> Unit,
+ onRename: (String) -> Unit
+) {
+ var value by rememberSaveable { mutableStateOf(name) }
+ val focusRequester = remember { FocusRequester() }
+
+ LaunchedEffect(Unit) {
+ focusRequester.requestFocus()
+ }
+
+ Dialog(
+ onDismissRequest = {
+ onDismiss()
+ }
+ ) {
+ Card {
+ Column(
+ modifier = Modifier.padding(16.dp)
+ ) {
+ Row {
+ TextField(
+ value = value,
+ onValueChange = { value = it },
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f)
+ .focusRequester(focusRequester),
+ supportingText = { Text("New name") },
+ singleLine = true
+ )
+ }
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.End
+ ) {
+ TextButton(
+ onClick = {
+ value = name
+ onDismiss()
+ }
+ ) {
+ Text(text = "Cancel")
+ }
+ TextButton(
+ onClick = {
+ onRename(value)
+ }
+ ) {
+ Text("Rename")
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/baseline_error_24.xml b/app/src/main/res/drawable/baseline_error_24.xml
new file mode 100644
index 0000000..aef9dd2
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_error_24.xml
@@ -0,0 +1,5 @@
+
+
+
+
+