build(telefon): default phone app with InCallService, call log, favourites, keypad, details
uhr: handle the public AlarmClock intents (set/show alarm and timer, dismiss, snooze); 0.2.1 docs: how each app replaces the system app Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -16,8 +16,8 @@ android {
|
||||
applicationId = "app.uhr"
|
||||
minSdk = 29
|
||||
targetSdk = 36
|
||||
versionCode = 4
|
||||
versionName = "0.2.0"
|
||||
versionCode = 5
|
||||
versionName = "0.2.1"
|
||||
}
|
||||
|
||||
// Release key lives outside the repo; without it, release builds fall back to the debug key (local only).
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
|
||||
|
||||
<application
|
||||
android:name=".UhrApp"
|
||||
@@ -28,6 +29,21 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".AlarmIntentActivity" android:exported="true" android:excludeFromRecents="true"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" android:noHistory="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SET_ALARM" />
|
||||
<action android:name="android.intent.action.SET_TIMER" />
|
||||
<action android:name="android.intent.action.SHOW_ALARMS" />
|
||||
<action android:name="android.intent.action.SHOW_TIMERS" />
|
||||
<action android:name="android.intent.action.DISMISS_ALARM" />
|
||||
<action android:name="android.intent.action.DISMISS_TIMER" />
|
||||
<action android:name="android.intent.action.SNOOZE_ALARM" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.VOICE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".alarm.RingingActivity"
|
||||
android:exported="false"
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package app.uhr
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.provider.AlarmClock
|
||||
import android.widget.Toast
|
||||
import app.uhr.alarm.Ring
|
||||
import app.uhr.alarm.RingingService
|
||||
import app.uhr.data.Alarm
|
||||
import app.uhr.data.Graph
|
||||
import app.uhr.ui.components.Fmt
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.Calendar
|
||||
|
||||
/**
|
||||
* The public AlarmClock intents, so other apps and voice assistants can use this app
|
||||
* instead of the system clock: set/show alarms and timers, dismiss and snooze.
|
||||
*/
|
||||
class AlarmIntentActivity : Activity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Graph.init(this)
|
||||
val i = intent
|
||||
val skipUi = i.getBooleanExtra(AlarmClock.EXTRA_SKIP_UI, false)
|
||||
when (i.action) {
|
||||
AlarmClock.ACTION_SET_ALARM -> setAlarm(i, skipUi)
|
||||
AlarmClock.ACTION_SET_TIMER -> setTimer(i, skipUi)
|
||||
AlarmClock.ACTION_SHOW_ALARMS -> open(0)
|
||||
AlarmClock.ACTION_SHOW_TIMERS -> open(2)
|
||||
AlarmClock.ACTION_DISMISS_ALARM, AlarmClock.ACTION_DISMISS_TIMER -> RingingService.ringing.value?.let { RingingService.stop(this) } ?: open(0)
|
||||
AlarmClock.ACTION_SNOOZE_ALARM -> if (RingingService.ringing.value is Ring.AlarmRing) RingingService.snooze(this) else open(0)
|
||||
else -> open(0)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun setAlarm(i: Intent, skipUi: Boolean) {
|
||||
if (!i.hasExtra(AlarmClock.EXTRA_HOUR)) { open(0, newAlarm = true); return }
|
||||
val hour = i.getIntExtra(AlarmClock.EXTRA_HOUR, 7).coerceIn(0, 23)
|
||||
val minute = i.getIntExtra(AlarmClock.EXTRA_MINUTES, 0).coerceIn(0, 59)
|
||||
// EXTRA_DAYS uses Calendar.SUNDAY = 1 … SATURDAY = 7; we store Monday = bit 0
|
||||
val days = i.getIntegerArrayListExtra(AlarmClock.EXTRA_DAYS).orEmpty().fold(0) { acc, d ->
|
||||
val mondayBased = if (d == Calendar.SUNDAY) 6 else d - 2
|
||||
if (mondayBased in 0..6) acc or (1 shl mondayBased) else acc
|
||||
}
|
||||
val ringtone = i.getStringExtra(AlarmClock.EXTRA_RINGTONE)?.let { if (it == AlarmClock.VALUE_RINGTONE_SILENT) Alarm.SILENT else it }
|
||||
val alarm = Alarm(
|
||||
hour = hour, minute = minute, days = days, label = i.getStringExtra(AlarmClock.EXTRA_MESSAGE).orEmpty(),
|
||||
vibrate = i.getBooleanExtra(AlarmClock.EXTRA_VIBRATE, true), ringtoneUri = ringtone,
|
||||
)
|
||||
val id = runBlocking { Graph.repo.save(alarm) }
|
||||
val inst = runBlocking { Graph.repo.instance(id) }
|
||||
Fmt.is24h = android.text.format.DateFormat.is24HourFormat(this)
|
||||
inst?.let { Toast.makeText(this, getString(R.string.alarm_set_in, Fmt.until(resources, it.fireAt)), Toast.LENGTH_SHORT).show() }
|
||||
if (!skipUi) open(0)
|
||||
}
|
||||
|
||||
private fun setTimer(i: Intent, skipUi: Boolean) {
|
||||
val seconds = i.getIntExtra(AlarmClock.EXTRA_LENGTH, 0)
|
||||
if (seconds <= 0) { open(2); return }
|
||||
runBlocking { Graph.repo.startTimer(seconds * 1000L, i.getStringExtra(AlarmClock.EXTRA_MESSAGE).orEmpty()) }
|
||||
if (!skipUi) open(2)
|
||||
}
|
||||
|
||||
private fun open(tab: Int, newAlarm: Boolean = false) = startActivity(
|
||||
Intent(this, MainActivity::class.java).putExtra(MainActivity.EXTRA_TAB, tab).putExtra(MainActivity.EXTRA_NEW_ALARM, newAlarm).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
||||
)
|
||||
}
|
||||
@@ -80,6 +80,7 @@ import java.time.LocalTime
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
private var tabFromIntent = mutableIntStateOf(-1)
|
||||
private val newAlarm = mutableStateOf(false)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -87,15 +88,17 @@ class MainActivity : ComponentActivity() {
|
||||
Graph.init(this)
|
||||
Fmt.is24h = DateFormat.is24HourFormat(this)
|
||||
tabFromIntent.intValue = intent.getIntExtra(EXTRA_TAB, -1)
|
||||
newAlarm.value = intent.getBooleanExtra(EXTRA_NEW_ALARM, false)
|
||||
setContent {
|
||||
val settings by Graph.repo.prefs.settings.collectAsState(initial = Settings())
|
||||
ClockTheme(settings.theme) { ClockApp(settings, tabFromIntent.intValue) }
|
||||
ClockTheme(settings.theme) { ClockApp(settings, tabFromIntent.intValue, newAlarm.value) { newAlarm.value = false } }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
tabFromIntent.intValue = intent.getIntExtra(EXTRA_TAB, -1)
|
||||
newAlarm.value = intent.getBooleanExtra(EXTRA_NEW_ALARM, false)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@@ -103,7 +106,10 @@ class MainActivity : ComponentActivity() {
|
||||
Fmt.is24h = DateFormat.is24HourFormat(this)
|
||||
}
|
||||
|
||||
companion object { const val EXTRA_TAB = "tab" }
|
||||
companion object {
|
||||
const val EXTRA_TAB = "tab"
|
||||
const val EXTRA_NEW_ALARM = "new_alarm"
|
||||
}
|
||||
}
|
||||
|
||||
private data class Tab(val title: Int, val icon: ImageVector)
|
||||
@@ -118,7 +124,7 @@ private val TABS = listOf(
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ClockApp(settings: Settings, requestedTab: Int) {
|
||||
private fun ClockApp(settings: Settings, requestedTab: Int, requestNewAlarm: Boolean = false, onNewAlarmHandled: () -> Unit = {}) {
|
||||
val repo = Graph.repo
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
@@ -127,6 +133,7 @@ private fun ClockApp(settings: Settings, requestedTab: Int) {
|
||||
var showSettings by rememberSaveable { mutableStateOf(false) }
|
||||
var menu by remember { mutableStateOf(false) }
|
||||
var addAlarm by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(requestNewAlarm) { if (requestNewAlarm) { tab = 0; addAlarm = true; onNewAlarmHandled() } }
|
||||
var addCity by remember { mutableStateOf(false) }
|
||||
var keypad by rememberSaveable { mutableStateOf(false) }
|
||||
var digits by rememberSaveable { mutableStateOf("") }
|
||||
|
||||
Reference in New Issue
Block a user