2026-06-10 15:13:28 +02:00
|
|
|
package no.naiv.meddetsamme.ui
|
|
|
|
|
|
|
|
|
|
import androidx.compose.foundation.layout.Arrangement
|
|
|
|
|
import androidx.compose.foundation.layout.Column
|
|
|
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
|
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
|
|
|
import androidx.compose.foundation.layout.padding
|
|
|
|
|
import androidx.compose.foundation.rememberScrollState
|
|
|
|
|
import androidx.compose.foundation.verticalScroll
|
|
|
|
|
import androidx.compose.material3.AlertDialog
|
|
|
|
|
import androidx.compose.material3.Button
|
|
|
|
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
|
|
|
import androidx.compose.material3.Icon
|
|
|
|
|
import androidx.compose.material3.IconButton
|
|
|
|
|
import androidx.compose.material3.MaterialTheme
|
|
|
|
|
import androidx.compose.material3.Scaffold
|
|
|
|
|
import androidx.compose.material3.Text
|
|
|
|
|
import androidx.compose.material3.TextButton
|
|
|
|
|
import androidx.compose.material3.TopAppBar
|
|
|
|
|
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.rememberCoroutineScope
|
|
|
|
|
import androidx.compose.runtime.setValue
|
|
|
|
|
import androidx.compose.ui.Modifier
|
|
|
|
|
import androidx.compose.ui.platform.LocalContext
|
|
|
|
|
import androidx.compose.ui.res.painterResource
|
Rx-expiry ongoing alert (7-day lead), configurable morning check, Felleskatalogen links
The daily digest splits in two: low stock stays an auto-cancel
notification re-posted each morning while below the per-item limit;
prescription renewal becomes a separate ONGOING notification starting
7 days before rx expiry — survives 'Clear all', swipe-dismissable on
Android 14+, returns each morning until the rx date is updated, and
cancels itself once renewed (post-or-cancel in SupplyCheckReceiver).
Check time is now configurable (Innstillinger → Varsler, M3 TimePicker,
default 10:00); same PendingIntent so re-arming replaces. 'Slå opp i
Felleskatalogen' buttons on the item editor and the med editor's
summary card open the browser — decision #8 clarified: the ban covers
Felleskatalogen as a data source, not human-facing links.
Emulator-verified: both notifications post with correct flags
(ONGOING_EVENT vs AUTO_CANCEL), auto-clear after renewal/restock,
alarm follows the configured time (10:00 → 07:30), FK button opens
Chrome.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 16:21:25 +02:00
|
|
|
import androidx.compose.ui.semantics.contentDescription
|
|
|
|
|
import androidx.compose.ui.semantics.semantics
|
2026-06-10 15:13:28 +02:00
|
|
|
import androidx.compose.ui.unit.dp
|
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
|
import no.naiv.meddetsamme.R
|
|
|
|
|
import no.naiv.meddetsamme.data.MedDatabase
|
|
|
|
|
|
|
|
|
|
@OptIn(ExperimentalMaterial3Api::class)
|
|
|
|
|
@Composable
|
|
|
|
|
fun ItemEditScreen(nav: Nav, itemId: Long?) {
|
|
|
|
|
val context = LocalContext.current
|
|
|
|
|
val db = remember { MedDatabase.get(context) }
|
|
|
|
|
val scope = rememberCoroutineScope()
|
|
|
|
|
val form = remember { ItemFormState() }
|
|
|
|
|
var confirmDelete by remember { mutableStateOf(false) }
|
|
|
|
|
var deleteBlocked by remember { mutableStateOf(false) }
|
|
|
|
|
|
|
|
|
|
LaunchedEffect(itemId) {
|
|
|
|
|
itemId?.let { id -> db.inventoryDao().getById(id)?.let(form::loadFrom) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Scaffold(
|
|
|
|
|
topBar = {
|
|
|
|
|
TopAppBar(
|
|
|
|
|
title = { Text(if (itemId == null) "Ny lagervare" else "Rediger lagervare") },
|
|
|
|
|
navigationIcon = {
|
|
|
|
|
IconButton(onClick = { nav.pop() }) {
|
|
|
|
|
Icon(painterResource(R.drawable.ic_back), contentDescription = "Tilbake")
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
) { padding ->
|
|
|
|
|
Column(
|
|
|
|
|
modifier = Modifier
|
|
|
|
|
.fillMaxSize()
|
|
|
|
|
.padding(padding)
|
|
|
|
|
.padding(horizontal = 16.dp)
|
|
|
|
|
.verticalScroll(rememberScrollState()),
|
|
|
|
|
verticalArrangement = Arrangement.spacedBy(10.dp),
|
|
|
|
|
) {
|
|
|
|
|
InventoryItemForm(form)
|
|
|
|
|
|
Rx-expiry ongoing alert (7-day lead), configurable morning check, Felleskatalogen links
The daily digest splits in two: low stock stays an auto-cancel
notification re-posted each morning while below the per-item limit;
prescription renewal becomes a separate ONGOING notification starting
7 days before rx expiry — survives 'Clear all', swipe-dismissable on
Android 14+, returns each morning until the rx date is updated, and
cancels itself once renewed (post-or-cancel in SupplyCheckReceiver).
Check time is now configurable (Innstillinger → Varsler, M3 TimePicker,
default 10:00); same PendingIntent so re-arming replaces. 'Slå opp i
Felleskatalogen' buttons on the item editor and the med editor's
summary card open the browser — decision #8 clarified: the ban covers
Felleskatalogen as a data source, not human-facing links.
Emulator-verified: both notifications post with correct flags
(ONGOING_EVENT vs AUTO_CANCEL), auto-clear after renewal/restock,
alarm follows the configured time (10:00 → 07:30), FK button opens
Chrome.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 16:21:25 +02:00
|
|
|
if (form.name.isNotBlank()) {
|
|
|
|
|
TextButton(
|
|
|
|
|
onClick = { context.startActivity(felleskatalogenIntent(form.name)) },
|
|
|
|
|
modifier = Modifier
|
|
|
|
|
.fillMaxWidth()
|
|
|
|
|
.semantics { contentDescription = "Slå opp ${form.name} i Felleskatalogen. Åpner nettleseren." },
|
|
|
|
|
) { Text("Slå opp i Felleskatalogen ↗") }
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-10 15:13:28 +02:00
|
|
|
Button(
|
|
|
|
|
onClick = {
|
|
|
|
|
val item = form.buildItem(itemId ?: 0) ?: return@Button
|
|
|
|
|
scope.launch {
|
|
|
|
|
if (itemId == null) db.inventoryDao().insert(item) else db.inventoryDao().update(item)
|
|
|
|
|
nav.pop()
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
) { Text("Lagre") }
|
|
|
|
|
|
|
|
|
|
if (itemId != null) {
|
|
|
|
|
TextButton(
|
|
|
|
|
onClick = {
|
|
|
|
|
scope.launch {
|
|
|
|
|
if (db.inventoryDao().referencingMedCount(itemId) > 0) {
|
|
|
|
|
deleteBlocked = true
|
|
|
|
|
} else {
|
|
|
|
|
confirmDelete = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
) { Text("Slett vare", color = MaterialTheme.colorScheme.error) }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (deleteBlocked) {
|
|
|
|
|
AlertDialog(
|
|
|
|
|
onDismissRequest = { deleteBlocked = false },
|
|
|
|
|
title = { Text("Varen er i bruk") },
|
|
|
|
|
text = { Text("En medisin bruker denne varen. Slett eller avslutt medisinen først.") },
|
|
|
|
|
confirmButton = { TextButton(onClick = { deleteBlocked = false }) { Text("OK") } },
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
if (confirmDelete) {
|
|
|
|
|
AlertDialog(
|
|
|
|
|
onDismissRequest = { confirmDelete = false },
|
|
|
|
|
title = { Text("Slette ${form.name.ifBlank { "varen" }}?") },
|
|
|
|
|
text = { Text("Dette kan ikke angres.") },
|
|
|
|
|
confirmButton = {
|
|
|
|
|
TextButton(onClick = {
|
|
|
|
|
confirmDelete = false
|
|
|
|
|
scope.launch {
|
|
|
|
|
itemId?.let { db.inventoryDao().delete(it) }
|
|
|
|
|
nav.pop()
|
|
|
|
|
}
|
|
|
|
|
}) { Text("Slett", color = MaterialTheme.colorScheme.error) }
|
|
|
|
|
},
|
|
|
|
|
dismissButton = { TextButton(onClick = { confirmDelete = false }) { Text("Avbryt") } },
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|