Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • main
1 result

Target

Select target project
  • justJanne/meteroid
1 result
Select Git revision
  • main
1 result
Show changes
Showing
with 412 additions and 35 deletions
/*
* The MIT License (MIT)
*
* Copyright (c) 2013-2023 Chaosdorf e.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.chaosdorf.meteroid.ui.users
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.ListItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.unit.dp
import de.chaosdorf.mete.UserId
import de.chaosdorf.meteroid.sync.SyncHandler
@Composable
fun UserListScreen(
viewModel: UserListViewModel,
onAdd: () -> Unit = {},
onSelect: (UserId) -> Unit = {},
onBack: () -> Unit = {},
) {
val server by viewModel.account.collectAsState()
val users by viewModel.users.collectAsState()
val syncState by viewModel.syncState.collectAsState()
Scaffold(
topBar = {
TopAppBar(
title = {
Text(
server?.first?.name
?: "Meteroid"
)
},
navigationIcon = {
IconButton(onClick = { onBack() }) {
Icon(Icons.AutoMirrored.Default.ArrowBack, contentDescription = "Back")
}
},
modifier = Modifier.shadow(4.dp)
)
}
) { paddingValues ->
Column {
if (syncState == SyncHandler.State.Loading) {
LinearProgressIndicator()
}
LazyColumn(modifier = Modifier.padding(paddingValues)) {
items(users) { user ->
ListItem(
headlineContent = { Text(user.name) },
supportingContent = { Text(user.email) },
modifier = Modifier.clickable { onSelect(user.userId) }
)
}
item {
ListItem(
headlineContent = { Text("Add User") },
modifier = Modifier.clickable { onAdd() }
)
}
}
}
}
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2013-2023 Chaosdorf e.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.chaosdorf.meteroid.ui.users
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.User
import de.chaosdorf.meteroid.model.UserRepository
import de.chaosdorf.meteroid.sync.AccountProvider
import de.chaosdorf.meteroid.sync.SyncHandler
import de.chaosdorf.meteroid.sync.UserSyncHandler
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
@HiltViewModel
class UserListViewModel @Inject constructor(
accountProvider: AccountProvider,
repository: UserRepository,
syncHandler: UserSyncHandler
) : ViewModel() {
val account: StateFlow<Pair<Server, User?>?> = accountProvider.account
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val users: StateFlow<List<User>> = accountProvider.account
.flatMapLatest { account ->
account?.let { (server, _) ->
repository.getAllFlow(server.serverId)
} ?: flowOf(emptyList())
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
val syncState: StateFlow<SyncHandler.State> = syncHandler.state
}
app/src/main/res/drawable-nodpi/euro_100.png

345 KiB

app/src/main/res/drawable-nodpi/euro_1000.png

414 KiB

app/src/main/res/drawable-nodpi/euro_200.png

301 KiB

app/src/main/res/drawable-nodpi/euro_2000.png

400 KiB

app/src/main/res/drawable-nodpi/euro_50.png

426 KiB

app/src/main/res/drawable-nodpi/euro_500.png

370 KiB

app/src/main/res/drawable-nodpi/euro_5000.png

394 KiB

...@@ -5,7 +5,7 @@ import org.gradle.api.Project ...@@ -5,7 +5,7 @@ import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.configure
import util.cmd import util.cmd
import util.properties import util.properties
import java.util.* import java.util.Locale
class AndroidApplicationConvention : Plugin<Project> { class AndroidApplicationConvention : Plugin<Project> {
override fun apply(target: Project) { override fun apply(target: Project) {
...@@ -48,6 +48,8 @@ class AndroidApplicationConvention : Plugin<Project> { ...@@ -48,6 +48,8 @@ class AndroidApplicationConvention : Plugin<Project> {
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17
isCoreLibraryDesugaringEnabled = true
} }
testOptions { testOptions {
......
...@@ -2,7 +2,6 @@ import com.android.build.api.dsl.LibraryExtension ...@@ -2,7 +2,6 @@ import com.android.build.api.dsl.LibraryExtension
import org.gradle.api.Plugin import org.gradle.api.Plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.configure
import java.util.*
class AndroidLibraryConvention : Plugin<Project> { class AndroidLibraryConvention : Plugin<Project> {
override fun apply(target: Project) { override fun apply(target: Project) {
...@@ -24,6 +23,10 @@ class AndroidLibraryConvention : Plugin<Project> { ...@@ -24,6 +23,10 @@ class AndroidLibraryConvention : Plugin<Project> {
testInstrumentationRunnerArguments["disableAnalytics"] = "true" testInstrumentationRunnerArguments["disableAnalytics"] = "true"
} }
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
lint { lint {
warningsAsErrors = true warningsAsErrors = true
lintConfig = file("../lint.xml") lintConfig = file("../lint.xml")
......
...@@ -8,11 +8,13 @@ androidx-compose-material = "1.5.0-alpha04" ...@@ -8,11 +8,13 @@ androidx-compose-material = "1.5.0-alpha04"
androidx-compose-material3 = "1.2.0-alpha10" androidx-compose-material3 = "1.2.0-alpha10"
androidx-compose-runtimetracing = "1.0.0-alpha04" androidx-compose-runtimetracing = "1.0.0-alpha04"
androidx-compose-tooling = "1.6.0-alpha08" androidx-compose-tooling = "1.6.0-alpha08"
androidx-datastore = "1.0.0"
androidx-hilt = "1.0.0" androidx-hilt = "1.0.0"
androidx-navigation = "2.7.4" androidx-navigation = "2.7.5"
androidx-room = "2.6.0" androidx-room = "2.6.0"
coil = "2.4.0" coil = "2.4.0"
dagger-hilt = "2.48.1" dagger-hilt = "2.48.1"
desugar-jdk = "2.0.4"
kotlin = "1.9.10" kotlin = "1.9.10"
kotlin-ksp = "1.9.10-1.0.13" kotlin-ksp = "1.9.10-1.0.13"
kotlinxCoroutines = "1.7.1" kotlinxCoroutines = "1.7.1"
...@@ -44,6 +46,8 @@ androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-toolin ...@@ -44,6 +46,8 @@ androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-toolin
androidx-compose-ui-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview", version.ref = "androidx-compose-tooling" } androidx-compose-ui-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview", version.ref = "androidx-compose-tooling" }
androidx-compose-ui-util = { group = "androidx.compose.ui", name = "ui-util" } androidx-compose-ui-util = { group = "androidx.compose.ui", name = "ui-util" }
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidx-room" } androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidx-room" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidx-room" } androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidx-room" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidx-room" } androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidx-room" }
...@@ -72,6 +76,8 @@ kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-cor ...@@ -72,6 +76,8 @@ kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-cor
kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" } kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
desugar-jdk = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar-jdk" }
# Dependencies of the included build-logic # Dependencies of the included build-logic
android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
......
...@@ -37,6 +37,9 @@ ksp { ...@@ -37,6 +37,9 @@ ksp {
dependencies { dependencies {
implementation(libs.kotlin.stdlib) implementation(libs.kotlin.stdlib)
implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.serialization.json)
coreLibraryDesugaring(libs.desugar.jdk)
implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.coroutines.core)
testImplementation(libs.kotlinx.coroutines.test) testImplementation(libs.kotlinx.coroutines.test)
...@@ -54,7 +57,5 @@ dependencies { ...@@ -54,7 +57,5 @@ dependencies {
implementation(libs.hilt.android) implementation(libs.hilt.android)
ksp(libs.hilt.compiler) ksp(libs.hilt.compiler)
implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.serialization.json)
implementation(project(":api")) implementation(project(":api"))
} }
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 1, "version": 1,
"identityHash": "74b0d505a7abb27362a1ee0986b700e0", "identityHash": "957bb839c1c422a256c594595437ee45",
"entities": [ "entities": [
{ {
"tableName": "Drink", "tableName": "Drink",
...@@ -155,7 +155,7 @@ ...@@ -155,7 +155,7 @@
}, },
{ {
"tableName": "User", "tableName": "User",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` INTEGER NOT NULL, `drinkId` INTEGER NOT NULL, `active` INTEGER NOT NULL, `name` TEXT NOT NULL, `email` TEXT NOT NULL, `balance` REAL NOT NULL, `audit` INTEGER NOT NULL, `redirect` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`serverId`, `drinkId`), FOREIGN KEY(`serverId`) REFERENCES `Server`(`serverId`) ON UPDATE NO ACTION ON DELETE CASCADE )", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` INTEGER NOT NULL, `userId` INTEGER NOT NULL, `active` INTEGER NOT NULL, `name` TEXT NOT NULL, `email` TEXT NOT NULL, `balance` REAL NOT NULL, `audit` INTEGER NOT NULL, `redirect` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`serverId`, `userId`), FOREIGN KEY(`serverId`) REFERENCES `Server`(`serverId`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [ "fields": [
{ {
"fieldPath": "serverId", "fieldPath": "serverId",
...@@ -164,8 +164,8 @@ ...@@ -164,8 +164,8 @@
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "drinkId", "fieldPath": "userId",
"columnName": "drinkId", "columnName": "userId",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": true "notNull": true
}, },
...@@ -222,7 +222,7 @@ ...@@ -222,7 +222,7 @@
"autoGenerate": false, "autoGenerate": false,
"columnNames": [ "columnNames": [
"serverId", "serverId",
"drinkId" "userId"
] ]
}, },
"indices": [], "indices": [],
...@@ -239,12 +239,122 @@ ...@@ -239,12 +239,122 @@
] ]
} }
] ]
},
{
"tableName": "Purchase",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serverId` INTEGER NOT NULL, `purchaseId` INTEGER NOT NULL, `userId` INTEGER NOT NULL, `drinkId` INTEGER, `difference` REAL NOT NULL, `createdAt` INTEGER NOT NULL, PRIMARY KEY(`serverId`, `purchaseId`), FOREIGN KEY(`serverId`) REFERENCES `Server`(`serverId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serverId`, `userId`) REFERENCES `User`(`serverId`, `userId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serverId`, `drinkId`) REFERENCES `Drink`(`serverId`, `drinkId`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "serverId",
"columnName": "serverId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "purchaseId",
"columnName": "purchaseId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userId",
"columnName": "userId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "drinkId",
"columnName": "drinkId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "difference",
"columnName": "difference",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "createdAt",
"columnName": "createdAt",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"serverId",
"purchaseId"
]
},
"indices": [
{
"name": "index_Purchase_serverId_userId",
"unique": false,
"columnNames": [
"serverId",
"userId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Purchase_serverId_userId` ON `${TABLE_NAME}` (`serverId`, `userId`)"
},
{
"name": "index_Purchase_serverId_drinkId",
"unique": false,
"columnNames": [
"serverId",
"drinkId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Purchase_serverId_drinkId` ON `${TABLE_NAME}` (`serverId`, `drinkId`)"
}
],
"foreignKeys": [
{
"table": "Server",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serverId"
],
"referencedColumns": [
"serverId"
]
},
{
"table": "User",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"serverId",
"userId"
],
"referencedColumns": [
"serverId",
"userId"
]
},
{
"table": "Drink",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"serverId",
"drinkId"
],
"referencedColumns": [
"serverId",
"drinkId"
]
}
]
} }
], ],
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "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, '74b0d505a7abb27362a1ee0986b700e0')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '957bb839c1c422a256c594595437ee45')"
] ]
} }
} }
\ No newline at end of file
...@@ -24,12 +24,13 @@ ...@@ -24,12 +24,13 @@
package de.chaosdorf.meteroid package de.chaosdorf.meteroid
import androidx.room.AutoMigration
import androidx.room.Database import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.TypeConverters import androidx.room.TypeConverters
import de.chaosdorf.meteroid.model.Drink import de.chaosdorf.meteroid.model.Drink
import de.chaosdorf.meteroid.model.DrinkRepository import de.chaosdorf.meteroid.model.DrinkRepository
import de.chaosdorf.meteroid.model.Purchase
import de.chaosdorf.meteroid.model.PurchaseRepository
import de.chaosdorf.meteroid.model.Server import de.chaosdorf.meteroid.model.Server
import de.chaosdorf.meteroid.model.ServerRepository import de.chaosdorf.meteroid.model.ServerRepository
import de.chaosdorf.meteroid.model.User import de.chaosdorf.meteroid.model.User
...@@ -41,7 +42,8 @@ import de.chaosdorf.meteroid.util.KotlinDatetimeTypeConverter ...@@ -41,7 +42,8 @@ import de.chaosdorf.meteroid.util.KotlinDatetimeTypeConverter
entities = [ entities = [
Drink::class, Drink::class,
Server::class, Server::class,
User::class User::class,
Purchase::class
], ],
autoMigrations = [], autoMigrations = [],
) )
...@@ -50,4 +52,5 @@ abstract class MeteroidDatabase : RoomDatabase() { ...@@ -50,4 +52,5 @@ abstract class MeteroidDatabase : RoomDatabase() {
abstract fun drinks(): DrinkRepository abstract fun drinks(): DrinkRepository
abstract fun server(): ServerRepository abstract fun server(): ServerRepository
abstract fun users(): UserRepository abstract fun users(): UserRepository
abstract fun purchases(): PurchaseRepository
} }
...@@ -34,6 +34,7 @@ import de.chaosdorf.mete.DrinkId ...@@ -34,6 +34,7 @@ import de.chaosdorf.mete.DrinkId
import de.chaosdorf.mete.v1.DrinkModelV1 import de.chaosdorf.mete.v1.DrinkModelV1
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
import java.net.URI
@Entity( @Entity(
primaryKeys = ["serverId", "drinkId"], primaryKeys = ["serverId", "drinkId"],
...@@ -58,8 +59,8 @@ data class Drink( ...@@ -58,8 +59,8 @@ data class Drink(
val logoUpdatedAt: Instant val logoUpdatedAt: Instant
) { ) {
companion object { companion object {
fun fromModelV1(serverId: ServerId, value: DrinkModelV1) = Drink( fun fromModelV1(server: Server, value: DrinkModelV1) = Drink(
serverId, server.serverId,
value.id, value.id,
value.active, value.active,
value.name, value.name,
...@@ -68,7 +69,7 @@ data class Drink( ...@@ -68,7 +69,7 @@ data class Drink(
value.price, value.price,
value.createdAt, value.createdAt,
value.updatedAt, value.updatedAt,
value.logoUrl, URI.create(server.url).resolve(value.logoUrl).toString(),
value.logoFileName, value.logoFileName,
value.logoContentType, value.logoContentType,
value.logoFileSize, value.logoFileSize,
...@@ -85,10 +86,10 @@ interface DrinkRepository { ...@@ -85,10 +86,10 @@ interface DrinkRepository {
@Query("SELECT * FROM Drink WHERE serverId = :serverId AND drinkId = :drinkId LIMIT 1") @Query("SELECT * FROM Drink WHERE serverId = :serverId AND drinkId = :drinkId LIMIT 1")
fun getFlow(serverId: ServerId, drinkId: DrinkId): Flow<Drink?> fun getFlow(serverId: ServerId, drinkId: DrinkId): Flow<Drink?>
@Query("SELECT * FROM Drink WHERE serverId = :serverId") @Query("SELECT * FROM Drink WHERE serverId = :serverId ORDER BY NAME ASC")
suspend fun getAll(serverId: ServerId): List<Drink> suspend fun getAll(serverId: ServerId): List<Drink>
@Query("SELECT * FROM Drink WHERE serverId = :serverId") @Query("SELECT * FROM Drink WHERE serverId = :serverId ORDER BY NAME ASC")
fun getAllFlow(serverId: ServerId): Flow<List<Drink>> fun getAllFlow(serverId: ServerId): Flow<List<Drink>>
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
......
/*
* The MIT License (MIT)
*
* Copyright (c) 2013-2023 Chaosdorf e.V.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.chaosdorf.meteroid.model
import androidx.room.Dao
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import de.chaosdorf.mete.AuditEntryId
import de.chaosdorf.mete.DrinkId
import de.chaosdorf.mete.UserId
import de.chaosdorf.mete.v1.AuditEntryModelV1
import kotlinx.coroutines.flow.Flow
import kotlinx.datetime.Instant
@Entity(
primaryKeys = ["serverId", "purchaseId"],
foreignKeys = [
ForeignKey(Server::class, ["serverId"], ["serverId"], onDelete = ForeignKey.CASCADE),
ForeignKey(User::class, ["serverId", "userId"], ["serverId", "userId"], onDelete = ForeignKey.CASCADE),
ForeignKey(Drink::class, ["serverId", "drinkId"], ["serverId", "drinkId"], onDelete = ForeignKey.NO_ACTION)
],
indices = [
Index("serverId", "userId"),
Index("serverId", "drinkId")
]
)
data class Purchase(
val serverId: ServerId,
val purchaseId: AuditEntryId,
val userId: UserId,
val drinkId: DrinkId?,
val difference: Double,
val createdAt: Instant
) {
companion object {
fun fromModelV1(server: Server, userId: UserId, value: AuditEntryModelV1) = Purchase(
server.serverId,
value.id,
userId,
value.drink,
value.difference,
value.createdAt
)
}
}
@Dao
interface PurchaseRepository {
@Query("SELECT * FROM Purchase WHERE serverId = :serverId AND userId = :userId ORDER BY createdAt DESC")
suspend fun getAll(serverId: ServerId, userId: UserId): List<Purchase>
@Query("SELECT * FROM Purchase WHERE serverId = :serverId AND userId = :userId ORDER BY createdAt DESC")
fun getAllFlow(serverId: ServerId, userId: UserId): Flow<List<Purchase>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun save(purchase: Purchase)
@Query("DELETE FROM Purchase WHERE serverId = :serverId AND purchaseId = :purchaseId")
suspend fun delete(serverId: ServerId, purchaseId: AuditEntryId)
@Query("DELETE FROM Purchase WHERE serverId = :serverId AND userId = :userId")
suspend fun deleteAll(serverId: ServerId, userId: UserId)
@Query("DELETE FROM Purchase WHERE serverId = :serverId")
suspend fun deleteAll(serverId: ServerId)
}
...@@ -30,21 +30,20 @@ import androidx.room.ForeignKey ...@@ -30,21 +30,20 @@ import androidx.room.ForeignKey
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import de.chaosdorf.mete.DrinkId
import de.chaosdorf.mete.UserId import de.chaosdorf.mete.UserId
import de.chaosdorf.mete.v1.UserModelV1 import de.chaosdorf.mete.v1.UserModelV1
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
@Entity( @Entity(
primaryKeys = ["serverId", "drinkId"], primaryKeys = ["serverId", "userId"],
foreignKeys = [ foreignKeys = [
ForeignKey(Server::class, ["serverId"], ["serverId"], onDelete = ForeignKey.CASCADE) ForeignKey(Server::class, ["serverId"], ["serverId"], onDelete = ForeignKey.CASCADE)
] ]
) )
data class User( data class User(
val serverId: ServerId, val serverId: ServerId,
val drinkId: UserId, val userId: UserId,
val active: Boolean, val active: Boolean,
val name: String, val name: String,
val email: String, val email: String,
...@@ -55,8 +54,8 @@ data class User( ...@@ -55,8 +54,8 @@ data class User(
val updatedAt: Instant, val updatedAt: Instant,
) { ) {
companion object { companion object {
fun fromModelV1(serverId: ServerId, value: UserModelV1) = User( fun fromModelV1(server: Server, value: UserModelV1) = User(
serverId, server.serverId,
value.id, value.id,
value.active, value.active,
value.name, value.name,
...@@ -72,24 +71,24 @@ data class User( ...@@ -72,24 +71,24 @@ data class User(
@Dao @Dao
interface UserRepository { interface UserRepository {
@Query("SELECT * FROM Drink WHERE serverId = :serverId AND drinkId = :drinkId LIMIT 1") @Query("SELECT * FROM User WHERE serverId = :serverId AND userId = :userId LIMIT 1")
suspend fun get(serverId: ServerId, drinkId: DrinkId): Drink? suspend fun get(serverId: ServerId, userId: UserId): User?
@Query("SELECT * FROM Drink WHERE serverId = :serverId AND drinkId = :drinkId LIMIT 1") @Query("SELECT * FROM User WHERE serverId = :serverId AND userId = :userId LIMIT 1")
fun getFlow(serverId: ServerId, drinkId: DrinkId): Flow<Drink?> fun getFlow(serverId: ServerId, userId: UserId): Flow<User?>
@Query("SELECT * FROM Drink WHERE serverId = :serverId") @Query("SELECT * FROM User WHERE serverId = :serverId ORDER BY NAME ASC")
suspend fun getAll(serverId: ServerId): List<Drink> suspend fun getAll(serverId: ServerId): List<User>
@Query("SELECT * FROM Drink WHERE serverId = :serverId") @Query("SELECT * FROM User WHERE serverId = :serverId ORDER BY NAME ASC")
fun getAllFlow(serverId: ServerId): Flow<List<Drink>> fun getAllFlow(serverId: ServerId): Flow<List<User>>
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun save(drink: Drink) suspend fun save(user: User)
@Query("DELETE FROM Drink WHERE serverId = :serverId AND drinkId = :drinkId") @Query("DELETE FROM User WHERE serverId = :serverId AND userId = :userId")
suspend fun delete(serverId: ServerId, drinkId: DrinkId) suspend fun delete(serverId: ServerId, userId: UserId)
@Query("DELETE FROM Drink WHERE serverId = :serverId") @Query("DELETE FROM User WHERE serverId = :serverId")
suspend fun deleteAll(serverId: ServerId) suspend fun deleteAll(serverId: ServerId)
} }