Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
65eb4f5
feat: Add widget and live wallpaper module
google-labs-jules[bot] Dec 17, 2025
3067561
feat: Add widget and live wallpaper module
google-labs-jules[bot] Dec 19, 2025
e370370
feat: Add widget and live wallpaper module
google-labs-jules[bot] Dec 19, 2025
f39a7dc
Merge branch 'main' into feat/widget-live-wallpaper-4530317895616930729
JesseScott Dec 20, 2025
dc9fc70
refactor: Move GradientUtil to common module
google-labs-jules[bot] Dec 20, 2025
efe8eda
Merge branch 'main' into feat/widget-live-wallpaper-4530317895616930729
JesseScott Dec 20, 2025
926ee0c
fix: Update Kotlin version and restore Logger.kt
google-labs-jules[bot] Dec 20, 2025
3a15f86
Merge branch 'main' into feat/widget-live-wallpaper-4530317895616930729
JesseScott Jan 20, 2026
b2a3357
fix: Add network security config to resolve CI failure
google-labs-jules[bot] Jan 20, 2026
73ee41b
CHANGE BACK TO Gradle 13
JesseScott Jan 29, 2026
0dc3914
Update Hilt and Kotlin
JesseScott Jan 29, 2026
7e2e687
reset signing
JesseScott Jan 29, 2026
2d3b56c
reset logger
JesseScott Jan 29, 2026
b3f5523
fix: Restore unintentionally modified files
google-labs-jules[bot] Jan 29, 2026
7a09d8d
fix: Restore unintentionally modified files
google-labs-jules[bot] Jan 29, 2026
16f3132
fix: Restore deleted widget and wallpaper classes
google-labs-jules[bot] Jan 29, 2026
dc9cc89
fix: Restore unintentionally modified files
google-labs-jules[bot] Jan 29, 2026
a468a85
fix: Revert Gradle wrapper version to 8.13
google-labs-jules[bot] Feb 12, 2026
9119c85
feat: Add widget and live wallpaper module and sync with main
google-labs-jules[bot] Feb 12, 2026
ca9bccf
feat: Add widget and live wallpaper module and fix Kotlin version mis…
google-labs-jules[bot] Feb 12, 2026
a2693c4
fix: Upgrade Hilt and Compose Compiler to match Kotlin 1.9.0
google-labs-jules[bot] Feb 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/androidApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ android {
buildConfig = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.7"
kotlinCompilerExtensionVersion = "1.5.1"
}
packaging {
resources {
Expand Down Expand Up @@ -69,7 +69,7 @@ dependencies {

// Hilt
implementation("com.google.dagger:hilt-android:2.49")
kapt("com.google.dagger:hilt-compiler:2.44")
kapt("com.google.dagger:hilt-compiler:2.49")
implementation("androidx.hilt:hilt-navigation-compose:1.2.0")

// Firebase
Expand Down
6 changes: 3 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ plugins {
//trick: for the same plugin versions in all sub-modules
id("com.android.application").version("8.6.0").apply(false)
id("com.android.library").version("8.6.0").apply(false)
kotlin("android").version("1.8.21").apply(false)
kotlin("multiplatform").version("1.8.21").apply(false)
id("com.google.dagger.hilt.android").version("2.44").apply(false)
kotlin("android").version("1.9.0").apply(false)
kotlin("multiplatform").version("1.9.0").apply(false)
id("com.google.dagger.hilt.android").version("2.49").apply(false)
id("com.google.gms.google-services").version("4.4.2").apply(false)
id("com.google.firebase.crashlytics").version("3.0.2").apply(false)
}
Expand Down
8 changes: 7 additions & 1 deletion app/common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ android {

buildFeatures {
buildConfig = true
compose = true
}

buildTypes {
Expand All @@ -35,16 +36,21 @@ android {
kotlinOptions {
jvmTarget = "17"
}

composeOptions {
kotlinCompilerExtensionVersion = "1.5.1"
}
}

dependencies {
implementation("androidx.compose.ui:ui:1.6.2")
implementation("androidx.core:core-ktx:1.12.0")
implementation("org.shredzone.commons:commons-suncalc:3.7")
implementation("androidx.datastore:datastore-preferences:1.1.1")

// Hilt
implementation("com.google.dagger:hilt-android:2.49")
kapt("com.google.dagger:hilt-compiler:2.44")
kapt("com.google.dagger:hilt-compiler:2.49")

// Testing
testImplementation("junit:junit:4.13.2")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package tt.co.jesses.moonlight.common.util

import android.graphics.Canvas
import android.graphics.LinearGradient
import android.graphics.Paint
import android.graphics.Shader
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin
import kotlin.math.sqrt

object GradientUtil {
private val silverColor = Color(0xFFC0C0C0)
private val lsb = Color(0xFFCCE5FF)
private val hsl = Color.hsl(
hue = 0f,
saturation = 0f,
lightness = 0f,
alpha = 1f,
)

fun generateHSLColor(): List<Color> {
return listOf(hsl, silverColor, lsb)
}
}

fun drawAngledGradient(degrees: Float, canvas: Canvas, colors: List<Int>) {
val (width, height) = canvas.width.toFloat() to canvas.height.toFloat()
val (x, y) = width to height
val gamma = (degrees / 180f) * Math.PI
val yComponent = cos(gamma)
val xComponent = sin(gamma)
val r = sqrt(x.pow(2) + y.pow(2)) / 2f
val offset = android.graphics.PointF(x / 2f, y / 2f)
val offset2 = android.graphics.PointF(xComponent.toFloat() * r, yComponent.toFloat() * r)

val gradient = LinearGradient(
offset.x - offset2.x,
offset.y - offset2.y,
offset.x + offset2.x,
offset.y + offset2.y,
colors.toIntArray(),
null,
Shader.TileMode.CLAMP
)

val paint = Paint().apply {
shader = gradient
}

canvas.drawRect(0f, 0f, width, height, paint)
}
2 changes: 0 additions & 2 deletions app/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,3 @@ networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists


1 change: 1 addition & 0 deletions app/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ include(":androidApp")
include(":shared")
include(":common")
include(":wearApp")
include(":widget")
4 changes: 2 additions & 2 deletions app/wearApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ android {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.7"
kotlinCompilerExtensionVersion = "1.5.1"
}

packaging {
Expand Down Expand Up @@ -65,7 +65,7 @@ dependencies {

// Hilt
implementation("com.google.dagger:hilt-android:2.49")
kapt("com.google.dagger:hilt-compiler:2.44")
kapt("com.google.dagger:hilt-compiler:2.49")
implementation("androidx.hilt:hilt-navigation-compose:1.2.0")

// Lifecycle
Expand Down
51 changes: 51 additions & 0 deletions app/widget/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
id("com.android.library")
kotlin("android")
}

android {
namespace = "tt.co.jesses.moonlight.widget"
compileSdk = 34

defaultConfig {
minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1"
}
}

dependencies {
implementation(project(":common"))
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.glance:glance-appwidget:1.0.0")
implementation("androidx.compose.ui:ui:1.6.2")
implementation("androidx.compose.ui:ui-tooling-preview:1.6.2")
implementation("androidx.compose.material3:material3:1.2.1")
debugImplementation("androidx.compose.ui:ui-tooling:1.6.2")
}
29 changes: 29 additions & 0 deletions app/widget/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="tt.co.jesses.moonlight.widget">
<uses-feature android:name="android.software.live_wallpaper" />
<application>
<receiver
android:name=".MoonlightWidgetReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/moonlight_widget_info" />
</receiver>
<service
android:name=".MoonlightWallpaperService"
android:label="@string/wallpaper_name"
android:permission="android.permission.BIND_WALLPAPER"
android:exported="true">
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
</intent-filter>
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/moonlight_wallpaper_info" />
</service>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package tt.co.jesses.moonlight.widget

import android.service.wallpaper.WallpaperService
import android.view.SurfaceHolder
import android.graphics.Canvas
import android.graphics.Color
import android.os.Handler
import android.os.Looper

class MoonlightWallpaperService : WallpaperService() {

override fun onCreateEngine(): Engine {
return MoonlightWallpaperEngine()
}

private inner class MoonlightWallpaperEngine : Engine() {

private val handler = Handler(Looper.getMainLooper())
private var isVisible = false

private val drawRunner = Runnable { draw() }

override fun onSurfaceCreated(holder: SurfaceHolder) {
super.onSurfaceCreated(holder)
handler.post(drawRunner)
}

override fun onVisibilityChanged(visible: Boolean) {
super.onVisibilityChanged(visible)
isVisible = visible
if (visible) {
handler.post(drawRunner)
} else {
handler.removeCallbacks(drawRunner)
}
}

override fun onSurfaceDestroyed(holder: SurfaceHolder) {
super.onSurfaceDestroyed(holder)
isVisible = false
handler.removeCallbacks(drawRunner)
}

private fun draw() {
val holder = surfaceHolder
var canvas: Canvas? = null
try {
canvas = holder.lockCanvas()
if (canvas != null) {
// For now, just draw a color
canvas.drawColor(Color.BLACK)
}
} finally {
if (canvas != null) {
holder.unlockCanvasAndPost(canvas)
}
}

handler.removeCallbacks(drawRunner)
if (isVisible) {
handler.postDelayed(drawRunner, 1000L / 60L) // 60fps
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package tt.co.jesses.moonlight.widget

import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.toArgb
import androidx.glance.BitmapImageProvider
import androidx.glance.GlanceId
import androidx.glance.Image
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.provideContent
import androidx.glance.layout.Alignment
import androidx.glance.layout.Box
import androidx.glance.layout.fillMaxSize
import androidx.glance.text.Text
import tt.co.jesses.moonlight.common.util.GradientUtil
import tt.co.jesses.moonlight.common.util.drawAngledGradient

class MoonlightWidget : GlanceAppWidget() {
override suspend fun provideGlance(context: Context, id: GlanceId) {
provideContent {
MoonlightWidgetContent(context)
}
}

@Composable
fun MoonlightWidgetContent(context: Context) {
val width = 256
val height = 256
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)

drawAngledGradient(
degrees = 270f,
canvas = canvas,
colors = GradientUtil.generateHSLColor().map { it.toArgb() }
)

Box(
modifier = androidx.glance.GlanceModifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Image(
provider = BitmapImageProvider(bitmap),
contentDescription = "Moonlight gradient background",
)
Text("Moonlight Widget")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package tt.co.jesses.moonlight.widget

import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver

class MoonlightWidgetReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget = MoonlightWidget()
}
4 changes: 4 additions & 0 deletions app/widget/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="wallpaper_name">moonlight</string>
</resources>
4 changes: 4 additions & 0 deletions app/widget/src/main/res/xml/moonlight_wallpaper_info.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/wallpaper_name"
android:thumbnail="@android:drawable/ic_menu_gallery" />
9 changes: 9 additions & 0 deletions app/widget/src/main/res/xml/moonlight_widget_info.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="40dp"
android:minHeight="40dp"
android:updatePeriodMillis="86400000"
android:initialLayout="@layout/glance_default_loading_layout"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen">
</appwidget-provider>