Hoofdscherm: donker thema + wekkerkaarten met grote tijden (stijl systeemwekker), lege state met instructie (les uit Alarmio)

This commit is contained in:
eddy 2026-10-14 20:26:41 +02:00
commit d7ce319704
6 changed files with 212 additions and 120 deletions

View file

@ -5,39 +5,33 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import android.widget.PopupMenu
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.google.android.material.button.MaterialButton
import kotlinx.coroutines.launch
import nl.eddydevink.buschauffeurswekker.core.AlarmScheduler
import nl.eddydevink.buschauffeurswekker.core.Prefs
import nl.eddydevink.buschauffeurswekker.core.ShiftCheck
import nl.eddydevink.buschauffeurswekker.core.ShiftPlanner
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Locale
/**
* Hoofdscherm: klok + de geplande wekkers van morgen. Instellingen zitten in
* [SettingsActivity]; de testwekker is alleen bereikbaar via launch-intent
* (--ez test true), niet via de productie-UI.
* Hoofdscherm in de stijl van de systeem-wekker-app: donker thema, één kaart
* per wekker met grote tijd, dag-label en naam. Acties zitten achter het ⋮-menu.
*/
class MainActivity : AppCompatActivity() {
private lateinit var prefs: Prefs
private val handler = Handler(Looper.getMainLooper())
private val clockTick = object : Runnable {
override fun run() {
updateClock()
handler.postDelayed(this, 1000)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -46,10 +40,7 @@ class MainActivity : AppCompatActivity() {
prefs = Prefs(this)
requestNotificationPermission()
findViewById<MaterialButton>(R.id.btn_check).setOnClickListener { runCheck() }
findViewById<MaterialButton>(R.id.btn_settings).setOnClickListener {
startActivity(Intent(this, SettingsActivity::class.java))
}
findViewById<TextView>(R.id.tv_menu).setOnClickListener { showMenu(it) }
AlarmScheduler.scheduleNoonCheck(this)
@ -92,56 +83,78 @@ class MainActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
renderStatus()
updateClock()
handler.removeCallbacks(clockTick)
handler.post(clockTick)
}
override fun onPause() {
handler.removeCallbacks(clockTick)
super.onPause()
private fun showMenu(anchor: View) {
val popup = PopupMenu(this, anchor)
popup.menu.add(0, MENU_CHECK, 0, "Nu controleren")
popup.menu.add(0, MENU_SETTINGS, 1, "Instellingen")
popup.setOnMenuItemClickListener { item ->
when (item.itemId) {
MENU_CHECK -> runCheck()
MENU_SETTINGS -> startActivity(Intent(this, SettingsActivity::class.java))
}
private fun updateClock() {
val now = LocalTime.now()
findViewById<TextView>(R.id.tv_clock).text = HH_MM.format(now)
findViewById<TextView>(R.id.tv_date).text = DATE.format(java.time.LocalDate.now())
true
}
popup.show()
}
private fun renderStatus() {
val p = prefs
val (_, alarms) = p.loadPlan()
val next = findViewById<TextView>(R.id.tv_next_alarms)
val info = findViewById<TextView>(R.id.tv_shift_info)
val (day, alarms) = p.loadPlan()
val container = findViewById<LinearLayout>(R.id.alarm_container)
container.removeAllViews()
val inflater = LayoutInflater.from(this)
if (alarms.isNotEmpty()) {
next.text = alarms.joinToString("\n") { "${it.name} ${HH_MM.format(it.time)}" }
if (alarms.isEmpty()) {
// Lege state, zoals Alarmio: vertel wat de gebruiker moet doen.
if (p.config() == null) {
container.addView(idleCard(inflater, "Geen instellingen", "Open het menu (⋮) → Instellingen en vul je agenda-gegevens in"))
} else {
container.addView(idleCard(inflater, "Geen wekkers gepland", "Morgen geen dienst"))
}
} else {
val tomorrow = LocalDate.now(ZoneId.systemDefault()).plusDays(1)
val dayLabel = when {
day == tomorrow -> "Morgen"
day != null -> "Gepland ${day.format(DAY_LABEL)}"
else -> "Gepland"
}
alarms.forEach { spec ->
container.addView(
inflater.inflate(R.layout.item_alarm_card, container, false).apply {
findViewById<TextView>(R.id.tv_day).text = dayLabel
findViewById<TextView>(R.id.tv_name).text = spec.name
findViewById<TextView>(R.id.tv_time).text = TIME.format(spec.time)
},
)
}
}
val info = findViewById<TextView>(R.id.tv_shift_info)
val shift = p.lastShiftStartIso?.let { runCatching { LocalDateTime.parse(it) }.getOrNull() }
info.text = if (shift != null) {
val rounded = ShiftPlanner.roundDownToQuarter(shift.toLocalTime())
"Dienst om ${HH_MM.format(shift.toLocalTime())} · afgerond ${HH_MM.format(rounded)}"
"Dienst om ${TIME_SHORT.format(shift.toLocalTime())} · afgerond ${TIME_SHORT.format(rounded)}"
} else {
p.lastStatusTxt.orEmpty()
}
} else {
next.text = "Geen wekkers"
info.text = p.lastStatusTxt ?: "Nog niet gecontroleerd"
}
/** Grijze kaart voor lege toestanden (geen instellingen / geen dienst). */
private fun idleCard(inflater: LayoutInflater, title: String, sub: String): View {
return inflater.inflate(R.layout.item_idle_card, null, false).apply {
findViewById<TextView>(R.id.tv_title).text = title
findViewById<TextView>(R.id.tv_sub).text = sub
}
}
private fun runCheck() {
val btn = findViewById<MaterialButton>(R.id.btn_check)
btn.isEnabled = false
btn.text = "Bezig…"
toast("Bezig met controleren…")
lifecycleScope.launch {
try {
val result = ShiftCheck.run(this@MainActivity)
val r = ShiftCheck.run(this@MainActivity)
renderStatus()
toast(result.message)
} finally {
btn.isEnabled = true
btn.text = "Nu controleren"
}
toast(r.message)
}
}
@ -158,11 +171,14 @@ class MainActivity : AppCompatActivity() {
private companion object {
const val REQ_NOTIFICATIONS = 42
const val MENU_CHECK = 1
const val MENU_SETTINGS = 2
const val EXTRA_SETUP_URL = "url"
const val EXTRA_SETUP_USER = "user"
const val EXTRA_SETUP_PASS = "pass"
const val EXTRA_TEST = "test"
val HH_MM = DateTimeFormatter.ofPattern("HH:mm")
val DATE = DateTimeFormatter.ofPattern("EEEE d MMMM")
val TIME = DateTimeFormatter.ofPattern("HH:mm")
val TIME_SHORT = DateTimeFormatter.ofPattern("HH:mm")
val DAY_LABEL = DateTimeFormatter.ofPattern("EEE d MMM", Locale.getDefault())
}
}

View file

@ -2,69 +2,52 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="24dp">
android:background="@color/bg"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="24dp"
android:paddingEnd="8dp"
android:paddingTop="16dp">
<TextView
android:id="@+id/tv_clock"
android:layout_width="wrap_content"
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:textSize="72sp"
android:layout_weight="1"
android:text="Wekkers"
android:textColor="@color/text_bright"
android:textSize="34sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_date"
android:id="@+id/tv_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="?android:attr/textColorSecondary" />
android:contentDescription="Menu"
android:padding="12dp"
android:text="⋮"
android:textColor="@color/text_bright"
android:textSize="28sp" />
</LinearLayout>
<View
<LinearLayout
android:id="@+id/alarm_container"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="40dp"
android:layout_marginBottom="24dp"
android:background="?android:attr/listDivider" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="VOLGENDE WEKKERS"
android:textSize="12sp"
android:textColor="?android:attr/textColorSecondary" />
<TextView
android:id="@+id/tv_next_alarms"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:gravity="center"
android:textSize="22sp" />
android:orientation="vertical" />
<TextView
android:id="@+id/tv_shift_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:textSize="14sp"
android:textColor="?android:attr/textColorSecondary" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_check"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="Nu controleren" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_settings"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Instellingen" />
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:textColor="@color/text_muted"
android:textSize="13sp" />
</LinearLayout>

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="14dp"
android:layout_marginEnd="16dp"
app:cardBackgroundColor="@color/card_active"
app:cardCornerRadius="28dp"
app:cardElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_day"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/text_bright"
android:textSize="15sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textColor="@color/text_muted"
android:textSize="15sp" />
</LinearLayout>
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textColor="@color/text_bright"
android:textSize="56sp"
android:textStyle="bold" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="14dp"
android:layout_marginEnd="16dp"
app:cardBackgroundColor="@color/card_idle"
app:cardCornerRadius="28dp"
app:cardElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/text_bright"
android:textSize="17sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_sub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="@color/text_muted"
android:textSize="14sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -1,6 +1,11 @@
<resources>
<color name="primary">#00695C</color>
<color name="on_primary">#FFFFFF</color>
<color name="secondary">#4DB6AC</color>
<color name="ic_launcher_background">#00695C</color>
<color name="bg">#0A0A0C</color>
<color name="card_active">#445C96</color>
<color name="card_idle">#1B1B1E</color>
<color name="text_bright">#EEF2FF</color>
<color name="text_muted">#A9B5D8</color>
<color name="primary">#A9B5D8</color>
<color name="on_primary">#0A0A0C</color>
<color name="secondary">#445C96</color>
<color name="ic_launcher_background">#445C96</color>
</resources>

View file

@ -1,12 +1,15 @@
<resources>
<style name="Theme.Buschauffeurswekker" parent="Theme.Material3.DayNight">
<style name="Theme.Buschauffeurswekker" parent="Theme.Material3.Dark">
<item name="colorPrimary">@color/primary</item>
<item name="colorOnPrimary">@color/on_primary</item>
<item name="colorSecondary">@color/secondary</item>
<item name="android:statusBarColor">@color/primary</item>
<item name="colorOnSecondary">@color/text_bright</item>
<item name="android:windowBackground">@color/bg</item>
<item name="android:statusBarColor">@color/bg</item>
<item name="android:navigationBarColor">@color/bg</item>
</style>
<style name="Theme.Buschauffeurswekker.Alarm" parent="Theme.Material3.DayNight.NoActionBar">
<style name="Theme.Buschauffeurswekker.Alarm" parent="Theme.Material3.Dark.NoActionBar">
<item name="android:windowBackground">@android:color/black</item>
<item name="android:statusBarColor">@android:color/black</item>
<item name="android:navigationBarColor">@android:color/black</item>