diff --git a/app/androidApp/build.gradle.kts b/app/androidApp/build.gradle.kts index efcc0bb..6a7e689 100644 --- a/app/androidApp/build.gradle.kts +++ b/app/androidApp/build.gradle.kts @@ -23,7 +23,7 @@ android { buildConfig = true } composeOptions { - kotlinCompilerExtensionVersion = "1.4.7" + kotlinCompilerExtensionVersion = "1.5.1" } packaging { resources { @@ -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 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5e969af..8fa5b61 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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) } diff --git a/app/common/build.gradle.kts b/app/common/build.gradle.kts index df3b4c5..97b3804 100644 --- a/app/common/build.gradle.kts +++ b/app/common/build.gradle.kts @@ -17,6 +17,7 @@ android { buildFeatures { buildConfig = true + compose = true } buildTypes { @@ -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") diff --git a/app/common/src/main/java/tt/co/jesses/moonlight/common/util/GradientUtil.kt b/app/common/src/main/java/tt/co/jesses/moonlight/common/util/GradientUtil.kt new file mode 100644 index 0000000..58a8707 --- /dev/null +++ b/app/common/src/main/java/tt/co/jesses/moonlight/common/util/GradientUtil.kt @@ -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 { + return listOf(hsl, silverColor, lsb) + } +} + +fun drawAngledGradient(degrees: Float, canvas: Canvas, colors: List) { + 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) +} diff --git a/app/gradle/wrapper/gradle-wrapper.properties b/app/gradle/wrapper/gradle-wrapper.properties index 3aabb17..36e4933 100644 --- a/app/gradle/wrapper/gradle-wrapper.properties +++ b/app/gradle/wrapper/gradle-wrapper.properties @@ -6,5 +6,3 @@ networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists - - diff --git a/app/settings.gradle.kts b/app/settings.gradle.kts index 044ae01..043dbce 100644 --- a/app/settings.gradle.kts +++ b/app/settings.gradle.kts @@ -18,3 +18,4 @@ include(":androidApp") include(":shared") include(":common") include(":wearApp") +include(":widget") diff --git a/app/wearApp/build.gradle.kts b/app/wearApp/build.gradle.kts index 4d6eb41..e7cc423 100644 --- a/app/wearApp/build.gradle.kts +++ b/app/wearApp/build.gradle.kts @@ -20,7 +20,7 @@ android { compose = true } composeOptions { - kotlinCompilerExtensionVersion = "1.4.7" + kotlinCompilerExtensionVersion = "1.5.1" } packaging { @@ -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 diff --git a/app/widget/build.gradle.kts b/app/widget/build.gradle.kts new file mode 100644 index 0000000..6d5b256 --- /dev/null +++ b/app/widget/build.gradle.kts @@ -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") +} diff --git a/app/widget/src/main/AndroidManifest.xml b/app/widget/src/main/AndroidManifest.xml new file mode 100644 index 0000000..54ebda1 --- /dev/null +++ b/app/widget/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/widget/src/main/java/tt/co/jesses/moonlight/widget/MoonlightWallpaperService.kt b/app/widget/src/main/java/tt/co/jesses/moonlight/widget/MoonlightWallpaperService.kt new file mode 100644 index 0000000..703295f --- /dev/null +++ b/app/widget/src/main/java/tt/co/jesses/moonlight/widget/MoonlightWallpaperService.kt @@ -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 + } + } + } +} diff --git a/app/widget/src/main/java/tt/co/jesses/moonlight/widget/MoonlightWidget.kt b/app/widget/src/main/java/tt/co/jesses/moonlight/widget/MoonlightWidget.kt new file mode 100644 index 0000000..5bc4c76 --- /dev/null +++ b/app/widget/src/main/java/tt/co/jesses/moonlight/widget/MoonlightWidget.kt @@ -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") + } + } +} diff --git a/app/widget/src/main/java/tt/co/jesses/moonlight/widget/MoonlightWidgetReceiver.kt b/app/widget/src/main/java/tt/co/jesses/moonlight/widget/MoonlightWidgetReceiver.kt new file mode 100644 index 0000000..8476973 --- /dev/null +++ b/app/widget/src/main/java/tt/co/jesses/moonlight/widget/MoonlightWidgetReceiver.kt @@ -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() +} diff --git a/app/widget/src/main/res/values/strings.xml b/app/widget/src/main/res/values/strings.xml new file mode 100644 index 0000000..56bf6ab --- /dev/null +++ b/app/widget/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + moonlight + diff --git a/app/widget/src/main/res/xml/moonlight_wallpaper_info.xml b/app/widget/src/main/res/xml/moonlight_wallpaper_info.xml new file mode 100644 index 0000000..d84bdef --- /dev/null +++ b/app/widget/src/main/res/xml/moonlight_wallpaper_info.xml @@ -0,0 +1,4 @@ + + diff --git a/app/widget/src/main/res/xml/moonlight_widget_info.xml b/app/widget/src/main/res/xml/moonlight_widget_info.xml new file mode 100644 index 0000000..311dab4 --- /dev/null +++ b/app/widget/src/main/res/xml/moonlight_widget_info.xml @@ -0,0 +1,9 @@ + + +