184 lines
No EOL
6.9 KiB
Kotlin
184 lines
No EOL
6.9 KiB
Kotlin
package nl.eddydevink.buschauffeurswekker
|
|
|
|
import android.Manifest
|
|
import android.content.Intent
|
|
import android.content.pm.PackageManager
|
|
import android.os.Build
|
|
import android.os.Bundle
|
|
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 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.ZoneId
|
|
import java.time.format.DateTimeFormatter
|
|
import java.util.Locale
|
|
|
|
/**
|
|
* 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
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
super.onCreate(savedInstanceState)
|
|
setContentView(R.layout.activity_main)
|
|
|
|
prefs = Prefs(this)
|
|
requestNotificationPermission()
|
|
|
|
findViewById<TextView>(R.id.tv_menu).setOnClickListener { showMenu(it) }
|
|
|
|
AlarmScheduler.scheduleNoonCheck(this)
|
|
|
|
// Installatiehulp/config zonder UI-interactie:
|
|
// --es url "…" --es user "…" --es pass "…" → config opslaan + controleren
|
|
// --ez test true → testwekker (debugging)
|
|
handleSetupIntent(intent)
|
|
if (intent?.getBooleanExtra(EXTRA_TEST, false) == true) {
|
|
AlarmScheduler.scheduleTest(this)
|
|
toast("Testwekker gaat over 10 seconden af")
|
|
}
|
|
}
|
|
|
|
override fun onNewIntent(intent: Intent) {
|
|
super.onNewIntent(intent)
|
|
setIntent(intent)
|
|
handleSetupIntent(intent)
|
|
if (intent.getBooleanExtra(EXTRA_TEST, false)) {
|
|
AlarmScheduler.scheduleTest(this)
|
|
toast("Testwekker gaat over 10 seconden af")
|
|
}
|
|
}
|
|
|
|
/** Slaat CalDAV-config van intent-extra's op en draait direct een controle. */
|
|
private fun handleSetupIntent(intent: Intent) {
|
|
val url = intent.getStringExtra(EXTRA_SETUP_URL)
|
|
val user = intent.getStringExtra(EXTRA_SETUP_USER)
|
|
val pass = intent.getStringExtra(EXTRA_SETUP_PASS)
|
|
if (url == null && user == null && pass == null) return
|
|
if (!url.isNullOrBlank()) prefs.calendarUrl = url.trim()
|
|
if (!user.isNullOrBlank()) prefs.username = user.trim()
|
|
if (pass != null) prefs.password = pass
|
|
lifecycleScope.launch {
|
|
val r = ShiftCheck.run(this@MainActivity)
|
|
renderStatus()
|
|
toast(r.message)
|
|
}
|
|
}
|
|
|
|
override fun onResume() {
|
|
super.onResume()
|
|
renderStatus()
|
|
}
|
|
|
|
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))
|
|
}
|
|
true
|
|
}
|
|
popup.show()
|
|
}
|
|
|
|
private fun renderStatus() {
|
|
val p = prefs
|
|
val (day, alarms) = p.loadPlan()
|
|
val container = findViewById<LinearLayout>(R.id.alarm_container)
|
|
container.removeAllViews()
|
|
val inflater = LayoutInflater.from(this)
|
|
|
|
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 ${TIME_SHORT.format(shift.toLocalTime())} · afgerond ${TIME_SHORT.format(rounded)}"
|
|
} else {
|
|
p.lastStatusTxt.orEmpty()
|
|
}
|
|
}
|
|
|
|
/** 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() {
|
|
toast("Bezig met controleren…")
|
|
lifecycleScope.launch {
|
|
val r = ShiftCheck.run(this@MainActivity)
|
|
renderStatus()
|
|
toast(r.message)
|
|
}
|
|
}
|
|
|
|
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
|
|
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 TIME = DateTimeFormatter.ofPattern("HH:mm")
|
|
val TIME_SHORT = DateTimeFormatter.ofPattern("HH:mm")
|
|
val DAY_LABEL = DateTimeFormatter.ofPattern("EEE d MMM", Locale.getDefault())
|
|
}
|
|
} |