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.material.icons)
|
||||
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
|
||||
implementation(libs.androidx.glance)
|
||||
implementation(libs.androidx.glance.m3)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,16 +6,23 @@ import androidx.activity.ComponentActivity
|
|||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
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.mutableStateOf
|
||||
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.ui.AgendaScreen
|
||||
import space.darkest.nova.android.ui.SettingsScreen
|
||||
import space.darkest.nova.android.ui.theme.NovaTheme
|
||||
|
||||
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(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
|
|
@ -31,7 +38,25 @@ class MainActivity : ComponentActivity() {
|
|||
|
||||
setContent {
|
||||
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.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LargeTopAppBar
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -47,7 +49,7 @@ import java.time.format.TextStyle
|
|||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AgendaScreen(agenda: List<AgendaDay>) {
|
||||
fun AgendaScreen(agenda: List<AgendaDay>, onSettingsClick: () -> Unit = {}) {
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
val context = LocalContext.current
|
||||
|
||||
|
|
@ -55,6 +57,11 @@ fun AgendaScreen(agenda: List<AgendaDay>) {
|
|||
topBar = {
|
||||
LargeTopAppBar(
|
||||
title = { Text("Agenda") },
|
||||
actions = {
|
||||
IconButton(onClick = onSettingsClick) {
|
||||
Icon(Icons.Default.Settings, contentDescription = "Widget settings")
|
||||
}
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
},
|
||||
|
|
@ -136,7 +143,16 @@ private fun DayHeader(date: LocalDate) {
|
|||
|
||||
@Composable
|
||||
private fun EventCard(event: AgendaEvent) {
|
||||
val context = LocalContext.current
|
||||
Card(
|
||||
onClick = {
|
||||
context.startActivity(Intent(Intent.ACTION_VIEW).apply {
|
||||
data = android.net.Uri.withAppendedPath(
|
||||
CalendarContract.Events.CONTENT_URI,
|
||||
event.id.toString(),
|
||||
)
|
||||
})
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.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.Intent
|
||||
import android.provider.AlarmClock
|
||||
import android.provider.Settings
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.provideContent
|
||||
import androidx.glance.background
|
||||
import androidx.glance.color.ColorProvider
|
||||
import androidx.glance.color.DynamicThemeColorProviders
|
||||
import space.darkest.nova.android.data.WidgetPreferences
|
||||
import androidx.glance.layout.Alignment
|
||||
import androidx.glance.layout.Box
|
||||
import androidx.glance.layout.Column
|
||||
|
|
@ -47,16 +48,17 @@ class ClockWidget : GlanceAppWidget() {
|
|||
.atZone(ZoneId.systemDefault())
|
||||
.toLocalDateTime()
|
||||
}
|
||||
val transparentBg = WidgetPreferences.clockTransparentBg(context)
|
||||
|
||||
provideContent {
|
||||
GlanceTheme(colors = DynamicThemeColorProviders) {
|
||||
ClockContent(nextAlarm?.toLocalTime(), nextAlarm?.toLocalDate())
|
||||
ClockContent(nextAlarm?.toLocalTime(), nextAlarm?.toLocalDate(), transparentBg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ClockContent(alarmTime: LocalTime?, alarmDate: LocalDate?) {
|
||||
private fun ClockContent(alarmTime: LocalTime?, alarmDate: LocalDate?, transparentBg: Boolean) {
|
||||
val now = LocalTime.now()
|
||||
val today = LocalDate.now()
|
||||
val hours = now.format(DateTimeFormatter.ofPattern("HH"))
|
||||
|
|
@ -67,10 +69,14 @@ class ClockWidget : GlanceAppWidget() {
|
|||
"$day, $date"
|
||||
}
|
||||
|
||||
val bgModifier = if (transparentBg) {
|
||||
GlanceModifier
|
||||
} else {
|
||||
GlanceModifier.background(GlanceTheme.colors.widgetBackground)
|
||||
}
|
||||
Box(
|
||||
modifier = GlanceModifier
|
||||
modifier = bgModifier
|
||||
.fillMaxSize()
|
||||
.background(GlanceTheme.colors.widgetBackground)
|
||||
.padding(16.dp)
|
||||
.clickable(actionStartActivity(Intent(AlarmClock.ACTION_SHOW_ALARMS))),
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ coreKtx = "1.18.0"
|
|||
lifecycleRuntime = "2.10.0"
|
||||
activityCompose = "1.13.0"
|
||||
composeBom = "2026.04.01"
|
||||
navigationCompose = "2.9.0"
|
||||
glance = "1.1.1"
|
||||
junit = "4.13.2"
|
||||
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-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||
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-m3 = { group = "androidx.glance", name = "glance-material3", version.ref = "glance" }
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue