Buschauffeurswekker v1: CalDAV-dagcheck 12:00, kwartier-afronding, wekkers Opstaan/Wally/Vertrek, fullscreen alarm-UI

This commit is contained in:
eddy 2026-10-14 16:31:50 +02:00
commit 34505be9b8
31 changed files with 1881 additions and 1 deletions

View file

@ -0,0 +1,171 @@
package nl.eddydevink.buschauffeurswekker
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.PowerManager
import android.provider.Settings
import android.widget.EditText
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 java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
class MainActivity : AppCompatActivity() {
private lateinit var prefs: Prefs
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
prefs = Prefs(this)
requestNotificationPermission()
findViewById<MaterialButton>(R.id.btn_check).setOnClickListener { runCheck() }
findViewById<MaterialButton>(R.id.btn_test).setOnClickListener {
AlarmScheduler.scheduleTest(this)
Toast.makeText(this, "Testwekker gaat over 10 seconden af", Toast.LENGTH_SHORT).show()
}
findViewById<MaterialButton>(R.id.btn_save).setOnClickListener { saveSettings() }
findViewById<MaterialButton>(R.id.btn_perm_notifications).setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED
) {
requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), REQ_NOTIFICATIONS)
} else {
toast("Meldingen zijn al toegestaan")
}
}
findViewById<MaterialButton>(R.id.btn_perm_exact).setOnClickListener { openExactAlarmSettings() }
findViewById<MaterialButton>(R.id.btn_perm_battery).setOnClickListener { openBatterySettings() }
findViewById<MaterialButton>(R.id.btn_perm_fullscreen).setOnClickListener { openFullScreenSettings() }
// Staat altijd zichtbaar in de status-edit: pionnen van de volgende controle alvast zetten.
AlarmScheduler.scheduleNoonCheck(this)
}
override fun onResume() {
super.onResume()
renderStatus()
renderSettings()
}
private fun renderStatus() {
val p = prefs
val last = p.lastCheckedAtMillis.takeIf { it > 0 }?.let {
Instant.ofEpochMilli(it)
.atZone(ZoneId.systemDefault())
.format(DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm"))
} ?: "nooit"
findViewById<TextView>(R.id.tv_status).text = buildString {
append("Dagelijkse controle: elke dag om 12:00\n")
append("Laatst gecontroleerd: $last\n\n")
append(p.lastStatusTxt ?: "Nog niet gecontroleerd.")
val (day, alarms) = p.loadPlan()
if (day != null && alarms.isNotEmpty()) {
append("\n\nGepland voor $day:")
alarms.forEach { append("\n ${it.name} om ${it.time.toLocalTime()}") }
}
}
}
private fun renderSettings() {
findViewById<EditText>(R.id.et_url).setText(prefs.calendarUrl.orEmpty())
findViewById<EditText>(R.id.et_user).setText(prefs.username.orEmpty())
findViewById<EditText>(R.id.et_pass).setText(prefs.password.orEmpty())
}
private fun saveSettings() {
prefs.calendarUrl = findViewById<EditText>(R.id.et_url).text.toString().trim()
prefs.username = findViewById<EditText>(R.id.et_user).text.toString().trim()
prefs.password = findViewById<EditText>(R.id.et_pass).text.toString()
toast("Instellingen opgeslagen")
}
private fun runCheck() {
saveSettings()
val btn = findViewById<MaterialButton>(R.id.btn_check)
btn.isEnabled = false
btn.text = "Bezig…"
lifecycleScope.launch {
try {
val result = ShiftCheck.run(this@MainActivity)
renderStatus()
toast(result.message)
} finally {
btn.isEnabled = true
btn.text = "Nu controleren"
}
}
}
private fun openExactAlarmSettings() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
runCatching {
startActivity(
Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM, Uri.parse("package:$packageName")),
)
}.onFailure { toast("Niet beschikbaar op dit toestel") }
} else {
toast("Niet nodig op dit toestel")
}
}
private fun openBatterySettings() {
val pm = getSystemService(Context.POWER_SERVICE) as PowerManager
if (pm.isIgnoringBatteryOptimizations(packageName)) {
toast("Deze app is al uitgezonderd van batterijoptimalisatie")
return
}
runCatching {
startActivity(
Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, Uri.parse("package:$packageName")),
)
}.onFailure { toast("Niet beschikbaar op dit toestel") }
}
private fun openFullScreenSettings() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
runCatching {
startActivity(
Intent(
Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT,
Uri.parse("package:$packageName"),
),
)
}.onFailure { toast("Niet beschikbaar op dit toestel") }
} else {
toast("Op dit toestel (Android < 14) is volledig scherm standaard geregeld")
}
}
private fun requestNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) !=
PackageManager.PERMISSION_GRANTED
) {
requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), REQ_NOTIFICATIONS)
}
}
private fun toast(msg: String) = Toast.makeText(this, msg, Toast.LENGTH_LONG).show()
private companion object {
const val REQ_NOTIFICATIONS = 42
}
}