neiam
/neiam-devices
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
Log in to mark this repository.
Add an Android live wallpaper version in droid/
3f7bd79 · Gabriel Morell · 2026-09-29 23:17
Message
{commit_body(@commit)}
Files changed
added
droid/.gitignore
+12
−0
| 1 | +*.iml | |
| 2 | +.gradle | |
| 3 | +.kotlin | |
| 4 | +.idea | |
| 5 | +/local.properties | |
| 6 | +/build | |
| 7 | +/app/build | |
| 8 | +.externalNativeBuild | |
| 9 | +.cxx | |
| 10 | +*.jks | |
| 11 | +*.pass | |
| 12 | +/apk |
added
droid/app/build.gradle.kts
+68
−0
| 1 | +plugins { | |
| 2 | + alias(libs.plugins.android.application) | |
| 3 | +} | |
| 4 | + | |
| 5 | +// Bumped by hand; everything after it is generated. | |
| 6 | +val baseVersion = "0.1.0" | |
| 7 | + | |
| 8 | +// Seconds, from the environment when it's there. CI sets it once per run | |
| 9 | +// so every artifact from that run agrees; a local build computes it, | |
| 10 | +// which is fine because local builds are never published. | |
| 11 | +val buildTimestamp: Long = | |
| 12 | + (System.getenv("SOURCE_DATE_EPOCH") ?: System.getenv("BUILD_TIMESTAMP")) | |
| 13 | + ?.toLongOrNull() | |
| 14 | + ?: (System.currentTimeMillis() / 1000) | |
| 15 | + | |
| 16 | +val appVersionName: String = | |
| 17 | + System.getenv("RELEASE_VERSION")?.takeIf { it.isNotBlank() } | |
| 18 | + ?: "$baseVersion-dev-$buildTimestamp" | |
| 19 | + | |
| 20 | +android { | |
| 21 | + namespace = "io.neiam.moire" | |
| 22 | + compileSdk { | |
| 23 | + version = release(36) { | |
| 24 | + minorApiLevel = 1 | |
| 25 | + } | |
| 26 | + } | |
| 27 | + | |
| 28 | + defaultConfig { | |
| 29 | + applicationId = "io.neiam.moire" | |
| 30 | + // Adaptive icons, and OpenGL ES 3.0 on every device. | |
| 31 | + minSdk = 26 | |
| 32 | + targetSdk = 36 | |
| 33 | + // Android refuses an update whose versionCode isn't higher than | |
| 34 | + // what's installed, and F-Droid orders versions by it. The CI run | |
| 35 | + // number is monotonic and needs no state; local builds stay at 1. | |
| 36 | + versionCode = (System.getenv("GITHUB_RUN_NUMBER") ?: "").toIntOrNull() ?: 1 | |
| 37 | + versionName = appVersionName | |
| 38 | + } | |
| 39 | + | |
| 40 | + // The shader is the desktop wallpaper's, read as it is: one source | |
| 41 | + // for both, so they can't drift apart. Shader.kt adapts its Qt | |
| 42 | + // header to OpenGL ES at load time. | |
| 43 | + sourceSets["main"].assets.srcDir("../../moire/shaders") | |
| 44 | + | |
| 45 | + buildTypes { | |
| 46 | + release { | |
| 47 | + optimization { | |
| 48 | + enable = false | |
| 49 | + } | |
| 50 | + } | |
| 51 | + } | |
| 52 | + compileOptions { | |
| 53 | + sourceCompatibility = JavaVersion.VERSION_11 | |
| 54 | + targetCompatibility = JavaVersion.VERSION_11 | |
| 55 | + } | |
| 56 | + lint { | |
| 57 | + // `assembleRelease` is a packaging step, not a review gate; lint | |
| 58 | + // still runs on demand with ./gradlew :app:lint. | |
| 59 | + checkReleaseBuilds = false | |
| 60 | + } | |
| 61 | +} | |
| 62 | + | |
| 63 | +dependencies { | |
| 64 | + implementation(libs.androidx.core.ktx) | |
| 65 | + implementation(libs.androidx.appcompat) | |
| 66 | + implementation(libs.androidx.preference) | |
| 67 | + testImplementation(libs.junit) | |
| 68 | +} |
added
droid/app/src/main/AndroidManifest.xml
+41
−0
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 3 | + | |
| 4 | + <!-- Only for "Use this device's location", which fills in latitude | |
| 5 | + and longitude once; the wallpaper itself never asks. --> | |
| 6 | + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> | |
| 7 | + | |
| 8 | + <uses-feature android:glEsVersion="0x00030000" android:required="true" /> | |
| 9 | + <uses-feature android:name="android.software.live_wallpaper" android:required="true" /> | |
| 10 | + | |
| 11 | + <application | |
| 12 | + android:allowBackup="true" | |
| 13 | + android:icon="@mipmap/ic_launcher" | |
| 14 | + android:label="@string/app_name" | |
| 15 | + android:supportsRtl="true" | |
| 16 | + android:theme="@style/Theme.Moire"> | |
| 17 | + | |
| 18 | + <activity | |
| 19 | + android:name=".SettingsActivity" | |
| 20 | + android:exported="true" | |
| 21 | + android:label="@string/app_name"> | |
| 22 | + <intent-filter> | |
| 23 | + <action android:name="android.intent.action.MAIN" /> | |
| 24 | + <category android:name="android.intent.category.LAUNCHER" /> | |
| 25 | + </intent-filter> | |
| 26 | + </activity> | |
| 27 | + | |
| 28 | + <service | |
| 29 | + android:name=".MoireWallpaperService" | |
| 30 | + android:exported="true" | |
| 31 | + android:label="@string/app_name" | |
| 32 | + android:permission="android.permission.BIND_WALLPAPER"> | |
| 33 | + <intent-filter> | |
| 34 | + <action android:name="android.service.wallpaper.WallpaperService" /> | |
| 35 | + </intent-filter> | |
| 36 | + <meta-data | |
| 37 | + android:name="android.service.wallpaper" | |
| 38 | + android:resource="@xml/wallpaper" /> | |
| 39 | + </service> | |
| 40 | + </application> | |
| 41 | +</manifest> |
added
droid/app/src/main/assets/licenses/B612-OFL.txt
+93
−0
| 1 | +Copyright 2012 The B612 Project Authors (https://github.com/polarsys/b612) | |
| 2 | + | |
| 3 | +This Font Software is licensed under the SIL Open Font License, Version 1.1. | |
| 4 | +This license is copied below, and is also available with a FAQ at: | |
| 5 | +http://scripts.sil.org/OFL | |
| 6 | + | |
| 7 | + | |
| 8 | +----------------------------------------------------------- | |
| 9 | +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 | |
| 10 | +----------------------------------------------------------- | |
| 11 | + | |
| 12 | +PREAMBLE | |
| 13 | +The goals of the Open Font License (OFL) are to stimulate worldwide | |
| 14 | +development of collaborative font projects, to support the font creation | |
| 15 | +efforts of academic and linguistic communities, and to provide a free and | |
| 16 | +open framework in which fonts may be shared and improved in partnership | |
| 17 | +with others. | |
| 18 | + | |
| 19 | +The OFL allows the licensed fonts to be used, studied, modified and | |
| 20 | +redistributed freely as long as they are not sold by themselves. The | |
| 21 | +fonts, including any derivative works, can be bundled, embedded, | |
| 22 | +redistributed and/or sold with any software provided that any reserved | |
| 23 | +names are not used by derivative works. The fonts and derivatives, | |
| 24 | +however, cannot be released under any other type of license. The | |
| 25 | +requirement for fonts to remain under this license does not apply | |
| 26 | +to any document created using the fonts or their derivatives. | |
| 27 | + | |
| 28 | +DEFINITIONS | |
| 29 | +"Font Software" refers to the set of files released by the Copyright | |
| 30 | +Holder(s) under this license and clearly marked as such. This may | |
| 31 | +include source files, build scripts and documentation. | |
| 32 | + | |
| 33 | +"Reserved Font Name" refers to any names specified as such after the | |
| 34 | +copyright statement(s). | |
| 35 | + | |
| 36 | +"Original Version" refers to the collection of Font Software components as | |
| 37 | +distributed by the Copyright Holder(s). | |
| 38 | + | |
| 39 | +"Modified Version" refers to any derivative made by adding to, deleting, | |
| 40 | +or substituting -- in part or in whole -- any of the components of the | |
| 41 | +Original Version, by changing formats or by porting the Font Software to a | |
| 42 | +new environment. | |
| 43 | + | |
| 44 | +"Author" refers to any designer, engineer, programmer, technical | |
| 45 | +writer or other person who contributed to the Font Software. | |
| 46 | + | |
| 47 | +PERMISSION & CONDITIONS | |
| 48 | +Permission is hereby granted, free of charge, to any person obtaining | |
| 49 | +a copy of the Font Software, to use, study, copy, merge, embed, modify, | |
| 50 | +redistribute, and sell modified and unmodified copies of the Font | |
| 51 | +Software, subject to the following conditions: | |
| 52 | + | |
| 53 | +1) Neither the Font Software nor any of its individual components, | |
| 54 | +in Original or Modified Versions, may be sold by itself. | |
| 55 | + | |
| 56 | +2) Original or Modified Versions of the Font Software may be bundled, | |
| 57 | +redistributed and/or sold with any software, provided that each copy | |
| 58 | +contains the above copyright notice and this license. These can be | |
| 59 | +included either as stand-alone text files, human-readable headers or | |
| 60 | +in the appropriate machine-readable metadata fields within text or | |
| 61 | +binary files as long as those fields can be easily viewed by the user. | |
| 62 | + | |
| 63 | +3) No Modified Version of the Font Software may use the Reserved Font | |
| 64 | +Name(s) unless explicit written permission is granted by the corresponding | |
| 65 | +Copyright Holder. This restriction only applies to the primary font name as | |
| 66 | +presented to the users. | |
| 67 | + | |
| 68 | +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font | |
| 69 | +Software shall not be used to promote, endorse or advertise any | |
| 70 | +Modified Version, except to acknowledge the contribution(s) of the | |
| 71 | +Copyright Holder(s) and the Author(s) or with their explicit written | |
| 72 | +permission. | |
| 73 | + | |
| 74 | +5) The Font Software, modified or unmodified, in part or in whole, | |
| 75 | +must be distributed entirely under this license, and must not be | |
| 76 | +distributed under any other license. The requirement for fonts to | |
| 77 | +remain under this license does not apply to any document created | |
| 78 | +using the Font Software. | |
| 79 | + | |
| 80 | +TERMINATION | |
| 81 | +This license becomes null and void if any of the above conditions are | |
| 82 | +not met. | |
| 83 | + | |
| 84 | +DISCLAIMER | |
| 85 | +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 86 | +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF | |
| 87 | +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT | |
| 88 | +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE | |
| 89 | +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
| 90 | +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL | |
| 91 | +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| 92 | +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM | |
| 93 | +OTHER DEALINGS IN THE FONT SOFTWARE. |
added
droid/app/src/main/java/io/neiam/moire/FieldRenderer.kt
+200
−0
| 1 | +package io.neiam.moire | |
| 2 | + | |
| 3 | +import android.content.Context | |
| 4 | +import android.opengl.GLES30 | |
| 5 | +import android.opengl.GLSurfaceView | |
| 6 | +import android.opengl.GLUtils | |
| 7 | +import android.util.Log | |
| 8 | +import java.nio.ByteBuffer | |
| 9 | +import java.nio.ByteOrder | |
| 10 | +import javax.microedition.khronos.egl.EGLConfig | |
| 11 | +import javax.microedition.khronos.opengles.GL10 | |
| 12 | + | |
| 13 | +/** | |
| 14 | + * Draws the field with the desktop's shader, then the wordmark over it. | |
| 15 | + * Runs on GLSurfaceView's thread; settings arrive via [update], queued | |
| 16 | + * onto that thread by the engine. | |
| 17 | + */ | |
| 18 | +class FieldRenderer(private val context: Context) : GLSurfaceView.Renderer { | |
| 19 | + private val motion = Motion() | |
| 20 | + private val wordmark = Wordmark(context) | |
| 21 | + | |
| 22 | + private var settings = Settings() | |
| 23 | + private var scheme: Schedule.Scheme = Schedule.scheme(settings, System.currentTimeMillis()) | |
| 24 | + private var schemeAt = 0L | |
| 25 | + | |
| 26 | + /** Called on the GL thread whenever the scheme changes, for | |
| 27 | + * WallpaperColors. */ | |
| 28 | + var onScheme: ((Schedule.Scheme) -> Unit)? = null | |
| 29 | + | |
| 30 | + private var width = 0 | |
| 31 | + private var height = 0 | |
| 32 | + private var lastFrame = 0L | |
| 33 | + | |
| 34 | + private var field = 0 | |
| 35 | + private var fieldUniforms = HashMap<String, Int>() | |
| 36 | + private var mark = 0 | |
| 37 | + private var markTexture = 0 | |
| 38 | + private var markImage: Wordmark.Image? = null | |
| 39 | + private var markKey: Triple<Int, Int, Double>? = null | |
| 40 | + | |
| 41 | + val settling get() = motion.settling | |
| 42 | + | |
| 43 | + fun update(s: Settings) { | |
| 44 | + settings = s | |
| 45 | + refreshScheme(force = true) | |
| 46 | + } | |
| 47 | + | |
| 48 | + private fun refreshScheme(force: Boolean) { | |
| 49 | + val now = System.currentTimeMillis() | |
| 50 | + // The sun moves slowly enough that once a minute is smooth, and | |
| 51 | + // Motion eases between the steps. | |
| 52 | + if (!force && now - schemeAt < 60_000) return | |
| 53 | + val next = Schedule.scheme(settings, now) | |
| 54 | + schemeAt = now | |
| 55 | + if (next != scheme) { | |
| 56 | + scheme = next | |
| 57 | + onScheme?.invoke(next) | |
| 58 | + } | |
| 59 | + } | |
| 60 | + | |
| 61 | + private val quad = ByteBuffer.allocateDirect(8 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer() | |
| 62 | + .apply { put(floatArrayOf(-1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f)); position(0) } | |
| 63 | + | |
| 64 | + override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) { | |
| 65 | + val source = context.assets.open("field.frag").bufferedReader().use { it.readText() } | |
| 66 | + field = program(Shader.VERTEX, Shader.toGles(source)) | |
| 67 | + fieldUniforms.clear() | |
| 68 | + mark = program(MARK_VERTEX, MARK_FRAGMENT) | |
| 69 | + markTexture = IntArray(1).also { GLES30.glGenTextures(1, it, 0) }[0] | |
| 70 | + markKey = null | |
| 71 | + lastFrame = 0 | |
| 72 | + } | |
| 73 | + | |
| 74 | + override fun onSurfaceChanged(gl: GL10?, w: Int, h: Int) { | |
| 75 | + width = w | |
| 76 | + height = h | |
| 77 | + GLES30.glViewport(0, 0, w, h) | |
| 78 | + } | |
| 79 | + | |
| 80 | + override fun onDrawFrame(gl: GL10?) { | |
| 81 | + val now = System.nanoTime() | |
| 82 | + val dt = if (lastFrame == 0L) 0.0 else (now - lastFrame) / 1e9 | |
| 83 | + lastFrame = now | |
| 84 | + | |
| 85 | + refreshScheme(force = false) | |
| 86 | + motion.advance(dt, settings, scheme) | |
| 87 | + | |
| 88 | + GLES30.glDisable(GLES30.GL_BLEND) | |
| 89 | + GLES30.glUseProgram(field) | |
| 90 | + for ((name, v) in motion.uniforms(width, height, settings)) { | |
| 91 | + val at = fieldUniforms.getOrPut(name) { GLES30.glGetUniformLocation(field, name) } | |
| 92 | + if (at < 0) continue | |
| 93 | + when (v.size) { | |
| 94 | + 1 -> GLES30.glUniform1f(at, v[0]) | |
| 95 | + 2 -> GLES30.glUniform2f(at, v[0], v[1]) | |
| 96 | + 4 -> GLES30.glUniform4f(at, v[0], v[1], v[2], v[3]) | |
| 97 | + } | |
| 98 | + } | |
| 99 | + drawQuad(field) | |
| 100 | + | |
| 101 | + if (settings.wordmark) drawWordmark() | |
| 102 | + } | |
| 103 | + | |
| 104 | + private fun drawWordmark() { | |
| 105 | + val key = Triple(width, height, settings.wordmarkGlow) | |
| 106 | + if (key != markKey) { | |
| 107 | + markImage?.bitmap?.recycle() | |
| 108 | + markImage = wordmark.render(width, height, settings.wordmarkGlow) | |
| 109 | + GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, markTexture) | |
| 110 | + GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR) | |
| 111 | + GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR) | |
| 112 | + GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE) | |
| 113 | + GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE) | |
| 114 | + GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, markImage!!.bitmap, 0) | |
| 115 | + markKey = key | |
| 116 | + } | |
| 117 | + val image = markImage ?: return | |
| 118 | + | |
| 119 | + GLES30.glEnable(GLES30.GL_BLEND) | |
| 120 | + // The bitmap is premultiplied, as Android's always are. | |
| 121 | + GLES30.glBlendFunc(GLES30.GL_ONE, GLES30.GL_ONE_MINUS_SRC_ALPHA) | |
| 122 | + GLES30.glUseProgram(mark) | |
| 123 | + GLES30.glActiveTexture(GLES30.GL_TEXTURE0) | |
| 124 | + GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, markTexture) | |
| 125 | + GLES30.glUniform1i(GLES30.glGetUniformLocation(mark, "image"), 0) | |
| 126 | + // Where the bitmap sits, as a rectangle in clip space. | |
| 127 | + val x0 = image.left * 2f / width - 1 | |
| 128 | + val x1 = (image.left + image.bitmap.width) * 2f / width - 1 | |
| 129 | + val y0 = 1 - (image.top + image.bitmap.height) * 2f / height | |
| 130 | + val y1 = 1 - image.top * 2f / height | |
| 131 | + GLES30.glUniform4f(GLES30.glGetUniformLocation(mark, "rect"), x0, y0, x1, y1) | |
| 132 | + val ink = motion.ink | |
| 133 | + GLES30.glUniform4f( | |
| 134 | + GLES30.glGetUniformLocation(mark, "ink"), | |
| 135 | + ink.r, ink.g, ink.b, settings.wordmarkOpacity.toFloat(), | |
| 136 | + ) | |
| 137 | + drawQuad(mark) | |
| 138 | + } | |
| 139 | + | |
| 140 | + private fun drawQuad(program: Int) { | |
| 141 | + val at = GLES30.glGetAttribLocation(program, "position") | |
| 142 | + GLES30.glEnableVertexAttribArray(at) | |
| 143 | + GLES30.glVertexAttribPointer(at, 2, GLES30.GL_FLOAT, false, 0, quad) | |
| 144 | + GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4) | |
| 145 | + GLES30.glDisableVertexAttribArray(at) | |
| 146 | + } | |
| 147 | + | |
| 148 | + private fun program(vertex: String, fragment: String): Int { | |
| 149 | + fun compile(type: Int, src: String): Int { | |
| 150 | + val shader = GLES30.glCreateShader(type) | |
| 151 | + GLES30.glShaderSource(shader, src) | |
| 152 | + GLES30.glCompileShader(shader) | |
| 153 | + val ok = IntArray(1).also { GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, it, 0) } | |
| 154 | + if (ok[0] == 0) { | |
| 155 | + val log = GLES30.glGetShaderInfoLog(shader) | |
| 156 | + Log.e(TAG, "shader failed to compile: $log") | |
| 157 | + error("shader failed to compile: $log") | |
| 158 | + } | |
| 159 | + return shader | |
| 160 | + } | |
| 161 | + val p = GLES30.glCreateProgram() | |
| 162 | + GLES30.glAttachShader(p, compile(GLES30.GL_VERTEX_SHADER, vertex)) | |
| 163 | + GLES30.glAttachShader(p, compile(GLES30.GL_FRAGMENT_SHADER, fragment)) | |
| 164 | + GLES30.glLinkProgram(p) | |
| 165 | + val ok = IntArray(1).also { GLES30.glGetProgramiv(p, GLES30.GL_LINK_STATUS, it, 0) } | |
| 166 | + if (ok[0] == 0) error("program failed to link: ${GLES30.glGetProgramInfoLog(p)}") | |
| 167 | + return p | |
| 168 | + } | |
| 169 | + | |
| 170 | + companion object { | |
| 171 | + private const val TAG = "neiam-moire" | |
| 172 | + | |
| 173 | + private const val MARK_VERTEX = """#version 300 es | |
| 174 | +in vec2 position; | |
| 175 | +uniform vec4 rect; // x0, y0, x1, y1 in clip space | |
| 176 | +out vec2 uv; | |
| 177 | +void main() | |
| 178 | +{ | |
| 179 | + vec2 t = position * 0.5 + 0.5; | |
| 180 | + uv = vec2(t.x, 1.0 - t.y); | |
| 181 | + gl_Position = vec4(mix(rect.xy, rect.zw, t), 0.0, 1.0); | |
| 182 | +} | |
| 183 | +""" | |
| 184 | + | |
| 185 | + // The bitmap is white with coverage in alpha; tint it with the | |
| 186 | + // ink colour, and apply the opacity to letters and glow together. | |
| 187 | + private const val MARK_FRAGMENT = """#version 300 es | |
| 188 | +precision mediump float; | |
| 189 | +in vec2 uv; | |
| 190 | +uniform sampler2D image; | |
| 191 | +uniform vec4 ink; // rgb, and the wordmark's opacity in a | |
| 192 | +out vec4 fragColor; | |
| 193 | +void main() | |
| 194 | +{ | |
| 195 | + float a = texture(image, uv).a * ink.a; | |
| 196 | + fragColor = vec4(ink.rgb * a, a); | |
| 197 | +} | |
| 198 | +""" | |
| 199 | + } | |
| 200 | +} |
added
droid/app/src/main/java/io/neiam/moire/MoireWallpaperService.kt
+116
−0
Click to load diff…
added
droid/app/src/main/java/io/neiam/moire/Motion.kt
+144
−0
Click to load diff…
added
droid/app/src/main/java/io/neiam/moire/Schedule.kt
+186
−0
Click to load diff…
added
droid/app/src/main/java/io/neiam/moire/Settings.kt
+97
−0
Click to load diff…
added
droid/app/src/main/java/io/neiam/moire/SettingsActivity.kt
+170
−0
Click to load diff…
added
droid/app/src/main/java/io/neiam/moire/Shader.kt
+48
−0
Click to load diff…
added
droid/app/src/main/java/io/neiam/moire/Themes.kt
+69
−0
Click to load diff…
added
droid/app/src/main/java/io/neiam/moire/Wordmark.kt
+56
−0
Click to load diff…
modified
?
+0
−0
Click to load diff…
added
droid/app/src/main/res/drawable/ic_foreground.xml
+79
−0
Click to load diff…
modified
?
+0
−0
Click to load diff…
added
droid/app/src/main/res/mipmap-anydpi/ic_launcher.xml
+6
−0
Click to load diff…
added
droid/app/src/main/res/values/arrays.xml
+44
−0
Click to load diff…
added
droid/app/src/main/res/values/colors.xml
+4
−0
Click to load diff…
added
droid/app/src/main/res/values/strings.xml
+5
−0
Click to load diff…
added
droid/app/src/main/res/values/themes.xml
+8
−0
Click to load diff…
added
droid/app/src/main/res/xml/preferences.xml
+140
−0
Click to load diff…
added
droid/app/src/main/res/xml/wallpaper.xml
+5
−0
Click to load diff…
added
droid/app/src/test/java/io/neiam/moire/ScheduleTest.kt
+53
−0
Click to load diff…
added
droid/app/src/test/java/io/neiam/moire/ShaderTest.kt
+41
−0
Click to load diff…
added
droid/build.gradle.kts
+3
−0
Click to load diff…
added
droid/gradle.properties
+3
−0
Click to load diff…
added
droid/gradle/gradle-daemon-jvm.properties
+12
−0
Click to load diff…
added
droid/gradle/libs.versions.toml
+15
−0
Click to load diff…
modified
?
+0
−0
Click to load diff…
added
droid/gradle/wrapper/gradle-wrapper.properties
+8
−0
Click to load diff…
added
droid/gradlew
+251
−0
Click to load diff…
added
droid/gradlew.bat
+94
−0
Click to load diff…
added
droid/settings.gradle.kts
+26
−0
Click to load diff…
modified
tools/themes.py
+31
−4
Click to load diff…
Parents: 56cf58f