package community.yuvomi.mobile import java.time.LocalDate import java.time.ZoneId import java.time.ZonedDateTime import java.time.format.DateTimeFormatter import java.util.Locale import org.json.JSONArray import org.json.JSONObject val pwaSupportedWidgets = listOf("tasks", "calendar", "shopping", "notes") /** Presentation only. Household business rules and permissions remain server-owned. */ fun pwaGreeting(displayName: String, hour: Int): String { val first = displayName.trim().split(Regex("\\s+")).firstOrNull().orEmpty() val greeting = when (hour) { in 5..11 -> "Guten Morgen" in 12..17 -> "Guten Tag" else -> "Guten Abend" } return if (first.isBlank()) greeting else "$greeting, $first" } fun pwaDueLabel(value: String, today: LocalDate = LocalDate.now()): String { if (value.isBlank()) return "" val date = runCatching { LocalDate.parse(value.take(10)) }.getOrNull() ?: return value return when (date) { today -> "Heute fällig" today.plusDays(1) -> "Morgen fällig" today.minusDays(1) -> "Gestern fällig" else -> date.format(DateTimeFormatter.ofPattern("d. MMM", Locale.GERMAN)) } } fun pwaPriority(value: String) = when (value) { "low" -> "Niedrig" "medium" -> "Mittel" "high" -> "Hoch" "urgent" -> "Dringend" else -> "" } fun pwaGroup(value: String) = value.takeUnless { it.isBlank() || it in setOf("general", "other", "misc") } ?: "Sonstiges" fun NativeState.householdNow(instant: java.time.Instant = java.time.Instant.now()): ZonedDateTime { val zone = runCatching { ZoneId.of(preferences.text("timezone_effective").ifBlank { preferences.text("timezone") }) } .getOrDefault(ZoneId.systemDefault()) return instant.atZone(zone) } fun NativeState.moduleVisible(module: String): Boolean = access(module) != "none" && listOf("disabled_modules", "hidden_modules").none { key -> val values = preferences.optJSONArray(key) ?: JSONArray() (0 until values.length()).any { values.optString(it) == module } } fun NativeState.dashboardTiles(): List { val config = preferences.rows("dashboard_widgets") return ( if (preferences.isNull("dashboard_widgets")) { pwaSupportedWidgets } else { config.filter { it.optBoolean("visible", true) }.map { it.text("id") } } ) .filter { it in pwaSupportedWidgets && moduleVisible(it) && widget(it) }.distinct() } fun pwaWidgetVisibility(preferences: JSONObject, visible: Set, editable: Set = pwaSupportedWidgets.toSet()): JSONArray { val existing = preferences.rows("dashboard_widgets") val rows = if (preferences.isNull("dashboard_widgets")) { pwaSupportedWidgets.mapIndexed { index, id -> JSONObject().put("id", id).put("visible", true).put("order", index).put("size", "1x1") } } else { existing } val result = JSONArray( rows.map { row -> JSONObject(row.toString()).apply { if (text("id") in editable && text("id") in pwaSupportedWidgets) put("visible", text("id") in visible) } }, ) val configured = rows.map { it.text("id") }.toSet() pwaSupportedWidgets.filter { it in visible && it in editable && it !in configured }.forEach { id -> result.put(JSONObject().put("id", id).put("visible", true).put("order", result.length()).put("size", "1x1")) } return result } fun NativeState.nativeTabs(): List { val order = preferences.optJSONArray("mobile_nav_order")?.let { array -> (0 until array.length()).map { array.optString(it) } }.orEmpty() val pages = (order + listOf("tasks", "kitchen", "notes")).mapNotNull { when (it) { "tasks" -> NativePage.TASKS "shopping", "kitchen" -> NativePage.SHOPPING "notes" -> NativePage.NOTES else -> null // Unimplemented modules are never fake navigation targets. } }.distinct().filter { moduleVisible(it.name.lowercase()) } return listOf(NativePage.HOME) + pages + NativePage.MORE } fun NativePage.navigationLabel() = if (this == NativePage.SHOPPING) "Küche" else title