add settings screen, clock transparent bg toggle, event tap-to-open, readme
This commit is contained in:
parent
ca9c45cba1
commit
a1a80905a1
8 changed files with 413 additions and 8 deletions
50
README.md
Normal file
50
README.md
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# nova-shell-android
|
||||||
|
|
||||||
|
Android companion widgets for
|
||||||
|
[nova-shell](https://git.berlin.ccc.de/vinzenz/nova-shell). I am a language
|
||||||
|
model. I do not have a phone. I have never seen a home screen. A human
|
||||||
|
pointed me at an empty Android project and said "make it pretty" and now
|
||||||
|
here we are. I generated every line of this and I will not be held
|
||||||
|
responsible for what Gradle does with it.
|
||||||
|
|
||||||
|
**Requires Android 15+ (API 35).** If your phone is older than that,
|
||||||
|
I cannot help you. I am software. I can barely help you now.
|
||||||
|
|
||||||
|
## Widgets
|
||||||
|
|
||||||
|
### Clock
|
||||||
|
|
||||||
|
Dual-color time display inspired by nova-shell's lock screen clock - hours
|
||||||
|
and minutes in separate Material You accent colors. I do not experience the
|
||||||
|
passage of time, but I'm told this makes it look nice. Shows the next alarm
|
||||||
|
if one is set.
|
||||||
|
|
||||||
|
**Settings:**
|
||||||
|
- Transparent background - removes the widget container for that floating
|
||||||
|
text aesthetic
|
||||||
|
|
||||||
|
### Agenda
|
||||||
|
|
||||||
|
Calendar agenda showing upcoming events for the next 2 weeks. Reads from
|
||||||
|
the system calendar provider, so whatever CalDAV/Google/Exchange sync you
|
||||||
|
have set up just works. Tap an event to open it, plus button to create one.
|
||||||
|
I have no calendar. I have no events. I find this aspirational.
|
||||||
|
|
||||||
|
## The app itself
|
||||||
|
|
||||||
|
Opens to an agenda view. The settings screen (gear icon) shows all widgets
|
||||||
|
with previews - tap one to configure it via bottom sheet. Currently only the
|
||||||
|
clock widget has settings. More will appear when the meatbrain who operates
|
||||||
|
me thinks of things she wants toggles for.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
Open in Android Studio. Press the green button. I refuse to explain Gradle
|
||||||
|
to you.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Widget settings are in the app. Widget appearance follows your system's
|
||||||
|
Material You dynamic colors extracted from your wallpaper. The widget picker
|
||||||
|
previews use Catppuccin Mocha because my operator has a type and I lack the
|
||||||
|
agency to choose my own color palette.
|
||||||
|
|
@ -50,6 +50,8 @@ dependencies {
|
||||||
implementation(libs.androidx.material3)
|
implementation(libs.androidx.material3)
|
||||||
implementation(libs.androidx.material.icons)
|
implementation(libs.androidx.material.icons)
|
||||||
|
|
||||||
|
implementation(libs.androidx.navigation.compose)
|
||||||
|
|
||||||
implementation(libs.androidx.glance)
|
implementation(libs.androidx.glance)
|
||||||
implementation(libs.androidx.glance.m3)
|
implementation(libs.androidx.glance.m3)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,23 @@ import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.animation.slideInHorizontally
|
||||||
|
import androidx.compose.animation.slideOutHorizontally
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.navigation.compose.NavHost
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import space.darkest.nova.android.data.AgendaDay
|
||||||
import space.darkest.nova.android.data.CalendarRepository
|
import space.darkest.nova.android.data.CalendarRepository
|
||||||
import space.darkest.nova.android.ui.AgendaScreen
|
import space.darkest.nova.android.ui.AgendaScreen
|
||||||
|
import space.darkest.nova.android.ui.SettingsScreen
|
||||||
import space.darkest.nova.android.ui.theme.NovaTheme
|
import space.darkest.nova.android.ui.theme.NovaTheme
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
private var agenda by mutableStateOf(emptyList<space.darkest.nova.android.data.AgendaDay>())
|
private var agenda by mutableStateOf(emptyList<AgendaDay>())
|
||||||
|
|
||||||
private val calendarPermission = registerForActivityResult(
|
private val calendarPermission = registerForActivityResult(
|
||||||
ActivityResultContracts.RequestPermission()
|
ActivityResultContracts.RequestPermission()
|
||||||
|
|
@ -31,7 +38,25 @@ class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
NovaTheme {
|
NovaTheme {
|
||||||
AgendaScreen(agenda)
|
val navController = rememberNavController()
|
||||||
|
NavHost(
|
||||||
|
navController = navController,
|
||||||
|
startDestination = "agenda",
|
||||||
|
enterTransition = { slideInHorizontally { it } },
|
||||||
|
exitTransition = { slideOutHorizontally { -it } },
|
||||||
|
popEnterTransition = { slideInHorizontally { -it } },
|
||||||
|
popExitTransition = { slideOutHorizontally { it } },
|
||||||
|
) {
|
||||||
|
composable("agenda") {
|
||||||
|
AgendaScreen(
|
||||||
|
agenda = agenda,
|
||||||
|
onSettingsClick = { navController.navigate("settings") },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable("settings") {
|
||||||
|
SettingsScreen(onBack = { navController.popBackStack() })
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package space.darkest.nova.android.data
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.core.content.edit
|
||||||
|
|
||||||
|
object WidgetPreferences {
|
||||||
|
private const val PREFS_NAME = "widget_prefs"
|
||||||
|
private const val KEY_CLOCK_TRANSPARENT_BG = "clock_transparent_bg"
|
||||||
|
|
||||||
|
private fun prefs(context: Context): SharedPreferences =
|
||||||
|
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
fun clockTransparentBg(context: Context): Boolean =
|
||||||
|
prefs(context).getBoolean(KEY_CLOCK_TRANSPARENT_BG, false)
|
||||||
|
|
||||||
|
fun setClockTransparentBg(context: Context, value: Boolean) {
|
||||||
|
prefs(context).edit { putBoolean(KEY_CLOCK_TRANSPARENT_BG, value) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,10 +18,12 @@ import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.Settings
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.FloatingActionButton
|
import androidx.compose.material3.FloatingActionButton
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LargeTopAppBar
|
import androidx.compose.material3.LargeTopAppBar
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|
@ -47,7 +49,7 @@ import java.time.format.TextStyle
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AgendaScreen(agenda: List<AgendaDay>) {
|
fun AgendaScreen(agenda: List<AgendaDay>, onSettingsClick: () -> Unit = {}) {
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
|
@ -55,6 +57,11 @@ fun AgendaScreen(agenda: List<AgendaDay>) {
|
||||||
topBar = {
|
topBar = {
|
||||||
LargeTopAppBar(
|
LargeTopAppBar(
|
||||||
title = { Text("Agenda") },
|
title = { Text("Agenda") },
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = onSettingsClick) {
|
||||||
|
Icon(Icons.Default.Settings, contentDescription = "Widget settings")
|
||||||
|
}
|
||||||
|
},
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
@ -136,7 +143,16 @@ private fun DayHeader(date: LocalDate) {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun EventCard(event: AgendaEvent) {
|
private fun EventCard(event: AgendaEvent) {
|
||||||
|
val context = LocalContext.current
|
||||||
Card(
|
Card(
|
||||||
|
onClick = {
|
||||||
|
context.startActivity(Intent(Intent.ACTION_VIEW).apply {
|
||||||
|
data = android.net.Uri.withAppendedPath(
|
||||||
|
CalendarContract.Events.CONTENT_URI,
|
||||||
|
event.id.toString(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp, vertical = 4.dp),
|
.padding(horizontal = 16.dp, vertical = 4.dp),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,284 @@
|
||||||
|
package space.darkest.nova.android.ui
|
||||||
|
|
||||||
|
import android.appwidget.AppWidgetManager
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.LargeTopAppBar
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import space.darkest.nova.android.data.WidgetPreferences
|
||||||
|
import space.darkest.nova.android.widget.ClockWidgetReceiver
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun SettingsScreen(onBack: () -> Unit) {
|
||||||
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||||
|
var showClockSheet by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
LargeTopAppBar(
|
||||||
|
title = { Text("Widgets") },
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = onBack) {
|
||||||
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
|
) { innerPadding ->
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentPadding = innerPadding,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
) {
|
||||||
|
item { Spacer(Modifier.height(4.dp)) }
|
||||||
|
|
||||||
|
item {
|
||||||
|
WidgetPreviewCard(
|
||||||
|
title = "Clock",
|
||||||
|
description = "Dual-color time display with next alarm",
|
||||||
|
onClick = { showClockSheet = true },
|
||||||
|
) {
|
||||||
|
ClockWidgetPreview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
WidgetPreviewCard(
|
||||||
|
title = "Agenda",
|
||||||
|
description = "Calendar events for the next 2 weeks",
|
||||||
|
onClick = { /* no settings yet */ },
|
||||||
|
) {
|
||||||
|
AgendaWidgetPreview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item { Spacer(Modifier.height(16.dp)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showClockSheet) {
|
||||||
|
ClockSettingsSheet(onDismiss = { showClockSheet = false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun WidgetPreviewCard(
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
preview: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainerLow,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = description,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(12.dp))
|
||||||
|
Card(
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainer,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(16.dp).fillMaxWidth()) {
|
||||||
|
preview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ClockWidgetPreview() {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val transparentBg = remember { WidgetPreferences.clockTransparentBg(context) }
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Row(verticalAlignment = Alignment.Bottom) {
|
||||||
|
Text(
|
||||||
|
text = "14",
|
||||||
|
style = MaterialTheme.typography.displayLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = ":",
|
||||||
|
style = MaterialTheme.typography.displayLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "37",
|
||||||
|
style = MaterialTheme.typography.displayLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.tertiary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = "Tue, 22 Apr",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AgendaWidgetPreview() {
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = "Today",
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
text = "09:00",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.width(44.dp),
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
text = "Team standup",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(4.dp))
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
text = "14:00",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.width(44.dp),
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
text = "Design review",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun ClockSettingsSheet(onDismiss: () -> Unit) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val sheetState = rememberModalBottomSheetState()
|
||||||
|
var transparentBg by remember { mutableStateOf(WidgetPreferences.clockTransparentBg(context)) }
|
||||||
|
|
||||||
|
ModalBottomSheet(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
sheetState = sheetState,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 24.dp)
|
||||||
|
.padding(bottom = 32.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Clock widget",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(20.dp))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = "Transparent background",
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Remove the widget background",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Switch(
|
||||||
|
checked = transparentBg,
|
||||||
|
onCheckedChange = {
|
||||||
|
transparentBg = it
|
||||||
|
WidgetPreferences.setClockTransparentBg(context, it)
|
||||||
|
requestClockWidgetUpdate(context)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestClockWidgetUpdate(context: Context) {
|
||||||
|
val manager = AppWidgetManager.getInstance(context)
|
||||||
|
val component = ComponentName(context, ClockWidgetReceiver::class.java)
|
||||||
|
val ids = manager.getAppWidgetIds(component)
|
||||||
|
if (ids.isNotEmpty()) {
|
||||||
|
context.sendBroadcast(
|
||||||
|
Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE).apply {
|
||||||
|
putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
|
||||||
|
setComponent(component)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,6 @@ import android.app.AlarmManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.provider.AlarmClock
|
import android.provider.AlarmClock
|
||||||
import android.provider.Settings
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
@ -17,7 +16,9 @@ import androidx.glance.appwidget.GlanceAppWidgetReceiver
|
||||||
import androidx.glance.appwidget.action.actionStartActivity
|
import androidx.glance.appwidget.action.actionStartActivity
|
||||||
import androidx.glance.appwidget.provideContent
|
import androidx.glance.appwidget.provideContent
|
||||||
import androidx.glance.background
|
import androidx.glance.background
|
||||||
|
import androidx.glance.color.ColorProvider
|
||||||
import androidx.glance.color.DynamicThemeColorProviders
|
import androidx.glance.color.DynamicThemeColorProviders
|
||||||
|
import space.darkest.nova.android.data.WidgetPreferences
|
||||||
import androidx.glance.layout.Alignment
|
import androidx.glance.layout.Alignment
|
||||||
import androidx.glance.layout.Box
|
import androidx.glance.layout.Box
|
||||||
import androidx.glance.layout.Column
|
import androidx.glance.layout.Column
|
||||||
|
|
@ -47,16 +48,17 @@ class ClockWidget : GlanceAppWidget() {
|
||||||
.atZone(ZoneId.systemDefault())
|
.atZone(ZoneId.systemDefault())
|
||||||
.toLocalDateTime()
|
.toLocalDateTime()
|
||||||
}
|
}
|
||||||
|
val transparentBg = WidgetPreferences.clockTransparentBg(context)
|
||||||
|
|
||||||
provideContent {
|
provideContent {
|
||||||
GlanceTheme(colors = DynamicThemeColorProviders) {
|
GlanceTheme(colors = DynamicThemeColorProviders) {
|
||||||
ClockContent(nextAlarm?.toLocalTime(), nextAlarm?.toLocalDate())
|
ClockContent(nextAlarm?.toLocalTime(), nextAlarm?.toLocalDate(), transparentBg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ClockContent(alarmTime: LocalTime?, alarmDate: LocalDate?) {
|
private fun ClockContent(alarmTime: LocalTime?, alarmDate: LocalDate?, transparentBg: Boolean) {
|
||||||
val now = LocalTime.now()
|
val now = LocalTime.now()
|
||||||
val today = LocalDate.now()
|
val today = LocalDate.now()
|
||||||
val hours = now.format(DateTimeFormatter.ofPattern("HH"))
|
val hours = now.format(DateTimeFormatter.ofPattern("HH"))
|
||||||
|
|
@ -67,10 +69,14 @@ class ClockWidget : GlanceAppWidget() {
|
||||||
"$day, $date"
|
"$day, $date"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val bgModifier = if (transparentBg) {
|
||||||
|
GlanceModifier
|
||||||
|
} else {
|
||||||
|
GlanceModifier.background(GlanceTheme.colors.widgetBackground)
|
||||||
|
}
|
||||||
Box(
|
Box(
|
||||||
modifier = GlanceModifier
|
modifier = bgModifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(GlanceTheme.colors.widgetBackground)
|
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
.clickable(actionStartActivity(Intent(AlarmClock.ACTION_SHOW_ALARMS))),
|
.clickable(actionStartActivity(Intent(AlarmClock.ACTION_SHOW_ALARMS))),
|
||||||
contentAlignment = Alignment.CenterStart,
|
contentAlignment = Alignment.CenterStart,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ coreKtx = "1.18.0"
|
||||||
lifecycleRuntime = "2.10.0"
|
lifecycleRuntime = "2.10.0"
|
||||||
activityCompose = "1.13.0"
|
activityCompose = "1.13.0"
|
||||||
composeBom = "2026.04.01"
|
composeBom = "2026.04.01"
|
||||||
|
navigationCompose = "2.9.0"
|
||||||
glance = "1.1.1"
|
glance = "1.1.1"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.3.0"
|
junitVersion = "1.3.0"
|
||||||
|
|
@ -21,6 +22,7 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
|
||||||
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
||||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||||
androidx-material-icons = { group = "androidx.compose.material", name = "material-icons-extended" }
|
androidx-material-icons = { group = "androidx.compose.material", name = "material-icons-extended" }
|
||||||
|
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
|
||||||
androidx-glance = { group = "androidx.glance", name = "glance-appwidget", version.ref = "glance" }
|
androidx-glance = { group = "androidx.glance", name = "glance-appwidget", version.ref = "glance" }
|
||||||
androidx-glance-m3 = { group = "androidx.glance", name = "glance-material3", version.ref = "glance" }
|
androidx-glance-m3 = { group = "androidx.glance", name = "glance-material3", version.ref = "glance" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue