update
lost motivation :(
This commit is contained in:
parent
7f9eccba34
commit
97f1905179
17 changed files with 651 additions and 422 deletions
|
@ -69,6 +69,28 @@
|
||||||
<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="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="caiman" />
|
||||||
|
<option name="id" value="caiman" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 9 Pro" />
|
||||||
|
<option name="screenDensity" value="360" />
|
||||||
|
<option name="screenX" value="960" />
|
||||||
|
<option name="screenY" value="2142" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="comet" />
|
||||||
|
<option name="id" value="comet" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 9 Pro Fold" />
|
||||||
|
<option name="screenDensity" value="390" />
|
||||||
|
<option name="screenX" value="2076" />
|
||||||
|
<option name="screenY" value="2152" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
<PersistentDeviceSelectionData>
|
<PersistentDeviceSelectionData>
|
||||||
<option name="api" value="29" />
|
<option name="api" value="29" />
|
||||||
<option name="brand" value="samsung" />
|
<option name="brand" value="samsung" />
|
||||||
|
@ -91,6 +113,17 @@
|
||||||
<option name="screenX" value="1440" />
|
<option name="screenX" value="1440" />
|
||||||
<option name="screenY" value="3088" />
|
<option name="screenY" value="3088" />
|
||||||
</PersistentDeviceSelectionData>
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="e1q" />
|
||||||
|
<option name="id" value="e1q" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy S24" />
|
||||||
|
<option name="screenDensity" value="480" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2340" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
<PersistentDeviceSelectionData>
|
<PersistentDeviceSelectionData>
|
||||||
<option name="api" value="33" />
|
<option name="api" value="33" />
|
||||||
<option name="brand" value="google" />
|
<option name="brand" value="google" />
|
||||||
|
@ -102,6 +135,17 @@
|
||||||
<option name="screenX" value="2208" />
|
<option name="screenX" value="2208" />
|
||||||
<option name="screenY" value="1840" />
|
<option name="screenY" value="1840" />
|
||||||
</PersistentDeviceSelectionData>
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="felix" />
|
||||||
|
<option name="id" value="felix" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel Fold" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="2208" />
|
||||||
|
<option name="screenY" value="1840" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
<PersistentDeviceSelectionData>
|
<PersistentDeviceSelectionData>
|
||||||
<option name="api" value="33" />
|
<option name="api" value="33" />
|
||||||
<option name="brand" value="google" />
|
<option name="brand" value="google" />
|
||||||
|
@ -146,6 +190,17 @@
|
||||||
<option name="screenX" value="720" />
|
<option name="screenX" value="720" />
|
||||||
<option name="screenY" value="1600" />
|
<option name="screenY" value="1600" />
|
||||||
</PersistentDeviceSelectionData>
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="komodo" />
|
||||||
|
<option name="id" value="komodo" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 9 Pro XL" />
|
||||||
|
<option name="screenDensity" value="360" />
|
||||||
|
<option name="screenX" value="1008" />
|
||||||
|
<option name="screenY" value="2244" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
<PersistentDeviceSelectionData>
|
<PersistentDeviceSelectionData>
|
||||||
<option name="api" value="33" />
|
<option name="api" value="33" />
|
||||||
<option name="brand" value="google" />
|
<option name="brand" value="google" />
|
||||||
|
@ -235,6 +290,17 @@
|
||||||
<option name="screenX" value="1600" />
|
<option name="screenX" value="1600" />
|
||||||
<option name="screenY" value="2560" />
|
<option name="screenY" value="2560" />
|
||||||
</PersistentDeviceSelectionData>
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="tokay" />
|
||||||
|
<option name="id" value="tokay" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 9" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2424" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
<PersistentDeviceSelectionData>
|
<PersistentDeviceSelectionData>
|
||||||
<option name="api" value="29" />
|
<option name="api" value="29" />
|
||||||
<option name="brand" value="samsung" />
|
<option name="brand" value="samsung" />
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
android:theme="@style/Theme.CoinCounter"
|
android:theme="@style/Theme.CoinCounter"
|
||||||
tools:targetApi="35">
|
tools:targetApi="35">
|
||||||
<activity
|
<activity
|
||||||
android:name=".WalletActivity"
|
android:name=".wallet.WalletActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="@string/title_activity_wallet"
|
android:label="@string/title_activity_wallet"
|
||||||
android:theme="@style/Theme.CoinCounter" />
|
android:theme="@style/Theme.CoinCounter" />
|
||||||
|
|
|
@ -53,8 +53,10 @@ import androidx.lifecycle.lifecycleScope
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import eu.m724.coincounter.data.entity.Wallet
|
import eu.m724.coincounter.data.entity.Wallet
|
||||||
import eu.m724.coincounter.ui.theme.CoinCounterTheme
|
import eu.m724.coincounter.ui.theme.CoinCounterTheme
|
||||||
|
import eu.m724.coincounter.wallet.WalletActivity
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
// TODO modularize
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private val viewModel: MainViewModel by viewModels()
|
private val viewModel: MainViewModel by viewModels()
|
||||||
|
|
|
@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import eu.m724.coincounter.data.entity.Wallet
|
import eu.m724.coincounter.data.entity.Wallet
|
||||||
|
import eu.m724.coincounter.wallet.WalletRepository
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
|
|
@ -5,7 +5,6 @@ import dagger.hilt.android.HiltAndroidApp
|
||||||
|
|
||||||
@HiltAndroidApp
|
@HiltAndroidApp
|
||||||
class MyApplication : Application() {
|
class MyApplication : Application() {
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -13,14 +13,14 @@ interface TransactionDao {
|
||||||
fun getWalletTransactions(walletId: Long, after: Long = 0, limit: Int = 10): Flow<List<Transaction>>
|
fun getWalletTransactions(walletId: Long, after: Long = 0, limit: Int = 10): Flow<List<Transaction>>
|
||||||
|
|
||||||
@Insert
|
@Insert
|
||||||
fun insertTransaction(transaction: Transaction)
|
suspend fun insertTransaction(transaction: Transaction)
|
||||||
|
|
||||||
@Insert
|
@Insert
|
||||||
fun insertTransactions(transactions: List<Transaction>)
|
suspend fun insertTransactions(transactions: List<Transaction>)
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
fun deleteTransaction(transaction: Transaction)
|
suspend fun deleteTransaction(transaction: Transaction)
|
||||||
|
|
||||||
@Insert
|
@Insert
|
||||||
fun deleteTransactions(transactions: List<Transaction>)
|
suspend fun deleteTransactions(transactions: List<Transaction>)
|
||||||
}
|
}
|
|
@ -23,14 +23,14 @@ interface WalletDao {
|
||||||
suspend fun insertWallets(wallets: List<Wallet>): LongArray
|
suspend fun insertWallets(wallets: List<Wallet>): LongArray
|
||||||
|
|
||||||
@Update
|
@Update
|
||||||
fun updateWallet(wallet: Wallet)
|
suspend fun updateWallet(wallet: Wallet)
|
||||||
|
|
||||||
@Update
|
@Update
|
||||||
fun updateWallets(wallets: List<Wallet>)
|
suspend fun updateWallets(wallets: List<Wallet>)
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
fun deleteWallet(wallet: Wallet)
|
suspend fun deleteWallet(wallet: Wallet)
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
fun deleteWallets(wallet: List<Wallet>)
|
suspend fun deleteWallets(wallet: List<Wallet>)
|
||||||
}
|
}
|
|
@ -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() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package eu.m724.coincounter
|
package eu.m724.coincounter.wallet
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
|
@ -28,10 +28,10 @@ class WalletRepository @Inject constructor(
|
||||||
fun getWalletById(id: Long): Flow<Wallet> = walletDao.getWalletById(id)
|
fun getWalletById(id: Long): Flow<Wallet> = walletDao.getWalletById(id)
|
||||||
|
|
||||||
suspend fun insertWallet(wallet: Wallet): Long = walletDao.insertWallet(wallet)
|
suspend fun insertWallet(wallet: Wallet): Long = walletDao.insertWallet(wallet)
|
||||||
fun updateWallet(wallet: Wallet) = walletDao.updateWallet(wallet)
|
suspend fun updateWallet(wallet: Wallet) = walletDao.updateWallet(wallet)
|
||||||
fun deleteWallet(wallet: Wallet) = walletDao.deleteWallet(wallet)
|
suspend fun deleteWallet(wallet: Wallet) = walletDao.deleteWallet(wallet)
|
||||||
|
|
||||||
fun getWalletTransactions(walletId: Long) = transactionDao.getWalletTransactions(walletId)
|
fun getWalletTransactions(walletId: Long) = transactionDao.getWalletTransactions(walletId)
|
||||||
|
|
||||||
fun insertTransaction(transaction: Transaction) = transactionDao.insertTransaction(transaction)
|
suspend fun insertTransaction(transaction: Transaction) = transactionDao.insertTransaction(transaction)
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package eu.m724.coincounter
|
package eu.m724.coincounter.wallet
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
@ -45,21 +45,24 @@ class WalletViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun transact(value: Int, label: String?) {
|
fun transact(value: Int, absolute: Boolean, label: String?) {
|
||||||
val transaction = Transaction(
|
|
||||||
walletId = walletId,
|
|
||||||
value = value,
|
|
||||||
label = label
|
|
||||||
)
|
|
||||||
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val wallet = wallet.first()
|
val wallet = wallet.first()
|
||||||
|
|
||||||
|
val transaction = Transaction(
|
||||||
|
walletId = walletId,
|
||||||
|
value = if (!absolute) value else value - wallet.balance,
|
||||||
|
label = label
|
||||||
|
)
|
||||||
|
repository.insertTransaction(transaction)
|
||||||
|
|
||||||
wallet.let {
|
wallet.let {
|
||||||
repository.updateWallet(
|
repository.updateWallet(
|
||||||
wallet.copy(balance = wallet.balance + value)
|
wallet.copy(balance = wallet.balance + transaction.value)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
repository.insertTransaction(transaction)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 = { _, _, _ -> })
|
||||||
|
}
|
|
@ -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))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
app/src/main/res/drawable/baseline_error_24.xml
Normal file
5
app/src/main/res/drawable/baseline_error_24.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
|
||||||
|
|
||||||
|
</vector>
|
Loading…
Reference in a new issue