commit be5362ad76c0decef3b915c83a76c7f67e2a10b9 Author: Damocles Date: Wed Apr 22 01:06:53 2026 +0200 initial commit: material you calendar widget diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e3bdbed --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/app/build diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..9bf233b --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,41 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "dev.nova.widget" + compileSdk = 35 + buildToolsVersion = "35.0.0" + + defaultConfig { + applicationId = "dev.nova.widget" + minSdk = 31 // Material You requires Android 12+ + targetSdk = 35 + versionCode = 1 + versionName = "0.1.0" + } + + buildTypes { + release { + isMinifyEnabled = true + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt")) + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + } + + kotlinOptions { + jvmTarget = "21" + } +} + +dependencies { + implementation("com.google.android.material:material:1.12.0") + implementation("androidx.core:core-ktx:1.15.0") + implementation("androidx.glance:glance-appwidget:1.1.1") + implementation("androidx.glance:glance-material3:1.1.1") +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0d8bd0a --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/dev/nova/widget/CalendarWidget.kt b/app/src/main/java/dev/nova/widget/CalendarWidget.kt new file mode 100644 index 0000000..80eeb08 --- /dev/null +++ b/app/src/main/java/dev/nova/widget/CalendarWidget.kt @@ -0,0 +1,157 @@ +package dev.nova.widget + +import android.content.Context +import android.content.Intent +import android.provider.CalendarContract +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.glance.GlanceId +import androidx.glance.GlanceModifier +import androidx.glance.GlanceTheme +import androidx.glance.action.clickable +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.action.actionStartActivity +import androidx.glance.appwidget.provideContent +import androidx.glance.background +import androidx.glance.layout.Alignment +import androidx.glance.layout.Box +import androidx.glance.layout.Column +import androidx.glance.layout.Row +import androidx.glance.layout.Spacer +import androidx.glance.layout.fillMaxSize +import androidx.glance.layout.fillMaxWidth +import androidx.glance.layout.height +import androidx.glance.layout.padding +import androidx.glance.layout.size +import androidx.glance.text.FontWeight +import androidx.glance.text.Text +import androidx.glance.text.TextAlign +import androidx.glance.text.TextStyle +import java.time.DayOfWeek +import java.time.LocalDate +import java.time.YearMonth +import java.time.format.TextStyle as DateTextStyle +import java.util.Locale + +class CalendarWidget : GlanceAppWidget() { + + override suspend fun provideGlance(context: Context, id: GlanceId) { + provideContent { + GlanceTheme { + CalendarContent() + } + } + } + + @Composable + private fun CalendarContent() { + val today = LocalDate.now() + val yearMonth = YearMonth.from(today) + val daysInMonth = yearMonth.lengthOfMonth() + val firstDayOfWeek = yearMonth.atDay(1).dayOfWeek + // Monday-based offset + val startOffset = (firstDayOfWeek.value - DayOfWeek.MONDAY.value + 7) % 7 + + val calendarIntent = Intent(Intent.ACTION_VIEW).apply { + data = CalendarContract.CONTENT_URI + } + + Column( + modifier = GlanceModifier + .fillMaxSize() + .background(GlanceTheme.colors.widgetBackground) + .padding(12.dp) + .clickable(actionStartActivity(calendarIntent)), + ) { + // Header - month and year + Text( + text = "${yearMonth.month.getDisplayName(DateTextStyle.FULL, Locale.getDefault())} ${yearMonth.year}", + style = TextStyle( + color = GlanceTheme.colors.primary, + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + ), + modifier = GlanceModifier.fillMaxWidth().padding(bottom = 8.dp), + ) + + // Day of week headers + Row(modifier = GlanceModifier.fillMaxWidth()) { + val days = listOf("Mo", "Tu", "We", "Th", "Fr", "Sa", "Su") + for (day in days) { + Text( + text = day, + style = TextStyle( + color = GlanceTheme.colors.secondary, + fontSize = 11.sp, + fontWeight = FontWeight.Medium, + textAlign = TextAlign.Center, + ), + modifier = GlanceModifier.defaultWeight(), + ) + } + } + + Spacer(modifier = GlanceModifier.height(4.dp)) + + // Calendar grid + var dayCounter = 1 + for (week in 0 until 6) { + if (dayCounter > daysInMonth) break + + Row(modifier = GlanceModifier.fillMaxWidth()) { + for (col in 0 until 7) { + val cellIndex = week * 7 + col + if (cellIndex < startOffset || dayCounter > daysInMonth) { + Spacer(modifier = GlanceModifier.defaultWeight()) + } else { + val isToday = dayCounter == today.dayOfMonth + DayCell( + day = dayCounter, + isToday = isToday, + modifier = GlanceModifier.defaultWeight(), + ) + dayCounter++ + } + } + } + } + } + } + + @Composable + private fun DayCell(day: Int, isToday: Boolean, modifier: GlanceModifier) { + Box( + contentAlignment = Alignment.Center, + modifier = modifier.padding(vertical = 2.dp), + ) { + if (isToday) { + Box( + contentAlignment = Alignment.Center, + modifier = GlanceModifier + .size(28.dp) + .background(GlanceTheme.colors.primary), + ) { + Text( + text = day.toString(), + style = TextStyle( + color = GlanceTheme.colors.onPrimary, + fontSize = 13.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + ), + ) + } + } else { + Text( + text = day.toString(), + style = TextStyle( + color = GlanceTheme.colors.onSurface, + fontSize = 13.sp, + textAlign = TextAlign.Center, + ), + ) + } + } + } +} diff --git a/app/src/main/java/dev/nova/widget/CalendarWidgetReceiver.kt b/app/src/main/java/dev/nova/widget/CalendarWidgetReceiver.kt new file mode 100644 index 0000000..e0bc001 --- /dev/null +++ b/app/src/main/java/dev/nova/widget/CalendarWidgetReceiver.kt @@ -0,0 +1,8 @@ +package dev.nova.widget + +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.GlanceAppWidgetReceiver + +class CalendarWidgetReceiver : GlanceAppWidgetReceiver() { + override val glanceAppWidget: GlanceAppWidget = CalendarWidget() +} diff --git a/app/src/main/res/layout/calendar_widget_loading.xml b/app/src/main/res/layout/calendar_widget_loading.xml new file mode 100644 index 0000000..306ba42 --- /dev/null +++ b/app/src/main/res/layout/calendar_widget_loading.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml new file mode 100644 index 0000000..403378f --- /dev/null +++ b/app/src/main/res/values-night/colors.xml @@ -0,0 +1,11 @@ + + + @android:color/system_accent1_900 + @android:color/system_accent1_100 + @android:color/system_neutral1_900 + @android:color/system_neutral1_100 + @android:color/system_accent1_200 + @android:color/system_accent1_800 + @android:color/system_accent1_200 + @android:color/system_accent1_800 + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..878885e --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,12 @@ + + + + @android:color/system_accent1_100 + @android:color/system_accent1_900 + @android:color/system_neutral1_50 + @android:color/system_neutral1_900 + @android:color/system_accent1_600 + @android:color/system_accent1_0 + @android:color/system_accent1_600 + @android:color/system_accent1_0 + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..090f98e --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,4 @@ + + +