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(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(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(R.id.tv_day).text = dayLabel findViewById(R.id.tv_name).text = spec.name findViewById(R.id.tv_time).text = TIME.format(spec.time) }, ) } } val info = findViewById(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(R.id.tv_title).text = title findViewById(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()) } }