added preview and some up
Android CI / Build and Test (push) Failing after 20m13s
iOS CI / Build and Test SwiftUI App (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (push) Canceled after 0s
Android CI / Build and Test (push) Failing after 20m13s
iOS CI / Build and Test SwiftUI App (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,313 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "e5e368788e85c41e4ad977ca43851521",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "transactions",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `amount` REAL NOT NULL, `date` INTEGER NOT NULL, `note` TEXT NOT NULL, `categoryName` TEXT NOT NULL, `categoryIcon` TEXT NOT NULL, `categoryEmoji` TEXT NOT NULL, `categoryColorName` TEXT NOT NULL, `isBalanceAdjustment` INTEGER NOT NULL, `kindRawValue` TEXT NOT NULL, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "amount",
|
||||
"columnName": "amount",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "date",
|
||||
"columnName": "date",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "note",
|
||||
"columnName": "note",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "categoryName",
|
||||
"columnName": "categoryName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "categoryIcon",
|
||||
"columnName": "categoryIcon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "categoryEmoji",
|
||||
"columnName": "categoryEmoji",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "categoryColorName",
|
||||
"columnName": "categoryColorName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isBalanceAdjustment",
|
||||
"columnName": "isBalanceAdjustment",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "kindRawValue",
|
||||
"columnName": "kindRawValue",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "updatedAt",
|
||||
"columnName": "updatedAt",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_transactions_updatedAt",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"updatedAt"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_transactions_updatedAt` ON `${TABLE_NAME}` (`updatedAt`)"
|
||||
},
|
||||
{
|
||||
"name": "index_transactions_date",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"date"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_transactions_date` ON `${TABLE_NAME}` (`date`)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "categories",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `icon` TEXT NOT NULL, `emoji` TEXT NOT NULL, `colorName` TEXT NOT NULL, `kindRawValue` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "emoji",
|
||||
"columnName": "emoji",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "colorName",
|
||||
"columnName": "colorName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "kindRawValue",
|
||||
"columnName": "kindRawValue",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "createdAt",
|
||||
"columnName": "createdAt",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "updatedAt",
|
||||
"columnName": "updatedAt",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_categories_updatedAt",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"updatedAt"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_categories_updatedAt` ON `${TABLE_NAME}` (`updatedAt`)"
|
||||
},
|
||||
{
|
||||
"name": "index_categories_name_kindRawValue",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"name",
|
||||
"kindRawValue"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_categories_name_kindRawValue` ON `${TABLE_NAME}` (`name`, `kindRawValue`)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "budgets",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `categoryName` TEXT NOT NULL, `categoryIcon` TEXT NOT NULL, `categoryEmoji` TEXT NOT NULL, `limitAmount` REAL NOT NULL, `monthStart` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "categoryName",
|
||||
"columnName": "categoryName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "categoryIcon",
|
||||
"columnName": "categoryIcon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "categoryEmoji",
|
||||
"columnName": "categoryEmoji",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "limitAmount",
|
||||
"columnName": "limitAmount",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "monthStart",
|
||||
"columnName": "monthStart",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "updatedAt",
|
||||
"columnName": "updatedAt",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_budgets_updatedAt",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"updatedAt"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_budgets_updatedAt` ON `${TABLE_NAME}` (`updatedAt`)"
|
||||
},
|
||||
{
|
||||
"name": "index_budgets_monthStart",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"monthStart"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_budgets_monthStart` ON `${TABLE_NAME}` (`monthStart`)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "deletion_journal",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `entity` TEXT NOT NULL, `recordId` TEXT NOT NULL, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "entity",
|
||||
"columnName": "entity",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "recordId",
|
||||
"columnName": "recordId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "updatedAt",
|
||||
"columnName": "updatedAt",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_deletion_journal_updatedAt",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"updatedAt"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_deletion_journal_updatedAt` ON `${TABLE_NAME}` (`updatedAt`)"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e5e368788e85c41e4ad977ca43851521')"
|
||||
]
|
||||
}
|
||||
}
|
||||
+6
-5
@@ -10,6 +10,7 @@ import javax.crypto.Cipher
|
||||
import javax.crypto.KeyGenerator
|
||||
import javax.crypto.SecretKey
|
||||
import javax.crypto.spec.GCMParameterSpec
|
||||
import androidx.core.content.edit
|
||||
|
||||
data class ServerSession(
|
||||
val accessToken: String,
|
||||
@@ -33,10 +34,10 @@ class SecureSessionStore(context: Context) {
|
||||
.toString().toByteArray()
|
||||
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey())
|
||||
preferences.edit()
|
||||
.putString("iv", Base64.encodeToString(cipher.iv, Base64.NO_WRAP))
|
||||
.putString("data", Base64.encodeToString(cipher.doFinal(source), Base64.NO_WRAP))
|
||||
.apply()
|
||||
preferences.edit {
|
||||
putString("iv", Base64.encodeToString(cipher.iv, Base64.NO_WRAP))
|
||||
.putString("data", Base64.encodeToString(cipher.doFinal(source), Base64.NO_WRAP))
|
||||
}
|
||||
}
|
||||
|
||||
fun load(): ServerSession? = runCatching {
|
||||
@@ -58,7 +59,7 @@ class SecureSessionStore(context: Context) {
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
preferences.edit().clear().apply()
|
||||
preferences.edit { clear() }
|
||||
}
|
||||
|
||||
private fun secretKey(): SecretKey {
|
||||
|
||||
@@ -66,7 +66,7 @@ class ServerApi(
|
||||
return try {
|
||||
parseSession(post("v1/auth/refresh", JSONObject().put("refreshToken", session.refreshToken), null))
|
||||
.also { sessions.save(it) }
|
||||
} catch (error: Throwable) {
|
||||
} catch (_: Throwable) {
|
||||
sessions.clear()
|
||||
throw ServerException("Сессия истекла. Войдите снова.")
|
||||
}
|
||||
|
||||
+19
-8
@@ -4,26 +4,27 @@ import android.content.Context
|
||||
import java.net.URI
|
||||
import java.security.MessageDigest
|
||||
import java.util.UUID
|
||||
import androidx.core.content.edit
|
||||
|
||||
class ServerPreferences(context: Context) {
|
||||
private val values = context.getSharedPreferences("balance_settings", Context.MODE_PRIVATE)
|
||||
|
||||
var serverUrl: String
|
||||
get() = values.getString("server_url", "") ?: ""
|
||||
set(value) { values.edit().putString("server_url", value).apply() }
|
||||
set(value) { values.edit { putString("server_url", value) } }
|
||||
var email: String
|
||||
get() = values.getString("server_email", "") ?: ""
|
||||
set(value) { values.edit().putString("server_email", value).apply() }
|
||||
set(value) { values.edit { putString("server_email", value) } }
|
||||
var currency: String
|
||||
get() = values.getString("currency", "RUB") ?: "RUB"
|
||||
set(value) { values.edit().putString("currency", value).apply() }
|
||||
set(value) { values.edit { putString("currency", value) } }
|
||||
var theme: String
|
||||
get() = values.getString("theme", "system") ?: "system"
|
||||
set(value) { values.edit().putString("theme", value).apply() }
|
||||
set(value) { values.edit { putString("theme", value) } }
|
||||
|
||||
val deviceId: String
|
||||
get() = values.getString("device_id", null) ?: UUID.randomUUID().toString().also {
|
||||
values.edit().putString("device_id", it).apply()
|
||||
values.edit { putString("device_id", it) }
|
||||
}
|
||||
|
||||
fun normalizeServer(input: String): String {
|
||||
@@ -45,11 +46,21 @@ class ServerPreferences(context: Context) {
|
||||
}
|
||||
|
||||
fun cursor(scope: String): Long = values.getLong("cursor_${hash(scope)}", 0).coerceAtLeast(0)
|
||||
fun setCursor(scope: String, cursor: Long) = values.edit().putLong("cursor_${hash(scope)}", cursor).apply()
|
||||
fun setCursor(scope: String, cursor: Long) = values.edit {
|
||||
putLong(
|
||||
"cursor_${hash(scope)}",
|
||||
cursor
|
||||
)
|
||||
}
|
||||
fun lastPush(scope: String): Long = values.getLong("push_${hash(scope)}", 0)
|
||||
fun setLastPush(scope: String, value: Long) = values.edit().putLong("push_${hash(scope)}", value).apply()
|
||||
fun setLastPush(scope: String, value: Long) = values.edit {
|
||||
putLong(
|
||||
"push_${hash(scope)}",
|
||||
value
|
||||
)
|
||||
}
|
||||
fun resetSync(scope: String) {
|
||||
values.edit().remove("cursor_${hash(scope)}").remove("push_${hash(scope)}").apply()
|
||||
values.edit { remove("cursor_${hash(scope)}").remove("push_${hash(scope)}") }
|
||||
}
|
||||
|
||||
private fun hash(value: String): String = MessageDigest.getInstance("SHA-256")
|
||||
|
||||
@@ -32,8 +32,8 @@ class SyncRepository(
|
||||
val status: StateFlow<SyncStatus> = mutableStatus
|
||||
val session: StateFlow<ServerSession?> = mutableSession
|
||||
|
||||
fun serverUrl(): String = preferences.serverUrl
|
||||
fun savedEmail(): String = preferences.email
|
||||
// fun serverUrl(): String = preferences.serverUrl
|
||||
// fun savedEmail(): String = preferences.email
|
||||
|
||||
suspend fun changeServer(value: String): String {
|
||||
return try {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,9 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.DirectionsRun
|
||||
import androidx.compose.material.icons.automirrored.filled.DirectionsWalk
|
||||
import androidx.compose.material.icons.automirrored.filled.MenuBook
|
||||
import androidx.compose.material.icons.filled.AccountBalanceWallet
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.AutoAwesome
|
||||
@@ -26,11 +29,9 @@ import androidx.compose.material.icons.filled.Coffee
|
||||
import androidx.compose.material.icons.filled.Construction
|
||||
import androidx.compose.material.icons.filled.CreditCard
|
||||
import androidx.compose.material.icons.filled.DirectionsCar
|
||||
import androidx.compose.material.icons.filled.DirectionsRun
|
||||
import androidx.compose.material.icons.filled.DirectionsWalk
|
||||
import androidx.compose.material.icons.filled.Eco
|
||||
import androidx.compose.material.icons.filled.Favorite
|
||||
import androidx.compose.material.icons.filled.Fastfood
|
||||
import androidx.compose.material.icons.filled.Favorite
|
||||
import androidx.compose.material.icons.filled.FitnessCenter
|
||||
import androidx.compose.material.icons.filled.Flight
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
@@ -42,7 +43,6 @@ import androidx.compose.material.icons.filled.LocalGasStation
|
||||
import androidx.compose.material.icons.filled.LocalGroceryStore
|
||||
import androidx.compose.material.icons.filled.MedicalServices
|
||||
import androidx.compose.material.icons.filled.Medication
|
||||
import androidx.compose.material.icons.filled.MenuBook
|
||||
import androidx.compose.material.icons.filled.MoreHoriz
|
||||
import androidx.compose.material.icons.filled.Movie
|
||||
import androidx.compose.material.icons.filled.MusicNote
|
||||
@@ -75,10 +75,13 @@ import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.example.balanceandroid.data.CategoryDesign
|
||||
import com.example.balanceandroid.data.CategoryTotal
|
||||
import com.example.balanceandroid.ui.theme.BalanceGreen
|
||||
import com.example.balanceandroid.ui.theme.BalanceTheme
|
||||
import java.text.NumberFormat
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
@@ -148,12 +151,12 @@ fun iconFor(name: String): ImageVector = when (name) {
|
||||
"printer.fill" -> Icons.Default.Print
|
||||
"pawprint.fill" -> Icons.Default.Pets
|
||||
"leaf.fill" -> Icons.Default.Eco
|
||||
"figure.walk" -> Icons.Default.DirectionsWalk
|
||||
"figure.run" -> Icons.Default.DirectionsRun
|
||||
"figure.walk" -> Icons.AutoMirrored.Filled.DirectionsWalk
|
||||
"figure.run" -> Icons.AutoMirrored.Filled.DirectionsRun
|
||||
"dumbbell.fill" -> Icons.Default.FitnessCenter
|
||||
"cross.case.fill", "stethoscope" -> Icons.Default.MedicalServices
|
||||
"pills.fill" -> Icons.Default.Medication
|
||||
"book.fill" -> Icons.Default.MenuBook
|
||||
"book.fill" -> Icons.AutoMirrored.Filled.MenuBook
|
||||
"school", "graduationcap.fill" -> Icons.Default.School
|
||||
"laptopcomputer" -> Icons.Default.Laptop
|
||||
"briefcase.fill" -> Icons.Default.Work
|
||||
@@ -227,3 +230,34 @@ fun periodBounds(months: Int, now: Long = System.currentTimeMillis()): Pair<Long
|
||||
val end = currentStart.plusMonths(1)
|
||||
return start.toInstant().toEpochMilli() to end.toInstant().toEpochMilli()
|
||||
}
|
||||
|
||||
@Preview(name = "Metric Card", showBackground = true)
|
||||
@Composable
|
||||
fun MetricCardPreview() {
|
||||
BalanceTheme(theme = "dark") {
|
||||
MetricCard("Доходы", "120 000 ₽", BalanceGreen)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Category Badge", showBackground = true)
|
||||
@Composable
|
||||
fun CategoryBadgePreview() {
|
||||
BalanceTheme(theme = "dark") {
|
||||
CategoryBadge(CategoryDesign("Продукты", "cart.fill", "🛒", "orange", "expense"))
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Spending Bars", showBackground = true)
|
||||
@Composable
|
||||
fun SpendingBarsPreview() {
|
||||
BalanceTheme(theme = "dark") {
|
||||
SpendingBars(
|
||||
listOf(
|
||||
CategoryTotal("Продукты", "cart.fill", "🛒", "orange", 12500.0),
|
||||
CategoryTotal("Кафе", "fork.knife", "☕", "indigo", 4200.0),
|
||||
CategoryTotal("Транспорт", "car.fill", "🚗", "purple", 3100.0),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -53,14 +54,14 @@ import java.time.format.DateTimeFormatter
|
||||
@Composable
|
||||
fun TransactionEditorDialog(
|
||||
model: MainViewModel,
|
||||
state: FinanceUiState,
|
||||
// state: FinanceUiState,
|
||||
existing: TransactionEntity?,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
var amount by remember(existing?.id) { mutableStateOf(existing?.amount?.toString().orEmpty()) }
|
||||
var kind by remember(existing?.id) { mutableStateOf(existing?.kindRawValue ?: "expense") }
|
||||
var note by remember(existing?.id) { mutableStateOf(existing?.note.orEmpty()) }
|
||||
var date by remember(existing?.id) { mutableStateOf(existing?.date ?: System.currentTimeMillis()) }
|
||||
var date by remember(existing?.id) { mutableLongStateOf(existing?.date ?: System.currentTimeMillis()) }
|
||||
val categories = model.categories(kind)
|
||||
var categoryName by remember(existing?.id) { mutableStateOf(existing?.categoryName ?: categories.first().name) }
|
||||
val selected = categories.firstOrNull { it.name == categoryName } ?: categories.first()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id("com.android.application") version "8.13.0" apply false
|
||||
id("com.android.application") version "9.2.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.3.21" apply false
|
||||
id("org.jetbrains.kotlin.plugin.compose") version "2.3.21" apply false
|
||||
id("org.jetbrains.kotlin.kapt") version "2.3.21" apply false
|
||||
|
||||
@@ -6,3 +6,13 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=1024m -Dfile.encoding=UTF-8
|
||||
kotlin.daemon.jvmargs=-Xmx1024m -XX:MaxMetaspaceSize=512m
|
||||
org.gradle.workers.max=2
|
||||
org.gradle.parallel=false
|
||||
android.defaults.buildfeatures.resvalues=true
|
||||
android.sdk.defaultTargetSdkToCompileSdkIfUnset=false
|
||||
android.enableAppCompileTimeRClass=false
|
||||
android.usesSdkInManifest.disallowed=false
|
||||
android.uniquePackageNames=false
|
||||
android.dependency.useConstraints=true
|
||||
android.r8.strictFullModeForKeepRules=false
|
||||
android.r8.optimizedResourceShrinking=false
|
||||
android.builtInKotlin=false
|
||||
android.newDsl=false
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
Reference in New Issue
Block a user