summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMinteck <contact@minteck.org>2023-02-22 18:05:01 +0100
committerMinteck <contact@minteck.org>2023-02-22 18:05:01 +0100
commite379e0e9d1188b9e04e6055736112df30d6c9591 (patch)
treef44f0fa22e73d31d87602e11ffaaa9120280021e
parent72011e3cc3ee500a2825204fe4dad9dcb9bfd923 (diff)
downloadponywatch-e379e0e9d1188b9e04e6055736112df30d6c9591.tar.gz
ponywatch-e379e0e9d1188b9e04e6055736112df30d6c9591.tar.bz2
ponywatch-e379e0e9d1188b9e04e6055736112df30d6c9591.zip
Updated 9 files, added 32 files and deleted 5 files (automated)
-rw-r--r--.idea/compiler.xml2
-rw-r--r--.idea/gradle.xml6
-rw-r--r--.idea/kotlinc.xml6
-rw-r--r--.idea/misc.xml2
-rw-r--r--app/build.gradle10
-rw-r--r--app/src/main/AndroidManifest.xml78
-rw-r--r--app/src/main/ic_launcher-playstore.pngbin0 -> 35992 bytes
-rw-r--r--app/src/main/java/dev/equestria/ponywatch/AlicornFace.kt56
-rw-r--r--app/src/main/java/dev/equestria/ponywatch/CutiemarkFace.kt54
-rw-r--r--app/src/main/java/dev/equestria/ponywatch/FeathersFace.kt512
-rw-r--r--app/src/main/java/dev/equestria/ponywatch/GlitterFace.kt512
-rw-r--r--app/src/main/java/dev/equestria/ponywatch/UnityFace.kt675
-rw-r--r--app/src/main/res/drawable-nodpi/preview_feathers.pngbin0 -> 66504 bytes
-rw-r--r--app/src/main/res/drawable-nodpi/preview_glitter.pngbin0 -> 57163 bytes
-rw-r--r--app/src/main/res/drawable-nodpi/preview_unity.pngbin0 -> 57983 bytes
-rw-r--r--app/src/main/res/drawable-nodpi/watchface_feathers_bg.pngbin0 -> 48160 bytes
-rw-r--r--app/src/main/res/drawable-nodpi/watchface_glitter_bg.pngbin0 -> 38498 bytes
-rw-r--r--app/src/main/res/drawable-nodpi/watchface_unity_bg_0.pngbin0 -> 57674 bytes
-rw-r--r--app/src/main/res/drawable-nodpi/watchface_unity_bg_1.pngbin0 -> 53202 bytes
-rw-r--r--app/src/main/res/drawable-nodpi/watchface_unity_bg_2.pngbin0 -> 52749 bytes
-rw-r--r--app/src/main/res/drawable-nodpi/watchface_unity_bg_3.pngbin0 -> 49575 bytes
-rw-r--r--app/src/main/res/drawable-nodpi/watchface_unity_bg_4.pngbin0 -> 49433 bytes
-rw-r--r--app/src/main/res/drawable-nodpi/watchface_unity_bg_5.pngbin0 -> 49341 bytes
-rw-r--r--app/src/main/res/drawable-nodpi/watchface_unity_bg_6.pngbin0 -> 43922 bytes
-rw-r--r--app/src/main/res/drawable/ic_launcher_background.xml26
-rw-r--r--app/src/main/res/drawable/ic_launcher_foreground.xml92
-rwxr-xr-xapp/src/main/res/font/general.ttfbin0 -> 109128 bytes
-rw-r--r--app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml5
-rw-r--r--app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml5
-rw-r--r--app/src/main/res/mipmap-hdpi/ic_launcher.pngbin0 -> 5474 bytes
-rw-r--r--app/src/main/res/mipmap-hdpi/ic_launcher.webpbin1404 -> 0 bytes
-rw-r--r--app/src/main/res/mipmap-hdpi/ic_launcher_round.pngbin0 -> 5474 bytes
-rw-r--r--app/src/main/res/mipmap-mdpi/ic_launcher.pngbin0 -> 3416 bytes
-rw-r--r--app/src/main/res/mipmap-mdpi/ic_launcher.webpbin982 -> 0 bytes
-rw-r--r--app/src/main/res/mipmap-mdpi/ic_launcher_round.pngbin0 -> 3416 bytes
-rw-r--r--app/src/main/res/mipmap-xhdpi/ic_launcher.pngbin0 -> 7846 bytes
-rw-r--r--app/src/main/res/mipmap-xhdpi/ic_launcher.webpbin1900 -> 0 bytes
-rw-r--r--app/src/main/res/mipmap-xhdpi/ic_launcher_round.pngbin0 -> 7846 bytes
-rw-r--r--app/src/main/res/mipmap-xxhdpi/ic_launcher.pngbin0 -> 12407 bytes
-rw-r--r--app/src/main/res/mipmap-xxhdpi/ic_launcher.webpbin2884 -> 0 bytes
-rw-r--r--app/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngbin0 -> 12407 bytes
-rw-r--r--app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngbin0 -> 17595 bytes
-rw-r--r--app/src/main/res/mipmap-xxxhdpi/ic_launcher.webpbin3844 -> 0 bytes
-rw-r--r--app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngbin0 -> 17595 bytes
-rw-r--r--app/src/main/res/values/colors.xml24
-rw-r--r--build.gradle4
46 files changed, 2054 insertions, 15 deletions
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index fb7f4a8..b589d56 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
- <bytecodeTargetLevel target="11" />
+ <bytecodeTargetLevel target="17" />
</component>
</project> \ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 07112d9..ae388c2 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -7,11 +7,11 @@
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
- <option name="gradleJvm" value="Embedded JDK" />
+ <option name="gradleJvm" value="jbr-17" />
<option name="modules">
<set>
- <option value="/Volumes/Unicorn/ponywatch" />
- <option value="/Volumes/Unicorn/ponywatch/app" />
+ <option value="$PROJECT_DIR$" />
+ <option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 0000000..e1eea1d
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="KotlinJpsPluginSettings">
+ <option name="version" value="1.7.20" />
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 54d5acd..9f71c83 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
- <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
diff --git a/app/build.gradle b/app/build.gradle
index aeacb81..213a917 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -5,14 +5,14 @@ plugins {
android {
namespace 'dev.equestria.ponywatch'
- compileSdk 32
+ compileSdk 33
defaultConfig {
applicationId "dev.equestria.ponywatch"
minSdk 30
- targetSdk 32
- versionCode 2
- versionName "1.1"
+ targetSdk 33
+ versionCode 3
+ versionName "2.0"
}
@@ -25,7 +25,7 @@ android {
}
dependencies {
-
+ implementation("com.android.volley:volley:1.2.1")
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'com.google.android.gms:play-services-wearable:17.1.0'
implementation 'androidx.percentlayout:percentlayout:1.0.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2c9eaa1..088043f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -72,6 +72,84 @@
</intent-filter>
</service>
+ <service
+ android:name=".FeathersFace"
+ android:exported="true"
+ android:label="Feathers"
+ android:permission="android.permission.BIND_WALLPAPER">
+
+ <meta-data
+ android:name="android.service.wallpaper.square_mode"
+ android:value="false" />
+ <meta-data
+ android:name="android.service.wallpaper"
+ android:resource="@xml/watch_face" />
+ <meta-data
+ android:name="com.google.android.wearable.watchface.preview"
+ android:resource="@drawable/preview_feathers" />
+ <meta-data
+ android:name="com.google.android.wearable.watchface.preview_circular"
+ android:resource="@drawable/preview_feathers" />
+
+ <intent-filter>
+ <action android:name="android.service.wallpaper.WallpaperService" />
+
+ <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
+ </intent-filter>
+ </service>
+
+ <service
+ android:name=".GlitterFace"
+ android:exported="true"
+ android:label="Glitter"
+ android:permission="android.permission.BIND_WALLPAPER">
+
+ <meta-data
+ android:name="android.service.wallpaper.square_mode"
+ android:value="false" />
+ <meta-data
+ android:name="android.service.wallpaper"
+ android:resource="@xml/watch_face" />
+ <meta-data
+ android:name="com.google.android.wearable.watchface.preview"
+ android:resource="@drawable/preview_glitter" />
+ <meta-data
+ android:name="com.google.android.wearable.watchface.preview_circular"
+ android:resource="@drawable/preview_glitter" />
+
+ <intent-filter>
+ <action android:name="android.service.wallpaper.WallpaperService" />
+
+ <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
+ </intent-filter>
+ </service>
+
+ <service
+ android:name=".UnityFace"
+ android:exported="true"
+ android:label="Unity"
+ android:permission="android.permission.BIND_WALLPAPER">
+
+ <meta-data
+ android:name="android.service.wallpaper.square_mode"
+ android:value="false" />
+ <meta-data
+ android:name="android.service.wallpaper"
+ android:resource="@xml/watch_face" />
+ <meta-data
+ android:name="com.google.android.wearable.watchface.preview"
+ android:resource="@drawable/preview_unity" />
+ <meta-data
+ android:name="com.google.android.wearable.watchface.preview_circular"
+ android:resource="@drawable/preview_unity" />
+
+ <intent-filter>
+ <action android:name="android.service.wallpaper.WallpaperService" />
+
+ <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
+ </intent-filter>
+ </service>
+
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 0000000..33dd8f1
--- /dev/null
+++ b/app/src/main/ic_launcher-playstore.png
Binary files differ
diff --git a/app/src/main/java/dev/equestria/ponywatch/AlicornFace.kt b/app/src/main/java/dev/equestria/ponywatch/AlicornFace.kt
index fdc69e9..da60633 100644
--- a/app/src/main/java/dev/equestria/ponywatch/AlicornFace.kt
+++ b/app/src/main/java/dev/equestria/ponywatch/AlicornFace.kt
@@ -18,8 +18,13 @@ import android.widget.Toast
import com.squareup.picasso.MemoryPolicy
import com.squareup.picasso.Picasso
import com.squareup.picasso.Target
+import org.json.JSONObject
import java.lang.ref.WeakReference
import java.util.*
+import kotlin.system.exitProcess
+import com.android.volley.Request
+import com.android.volley.toolbox.JsonObjectRequest
+import com.android.volley.toolbox.Volley
/**
* Updates rate in milliseconds for interactive mode. We update once a second to advance the
@@ -106,7 +111,9 @@ class AlicornFace : CanvasWatchFaceService() {
private var mLowBitAmbient: Boolean = false
private var mBurnInProtection: Boolean = false
+ private var twoFronters: Boolean = false
private var bmp: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.default_pony)
+ private var bmp2: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.default_pony)
/* Handler to update the time once a second in interactive mode. */
private val mUpdateTimeHandler = EngineHandler(this)
@@ -358,6 +365,48 @@ class AlicornFace : CanvasWatchFaceService() {
} catch (ex: IllegalArgumentException) {
Log.e("Picasso", ex.toString())
}
+
+ val volleyQueue = Volley.newRequestQueue(baseContext)
+
+ val jsonObjectRequest = JsonObjectRequest("https://ponies.equestria.horse/api/raindrops-two",
+
+ { response ->
+ Log.i("HTTPRequest", response.toString())
+ twoFronters = response!!.get("multiple") as Boolean
+
+ if (twoFronters) {
+ val target = object : Target {
+ override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) {
+ try {
+ if (bitmap != null) {
+ bmp2 = bitmap
+ bmp2 = Bitmap.createScaledBitmap(
+ bmp2,
+ 48,
+ 48, true
+ )
+ }
+ } catch (ex: IllegalArgumentException) {
+ Log.e("Picasso", ex.toString())
+ }
+ }
+
+ override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) {
+ Log.e("Picasso", e.toString())
+ }
+
+ override fun onPrepareLoad(placeHolderDrawable: Drawable?) {}
+ }
+
+ Picasso.get().load(
+ "https://ponies.equestria.horse/api/raindrops-img2-round"
+ ).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
+ }
+ },
+
+ { error -> Log.e("HTTPRequest", "Request error: ${error.localizedMessage}") })
+
+ volleyQueue.add(jsonObjectRequest)
}
override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) {
@@ -371,7 +420,12 @@ class AlicornFace : CanvasWatchFaceService() {
"https://ponies.equestria.horse/api/raindrops-img-round"
).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
} else {
- canvas.drawBitmap(bmp, mCenterX - 48f/2f, 20f, mBackgroundPaint)
+ if (twoFronters) {
+ canvas.drawBitmap(bmp2, mCenterX + 6f/2f, 20f, mBackgroundPaint)
+ canvas.drawBitmap(bmp, mCenterX - 102f/2f, 20f, mBackgroundPaint)
+ } else {
+ canvas.drawBitmap(bmp, mCenterX - 48f/2f, 20f, mBackgroundPaint)
+ }
}
}
diff --git a/app/src/main/java/dev/equestria/ponywatch/CutiemarkFace.kt b/app/src/main/java/dev/equestria/ponywatch/CutiemarkFace.kt
index 561f785..204a884 100644
--- a/app/src/main/java/dev/equestria/ponywatch/CutiemarkFace.kt
+++ b/app/src/main/java/dev/equestria/ponywatch/CutiemarkFace.kt
@@ -16,6 +16,8 @@ import android.support.wearable.watchface.WatchFaceStyle
import android.util.Log
import android.view.SurfaceHolder
import android.widget.Toast
+import com.android.volley.toolbox.JsonObjectRequest
+import com.android.volley.toolbox.Volley
import com.squareup.picasso.MemoryPolicy
import com.squareup.picasso.Picasso
import com.squareup.picasso.Target
@@ -107,7 +109,9 @@ class CutiemarkFace : CanvasWatchFaceService() {
private var mLowBitAmbient: Boolean = false
private var mBurnInProtection: Boolean = false
+ private var twoFronters: Boolean = false
private var bmp: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.default_pony)
+ private var bmp2: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.default_pony)
/* Handler to update the time once a second in interactive mode. */
private val mUpdateTimeHandler = EngineHandler(this)
@@ -449,6 +453,7 @@ class CutiemarkFace : CanvasWatchFaceService() {
paint.textAlign = Paint.Align.CENTER
paint.textSize = 24f
+ paint.typeface = resources.getFont(R.font.general)
val h = mCalendar.get(Calendar.HOUR_OF_DAY)
val hs = if (h < 10) {
@@ -485,6 +490,48 @@ class CutiemarkFace : CanvasWatchFaceService() {
} catch (ex: IllegalArgumentException) {
Log.e("Picasso", ex.toString())
}
+
+ val volleyQueue = Volley.newRequestQueue(baseContext)
+
+ val jsonObjectRequest = JsonObjectRequest("https://ponies.equestria.horse/api/raindrops-two",
+
+ { response ->
+ Log.i("HTTPRequest", response.toString())
+ twoFronters = response!!.get("multiple") as Boolean
+
+ if (twoFronters) {
+ val target = object : Target {
+ override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) {
+ try {
+ if (bitmap != null) {
+ bmp2 = bitmap
+ bmp2 = Bitmap.createScaledBitmap(
+ bmp2,
+ 48,
+ 48, true
+ )
+ }
+ } catch (ex: IllegalArgumentException) {
+ Log.e("Picasso", ex.toString())
+ }
+ }
+
+ override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) {
+ Log.e("Picasso", e.toString())
+ }
+
+ override fun onPrepareLoad(placeHolderDrawable: Drawable?) {}
+ }
+
+ Picasso.get().load(
+ "https://ponies.equestria.horse/api/raindrops-img2-round"
+ ).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
+ }
+ },
+
+ { error -> Log.e("HTTPRequest", "Request error: ${error.localizedMessage}") })
+
+ volleyQueue.add(jsonObjectRequest)
}
override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) {
@@ -498,7 +545,12 @@ class CutiemarkFace : CanvasWatchFaceService() {
"https://ponies.equestria.horse/api/raindrops-img-round"
).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
} else {
- canvas.drawBitmap(bmp, mCenterX - 48f/2f, 49f, mBackgroundPaint)
+ if (twoFronters) {
+ canvas.drawBitmap(bmp2, mCenterX + 6f/2f, 49f, mBackgroundPaint)
+ canvas.drawBitmap(bmp, mCenterX - 102f/2f, 49f, mBackgroundPaint)
+ } else {
+ canvas.drawBitmap(bmp, mCenterX - 48f/2f, 49f, mBackgroundPaint)
+ }
}
}
diff --git a/app/src/main/java/dev/equestria/ponywatch/FeathersFace.kt b/app/src/main/java/dev/equestria/ponywatch/FeathersFace.kt
new file mode 100644
index 0000000..31ab928
--- /dev/null
+++ b/app/src/main/java/dev/equestria/ponywatch/FeathersFace.kt
@@ -0,0 +1,512 @@
+package dev.equestria.ponywatch
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorMatrix
+import android.graphics.ColorMatrixColorFilter
+import android.graphics.Paint
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.os.Message
+import android.support.wearable.watchface.CanvasWatchFaceService
+import android.support.wearable.watchface.WatchFaceStyle
+import android.util.Log
+import android.view.SurfaceHolder
+import android.widget.Toast
+import com.android.volley.toolbox.JsonObjectRequest
+import com.android.volley.toolbox.Volley
+import com.squareup.picasso.MemoryPolicy
+import com.squareup.picasso.Picasso
+import com.squareup.picasso.Target
+import java.lang.ref.WeakReference
+import java.util.Calendar
+import java.util.TimeZone
+
+/**
+ * Updates rate in milliseconds for interactive mode. We update once a second to advance the
+ * second hand.
+ */
+private const val INTERACTIVE_UPDATE_RATE_MS = 1000
+
+/**
+ * Handler message id for updating the time periodically in interactive mode.
+ */
+private const val MSG_UPDATE_TIME = 0
+
+private const val SHADOW_RADIUS = 3f
+
+/**
+ * Analog watch face with a ticking second hand. In ambient mode, the second hand isn"t
+ * shown. On devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient
+ * mode. The watch face is drawn with less contrast in mute mode.
+ *
+ *
+ * Important Note: Because watch face apps do not have a default Activity in
+ * their project, you will need to set your Configurations to
+ * "Do not launch Activity" for both the Wear and/or Application modules. If you
+ * are unsure how to do this, please review the "Run Starter project" section
+ * in the Google Watch Face Code Lab:
+ * https://codelabs.developers.google.com/codelabs/watchface/index.html#0
+ */
+
+/**
+ * Analog watch face with a ticking second hand. In ambient mode, the second hand isn"t
+ * shown. On devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient
+ * mode. The watch face is drawn with less contrast in mute mode.
+ *
+ *
+ * Important Note: Because watch face apps do not have a default Activity in
+ * their project, you will need to set your Configurations to
+ * "Do not launch Activity" for both the Wear and/or Application modules. If you
+ * are unsure how to do this, please review the "Run Starter project" section
+ * in the Google Watch Face Code Lab:
+ * https://codelabs.developers.google.com/codelabs/watchface/index.html#0
+ */
+class FeathersFace : CanvasWatchFaceService() {
+
+ override fun onCreateEngine(): Engine {
+ return Engine()
+ }
+
+ private class EngineHandler(reference: FeathersFace.Engine) : Handler(Looper.myLooper()!!) {
+ private val mWeakReference: WeakReference<Engine> = WeakReference(reference)
+
+ override fun handleMessage(msg: Message) {
+ val engine = mWeakReference.get()
+ if (engine != null) {
+ when (msg.what) {
+ MSG_UPDATE_TIME -> engine.handleUpdateTimeMessage()
+ }
+ }
+ }
+ }
+
+ inner class Engine : CanvasWatchFaceService.Engine() {
+
+ private lateinit var mCalendar: Calendar
+
+ private var mRegisteredTimeZoneReceiver = false
+ private var mMuteMode: Boolean = false
+ private var mCenterX: Float = 0F
+ private var mCenterY: Float = 0F
+ private var mHeight: Float = 0F
+
+ private var lastRefreshMinute: Int = -1
+
+ private var mSecondHandLength: Float = 0F
+ private var sMinuteHandLength: Float = 0F
+ private var sHourHandLength: Float = 0F
+
+ private var mWatchHandShadowColor: Int = 0
+
+ private lateinit var mBackgroundPaint: Paint
+ private lateinit var mBackgroundBitmap: Bitmap
+ private lateinit var mGrayBackgroundBitmap: Bitmap
+
+ private var mAmbient: Boolean = false
+ private var mLowBitAmbient: Boolean = false
+ private var mBurnInProtection: Boolean = false
+
+ private var twoFronters: Boolean = false
+ private var bmp: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.default_pony)
+ private var bmp2: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.default_pony)
+
+ /* Handler to update the time once a second in interactive mode. */
+ private val mUpdateTimeHandler = EngineHandler(this)
+
+ private val mTimeZoneReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ mCalendar.timeZone = TimeZone.getDefault()
+ invalidate()
+ }
+ }
+
+ override fun onCreate(holder: SurfaceHolder) {
+ super.onCreate(holder)
+
+ setWatchFaceStyle(
+ WatchFaceStyle.Builder(this@FeathersFace)
+ .setAcceptsTapEvents(true)
+ .build()
+ )
+
+ mCalendar = Calendar.getInstance()
+
+ initializeBackground()
+ }
+
+ private fun initializeBackground() {
+ mBackgroundPaint = Paint().apply {
+ color = Color.BLACK
+ }
+ mBackgroundBitmap =
+ BitmapFactory.decodeResource(resources, R.drawable.watchface_feathers_bg)
+ }
+
+ override fun onDestroy() {
+ mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME)
+ super.onDestroy()
+ }
+
+ override fun onPropertiesChanged(properties: Bundle) {
+ super.onPropertiesChanged(properties)
+ mLowBitAmbient = properties.getBoolean(
+ PROPERTY_LOW_BIT_AMBIENT, false
+ )
+ mBurnInProtection = properties.getBoolean(
+ PROPERTY_BURN_IN_PROTECTION, false
+ )
+ }
+
+ override fun onTimeTick() {
+ super.onTimeTick()
+ invalidate()
+ }
+
+ override fun onAmbientModeChanged(inAmbientMode: Boolean) {
+ super.onAmbientModeChanged(inAmbientMode)
+ mAmbient = inAmbientMode
+
+ // Check and trigger whether or not timer should be running (only
+ // in active mode).
+ updateTimer()
+ }
+
+ override fun onInterruptionFilterChanged(interruptionFilter: Int) {
+ super.onInterruptionFilterChanged(interruptionFilter)
+ val inMuteMode = interruptionFilter == INTERRUPTION_FILTER_NONE
+
+ /* Dim display in mute mode. */
+ if (mMuteMode != inMuteMode) {
+ mMuteMode = inMuteMode
+ invalidate()
+ }
+ }
+
+ override fun onSurfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
+ super.onSurfaceChanged(holder, format, width, height)
+
+ /*
+ * Find the coordinates of the center point on the screen, and ignore the window
+ * insets, so that, on round watches with a "chin", the watch face is centered on the
+ * entire screen, not just the usable portion.
+ */
+ mCenterX = width / 2f
+ mCenterY = height / 2f
+ mHeight = height.toFloat()
+
+ /*
+ * Calculate lengths of different hands based on watch screen size.
+ */
+ mSecondHandLength = (mCenterX * 0.875).toFloat()
+ sMinuteHandLength = (mCenterX * 0.75).toFloat()
+ sHourHandLength = (mCenterX * 0.5).toFloat()
+
+ /* Scale loaded background image (more efficient) if surface dimensions change. */
+ val scale = width.toFloat() / mBackgroundBitmap.width.toFloat()
+
+ mBackgroundBitmap = Bitmap.createScaledBitmap(
+ mBackgroundBitmap,
+ (mBackgroundBitmap.width * scale).toInt(),
+ (mBackgroundBitmap.height * scale).toInt(), true
+ )
+
+ /*
+ * Create a gray version of the image only if it will look nice on the device in
+ * ambient mode. That means we don"t want devices that support burn-in
+ * protection (slight movements in pixels, not great for images going all the way to
+ * edges) and low ambient mode (degrades image quality).
+ *
+ * Also, if your watch face will know about all images ahead of time (users aren"t
+ * selecting their own photos for the watch face), it will be more
+ * efficient to create a black/white version (png, etc.) and load that when you need it.
+ */
+ if (!mBurnInProtection && !mLowBitAmbient) {
+ initGrayBackgroundBitmap()
+ }
+ }
+
+ private fun initGrayBackgroundBitmap() {
+ mGrayBackgroundBitmap = Bitmap.createBitmap(
+ mBackgroundBitmap.width,
+ mBackgroundBitmap.height,
+ Bitmap.Config.ARGB_8888
+ )
+ val canvas = Canvas(mGrayBackgroundBitmap)
+ val grayPaint = Paint()
+ val colorMatrix = ColorMatrix()
+ colorMatrix.setSaturation(0f)
+ val filter = ColorMatrixColorFilter(colorMatrix)
+ grayPaint.colorFilter = filter
+ canvas.drawBitmap(mBackgroundBitmap, 0f, 0f, grayPaint)
+ }
+
+ /**
+ * Captures tap event (and tap type). The [WatchFaceService.TAP_TYPE_TAP] case can be
+ * used for implementing specific logic to handle the gesture.
+ */
+ override fun onTapCommand(tapType: Int, x: Int, y: Int, eventTime: Long) {
+ when (tapType) {
+ TAP_TYPE_TOUCH -> {
+ // The user has started touching the screen.
+ }
+ TAP_TYPE_TOUCH_CANCEL -> {
+ // The user has started a different gesture or otherwise cancelled the tap.
+ }
+ TAP_TYPE_TAP ->
+ // The user has completed the tap gesture.
+ // TODO: Add code to handle the tap gesture.
+ Toast.makeText(applicationContext, "tap", Toast.LENGTH_SHORT)
+ .show()
+ }
+ invalidate()
+ }
+
+ override fun onDraw(canvas: Canvas, bounds: Rect) {
+ val now = System.currentTimeMillis()
+ mCalendar.timeInMillis = now
+
+ drawBackground(canvas)
+ drawWatchFace(canvas)
+ }
+
+ private fun drawBackground(canvas: Canvas) {
+
+ if (mAmbient && (mLowBitAmbient || mBurnInProtection)) {
+ canvas.drawColor(Color.BLACK)
+ } else if (mAmbient) {
+ canvas.drawBitmap(mGrayBackgroundBitmap, 0f, 0f, mBackgroundPaint)
+ } else {
+ canvas.drawBitmap(mBackgroundBitmap, 0f, 0f, mBackgroundPaint)
+ }
+ }
+
+ private fun drawWatchFace(canvas: Canvas) {
+
+ /*
+ * Draw ticks. Usually you will want to bake this directly into the photo, but in
+ * cases where you want to allow users to select their own photos, this dynamically
+ * creates them on top of the photo.
+ */
+ /*val innerTickRadius = mCenterX - 10
+ val outerTickRadius = mCenterX
+ for (tickIndex in 0..11) {
+ val tickRot = (tickIndex.toDouble() * Math.PI * 2.0 / 12).toFloat()
+ val innerX = Math.sin(tickRot.toDouble()).toFloat() * innerTickRadius
+ val innerY = (-Math.cos(tickRot.toDouble())).toFloat() * innerTickRadius
+ val outerX = Math.sin(tickRot.toDouble()).toFloat() * outerTickRadius
+ val outerY = (-Math.cos(tickRot.toDouble())).toFloat() * outerTickRadius
+ canvas.drawLine(
+ mCenterX + innerX, mCenterY + innerY,
+ mCenterX + outerX, mCenterY + outerY, mTickAndCirclePaint
+ )
+ }*/
+
+ /*
+ * These calculations reflect the rotation in degrees per unit of time, e.g.,
+ * 360 / 60 = 6 and 360 / 12 = 30.
+ */
+ /*
+ * Save the canvas state before we can begin to rotate it.
+ */
+ canvas.save()
+
+ /* Restore the canvas" original orientation. */
+ canvas.restore()
+
+ val paint = Paint().apply {
+ color = Color.WHITE
+ isAntiAlias = true
+ setShadowLayer(
+ SHADOW_RADIUS, 5f, 0f, mWatchHandShadowColor
+ )
+ }
+
+ paint.textAlign = Paint.Align.CENTER
+ paint.textSize = 96f
+ paint.typeface = resources.getFont(R.font.alicorn_font)
+
+ val h = mCalendar.get(Calendar.HOUR_OF_DAY)
+ val hs = if (h < 10) {
+ "0$h"
+ } else {
+ h.toString()
+ }
+
+ val m = mCalendar.get(Calendar.MINUTE)
+ val ms = if (m < 10) {
+ "0$m"
+ } else {
+ m.toString()
+ }
+
+ canvas.drawText("$hs:$ms", (canvas.width / 2).toFloat(), 150f, paint)
+
+ if (mCalendar.get(Calendar.MINUTE) != lastRefreshMinute) {
+ lastRefreshMinute = mCalendar.get(Calendar.MINUTE)
+
+ val target = object : Target {
+ override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) {
+ try {
+ if (bitmap != null) {
+ bmp = bitmap
+ bmp = Bitmap.createScaledBitmap(
+ bmp,
+ 48,
+ 48, true
+ )
+
+ canvas.drawBitmap(bmp, mCenterX - 48f/2f, 49f, mBackgroundPaint)
+ }
+ } catch (ex: IllegalArgumentException) {
+ Log.e("Picasso", ex.toString())
+ }
+
+ val volleyQueue = Volley.newRequestQueue(baseContext)
+
+ val jsonObjectRequest =
+ JsonObjectRequest("https://ponies.equestria.horse/api/raindrops-two",
+
+ { response ->
+ Log.i("HTTPRequest", response.toString())
+ twoFronters = response!!.get("multiple") as Boolean
+
+ if (twoFronters) {
+ val target = object : Target {
+ override fun onBitmapLoaded(
+ bitmap: Bitmap?,
+ from: Picasso.LoadedFrom?
+ ) {
+ try {
+ if (bitmap != null) {
+ bmp2 = bitmap
+ bmp2 = Bitmap.createScaledBitmap(
+ bmp2,
+ 48,
+ 48, true
+ )
+ }
+ } catch (ex: IllegalArgumentException) {
+ Log.e("Picasso", ex.toString())
+ }
+ }
+
+ override fun onBitmapFailed(
+ e: Exception?,
+ errorDrawable: Drawable?
+ ) {
+ Log.e("Picasso", e.toString())
+ }
+
+ override fun onPrepareLoad(placeHolderDrawable: Drawable?) {}
+ }
+
+ Picasso.get().load(
+ "https://ponies.equestria.horse/api/raindrops-img2-round"
+ ).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
+ }
+ },
+
+ { error ->
+ Log.e(
+ "HTTPRequest",
+ "Request error: ${error.localizedMessage}"
+ )
+ })
+
+ volleyQueue.add(jsonObjectRequest)
+ }
+
+ override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) {
+ Log.e("Picasso", e.toString())
+ }
+
+ override fun onPrepareLoad(placeHolderDrawable: Drawable?) {}
+ }
+
+ Picasso.get().load(
+ "https://ponies.equestria.horse/api/raindrops-img-round"
+ ).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
+ } else {
+ if (twoFronters) {
+ canvas.drawBitmap(bmp2, mCenterX + 6f/2f, 20f, mBackgroundPaint)
+ canvas.drawBitmap(bmp, mCenterX - 102f/2f, 20f, mBackgroundPaint)
+ } else {
+ canvas.drawBitmap(bmp, mCenterX - 48f/2f, 20f, mBackgroundPaint)
+ }
+ }
+ }
+
+ override fun onVisibilityChanged(visible: Boolean) {
+ super.onVisibilityChanged(visible)
+
+ if (visible) {
+ registerReceiver()
+ /* Update time zone in case it changed while we weren"t visible. */
+ mCalendar.timeZone = TimeZone.getDefault()
+ invalidate()
+ } else {
+ unregisterReceiver()
+ }
+
+ /* Check and trigger whether or not timer should be running (only in active mode). */
+ updateTimer()
+ }
+
+ private fun registerReceiver() {
+ if (mRegisteredTimeZoneReceiver) {
+ return
+ }
+ mRegisteredTimeZoneReceiver = true
+ val filter = IntentFilter(Intent.ACTION_TIMEZONE_CHANGED)
+ this@FeathersFace.registerReceiver(mTimeZoneReceiver, filter)
+ }
+
+ private fun unregisterReceiver() {
+ if (!mRegisteredTimeZoneReceiver) {
+ return
+ }
+ mRegisteredTimeZoneReceiver = false
+ this@FeathersFace.unregisterReceiver(mTimeZoneReceiver)
+ }
+
+ /**
+ * Starts/stops the [.mUpdateTimeHandler] timer based on the state of the watch face.
+ */
+ private fun updateTimer() {
+ mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME)
+ if (shouldTimerBeRunning()) {
+ mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME)
+ }
+ }
+
+ /**
+ * Returns whether the [.mUpdateTimeHandler] timer should be running. The timer
+ * should only run in active mode.
+ */
+ private fun shouldTimerBeRunning(): Boolean {
+ return isVisible && !mAmbient
+ }
+
+ /**
+ * Handle updating the time periodically in interactive mode.
+ */
+ fun handleUpdateTimeMessage() {
+ invalidate()
+ if (shouldTimerBeRunning()) {
+ val timeMs = System.currentTimeMillis()
+ val delayMs = INTERACTIVE_UPDATE_RATE_MS - timeMs % INTERACTIVE_UPDATE_RATE_MS
+ mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs)
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/dev/equestria/ponywatch/GlitterFace.kt b/app/src/main/java/dev/equestria/ponywatch/GlitterFace.kt
new file mode 100644
index 0000000..db2fe53
--- /dev/null
+++ b/app/src/main/java/dev/equestria/ponywatch/GlitterFace.kt
@@ -0,0 +1,512 @@
+package dev.equestria.ponywatch
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorMatrix
+import android.graphics.ColorMatrixColorFilter
+import android.graphics.Paint
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.os.Message
+import android.support.wearable.watchface.CanvasWatchFaceService
+import android.support.wearable.watchface.WatchFaceStyle
+import android.util.Log
+import android.view.SurfaceHolder
+import android.widget.Toast
+import com.android.volley.toolbox.JsonObjectRequest
+import com.android.volley.toolbox.Volley
+import com.squareup.picasso.MemoryPolicy
+import com.squareup.picasso.Picasso
+import com.squareup.picasso.Target
+import java.lang.ref.WeakReference
+import java.util.Calendar
+import java.util.TimeZone
+
+/**
+ * Updates rate in milliseconds for interactive mode. We update once a second to advance the
+ * second hand.
+ */
+private const val INTERACTIVE_UPDATE_RATE_MS = 1000
+
+/**
+ * Handler message id for updating the time periodically in interactive mode.
+ */
+private const val MSG_UPDATE_TIME = 0
+
+private const val SHADOW_RADIUS = 3f
+
+/**
+ * Analog watch face with a ticking second hand. In ambient mode, the second hand isn"t
+ * shown. On devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient
+ * mode. The watch face is drawn with less contrast in mute mode.
+ *
+ *
+ * Important Note: Because watch face apps do not have a default Activity in
+ * their project, you will need to set your Configurations to
+ * "Do not launch Activity" for both the Wear and/or Application modules. If you
+ * are unsure how to do this, please review the "Run Starter project" section
+ * in the Google Watch Face Code Lab:
+ * https://codelabs.developers.google.com/codelabs/watchface/index.html#0
+ */
+
+/**
+ * Analog watch face with a ticking second hand. In ambient mode, the second hand isn"t
+ * shown. On devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient
+ * mode. The watch face is drawn with less contrast in mute mode.
+ *
+ *
+ * Important Note: Because watch face apps do not have a default Activity in
+ * their project, you will need to set your Configurations to
+ * "Do not launch Activity" for both the Wear and/or Application modules. If you
+ * are unsure how to do this, please review the "Run Starter project" section
+ * in the Google Watch Face Code Lab:
+ * https://codelabs.developers.google.com/codelabs/watchface/index.html#0
+ */
+class GlitterFace : CanvasWatchFaceService() {
+
+ override fun onCreateEngine(): Engine {
+ return Engine()
+ }
+
+ private class EngineHandler(reference: GlitterFace.Engine) : Handler(Looper.myLooper()!!) {
+ private val mWeakReference: WeakReference<Engine> = WeakReference(reference)
+
+ override fun handleMessage(msg: Message) {
+ val engine = mWeakReference.get()
+ if (engine != null) {
+ when (msg.what) {
+ MSG_UPDATE_TIME -> engine.handleUpdateTimeMessage()
+ }
+ }
+ }
+ }
+
+ inner class Engine : CanvasWatchFaceService.Engine() {
+
+ private lateinit var mCalendar: Calendar
+
+ private var mRegisteredTimeZoneReceiver = false
+ private var mMuteMode: Boolean = false
+ private var mCenterX: Float = 0F
+ private var mCenterY: Float = 0F
+ private var mHeight: Float = 0F
+
+ private var lastRefreshMinute: Int = -1
+
+ private var mSecondHandLength: Float = 0F
+ private var sMinuteHandLength: Float = 0F
+ private var sHourHandLength: Float = 0F
+
+ private var mWatchHandShadowColor: Int = 0
+
+ private lateinit var mBackgroundPaint: Paint
+ private lateinit var mBackgroundBitmap: Bitmap
+ private lateinit var mGrayBackgroundBitmap: Bitmap
+
+ private var mAmbient: Boolean = false
+ private var mLowBitAmbient: Boolean = false
+ private var mBurnInProtection: Boolean = false
+
+ private var twoFronters: Boolean = false
+ private var bmp: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.default_pony)
+ private var bmp2: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.default_pony)
+
+ /* Handler to update the time once a second in interactive mode. */
+ private val mUpdateTimeHandler = EngineHandler(this)
+
+ private val mTimeZoneReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ mCalendar.timeZone = TimeZone.getDefault()
+ invalidate()
+ }
+ }
+
+ override fun onCreate(holder: SurfaceHolder) {
+ super.onCreate(holder)
+
+ setWatchFaceStyle(
+ WatchFaceStyle.Builder(this@GlitterFace)
+ .setAcceptsTapEvents(true)
+ .build()
+ )
+
+ mCalendar = Calendar.getInstance()
+
+ initializeBackground()
+ }
+
+ private fun initializeBackground() {
+ mBackgroundPaint = Paint().apply {
+ color = Color.BLACK
+ }
+ mBackgroundBitmap =
+ BitmapFactory.decodeResource(resources, R.drawable.watchface_glitter_bg)
+ }
+
+ override fun onDestroy() {
+ mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME)
+ super.onDestroy()
+ }
+
+ override fun onPropertiesChanged(properties: Bundle) {
+ super.onPropertiesChanged(properties)
+ mLowBitAmbient = properties.getBoolean(
+ PROPERTY_LOW_BIT_AMBIENT, false
+ )
+ mBurnInProtection = properties.getBoolean(
+ PROPERTY_BURN_IN_PROTECTION, false
+ )
+ }
+
+ override fun onTimeTick() {
+ super.onTimeTick()
+ invalidate()
+ }
+
+ override fun onAmbientModeChanged(inAmbientMode: Boolean) {
+ super.onAmbientModeChanged(inAmbientMode)
+ mAmbient = inAmbientMode
+
+ // Check and trigger whether or not timer should be running (only
+ // in active mode).
+ updateTimer()
+ }
+
+ override fun onInterruptionFilterChanged(interruptionFilter: Int) {
+ super.onInterruptionFilterChanged(interruptionFilter)
+ val inMuteMode = interruptionFilter == INTERRUPTION_FILTER_NONE
+
+ /* Dim display in mute mode. */
+ if (mMuteMode != inMuteMode) {
+ mMuteMode = inMuteMode
+ invalidate()
+ }
+ }
+
+ override fun onSurfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
+ super.onSurfaceChanged(holder, format, width, height)
+
+ /*
+ * Find the coordinates of the center point on the screen, and ignore the window
+ * insets, so that, on round watches with a "chin", the watch face is centered on the
+ * entire screen, not just the usable portion.
+ */
+ mCenterX = width / 2f
+ mCenterY = height / 2f
+ mHeight = height.toFloat()
+
+ /*
+ * Calculate lengths of different hands based on watch screen size.
+ */
+ mSecondHandLength = (mCenterX * 0.875).toFloat()
+ sMinuteHandLength = (mCenterX * 0.75).toFloat()
+ sHourHandLength = (mCenterX * 0.5).toFloat()
+
+ /* Scale loaded background image (more efficient) if surface dimensions change. */
+ val scale = width.toFloat() / mBackgroundBitmap.width.toFloat()
+
+ mBackgroundBitmap = Bitmap.createScaledBitmap(
+ mBackgroundBitmap,
+ (mBackgroundBitmap.width * scale).toInt(),
+ (mBackgroundBitmap.height * scale).toInt(), true
+ )
+
+ /*
+ * Create a gray version of the image only if it will look nice on the device in
+ * ambient mode. That means we don"t want devices that support burn-in
+ * protection (slight movements in pixels, not great for images going all the way to
+ * edges) and low ambient mode (degrades image quality).
+ *
+ * Also, if your watch face will know about all images ahead of time (users aren"t
+ * selecting their own photos for the watch face), it will be more
+ * efficient to create a black/white version (png, etc.) and load that when you need it.
+ */
+ if (!mBurnInProtection && !mLowBitAmbient) {
+ initGrayBackgroundBitmap()
+ }
+ }
+
+ private fun initGrayBackgroundBitmap() {
+ mGrayBackgroundBitmap = Bitmap.createBitmap(
+ mBackgroundBitmap.width,
+ mBackgroundBitmap.height,
+ Bitmap.Config.ARGB_8888
+ )
+ val canvas = Canvas(mGrayBackgroundBitmap)
+ val grayPaint = Paint()
+ val colorMatrix = ColorMatrix()
+ colorMatrix.setSaturation(0f)
+ val filter = ColorMatrixColorFilter(colorMatrix)
+ grayPaint.colorFilter = filter
+ canvas.drawBitmap(mBackgroundBitmap, 0f, 0f, grayPaint)
+ }
+
+ /**
+ * Captures tap event (and tap type). The [WatchFaceService.TAP_TYPE_TAP] case can be
+ * used for implementing specific logic to handle the gesture.
+ */
+ override fun onTapCommand(tapType: Int, x: Int, y: Int, eventTime: Long) {
+ when (tapType) {
+ TAP_TYPE_TOUCH -> {
+ // The user has started touching the screen.
+ }
+ TAP_TYPE_TOUCH_CANCEL -> {
+ // The user has started a different gesture or otherwise cancelled the tap.
+ }
+ TAP_TYPE_TAP ->
+ // The user has completed the tap gesture.
+ // TODO: Add code to handle the tap gesture.
+ Toast.makeText(applicationContext, "tap", Toast.LENGTH_SHORT)
+ .show()
+ }
+ invalidate()
+ }
+
+ override fun onDraw(canvas: Canvas, bounds: Rect) {
+ val now = System.currentTimeMillis()
+ mCalendar.timeInMillis = now
+
+ drawBackground(canvas)
+ drawWatchFace(canvas)
+ }
+
+ private fun drawBackground(canvas: Canvas) {
+
+ if (mAmbient && (mLowBitAmbient || mBurnInProtection)) {
+ canvas.drawColor(Color.BLACK)
+ } else if (mAmbient) {
+ canvas.drawBitmap(mGrayBackgroundBitmap, 0f, 0f, mBackgroundPaint)
+ } else {
+ canvas.drawBitmap(mBackgroundBitmap, 0f, 0f, mBackgroundPaint)
+ }
+ }
+
+ private fun drawWatchFace(canvas: Canvas) {
+
+ /*
+ * Draw ticks. Usually you will want to bake this directly into the photo, but in
+ * cases where you want to allow users to select their own photos, this dynamically
+ * creates them on top of the photo.
+ */
+ /*val innerTickRadius = mCenterX - 10
+ val outerTickRadius = mCenterX
+ for (tickIndex in 0..11) {
+ val tickRot = (tickIndex.toDouble() * Math.PI * 2.0 / 12).toFloat()
+ val innerX = Math.sin(tickRot.toDouble()).toFloat() * innerTickRadius
+ val innerY = (-Math.cos(tickRot.toDouble())).toFloat() * innerTickRadius
+ val outerX = Math.sin(tickRot.toDouble()).toFloat() * outerTickRadius
+ val outerY = (-Math.cos(tickRot.toDouble())).toFloat() * outerTickRadius
+ canvas.drawLine(
+ mCenterX + innerX, mCenterY + innerY,
+ mCenterX + outerX, mCenterY + outerY, mTickAndCirclePaint
+ )
+ }*/
+
+ /*
+ * These calculations reflect the rotation in degrees per unit of time, e.g.,
+ * 360 / 60 = 6 and 360 / 12 = 30.
+ */
+ /*
+ * Save the canvas state before we can begin to rotate it.
+ */
+ canvas.save()
+
+ /* Restore the canvas" original orientation. */
+ canvas.restore()
+
+ val paint = Paint().apply {
+ color = Color.WHITE
+ isAntiAlias = true
+ setShadowLayer(
+ SHADOW_RADIUS, 5f, 0f, mWatchHandShadowColor
+ )
+ }
+
+ paint.textAlign = Paint.Align.CENTER
+ paint.textSize = 96f
+ paint.typeface = resources.getFont(R.font.alicorn_font)
+
+ val h = mCalendar.get(Calendar.HOUR_OF_DAY)
+ val hs = if (h < 10) {
+ "0$h"
+ } else {
+ h.toString()
+ }
+
+ val m = mCalendar.get(Calendar.MINUTE)
+ val ms = if (m < 10) {
+ "0$m"
+ } else {
+ m.toString()
+ }
+
+ canvas.drawText("$hs:$ms", (canvas.width / 2).toFloat(), 150f, paint)
+
+ if (mCalendar.get(Calendar.MINUTE) != lastRefreshMinute) {
+ lastRefreshMinute = mCalendar.get(Calendar.MINUTE)
+
+ val target = object : Target {
+ override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) {
+ try {
+ if (bitmap != null) {
+ bmp = bitmap
+ bmp = Bitmap.createScaledBitmap(
+ bmp,
+ 48,
+ 48, true
+ )
+
+ canvas.drawBitmap(bmp, mCenterX - 48f/2f, 49f, mBackgroundPaint)
+ }
+ } catch (ex: IllegalArgumentException) {
+ Log.e("Picasso", ex.toString())
+ }
+
+ val volleyQueue = Volley.newRequestQueue(baseContext)
+
+ val jsonObjectRequest =
+ JsonObjectRequest("https://ponies.equestria.horse/api/raindrops-two",
+
+ { response ->
+ Log.i("HTTPRequest", response.toString())
+ twoFronters = response!!.get("multiple") as Boolean
+
+ if (twoFronters) {
+ val target = object : Target {
+ override fun onBitmapLoaded(
+ bitmap: Bitmap?,
+ from: Picasso.LoadedFrom?
+ ) {
+ try {
+ if (bitmap != null) {
+ bmp2 = bitmap
+ bmp2 = Bitmap.createScaledBitmap(
+ bmp2,
+ 48,
+ 48, true
+ )
+ }
+ } catch (ex: IllegalArgumentException) {
+ Log.e("Picasso", ex.toString())
+ }
+ }
+
+ override fun onBitmapFailed(
+ e: Exception?,
+ errorDrawable: Drawable?
+ ) {
+ Log.e("Picasso", e.toString())
+ }
+
+ override fun onPrepareLoad(placeHolderDrawable: Drawable?) {}
+ }
+
+ Picasso.get().load(
+ "https://ponies.equestria.horse/api/raindrops-img2-round"
+ ).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
+ }
+ },
+
+ { error ->
+ Log.e(
+ "HTTPRequest",
+ "Request error: ${error.localizedMessage}"
+ )
+ })
+
+ volleyQueue.add(jsonObjectRequest)
+ }
+
+ override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) {
+ Log.e("Picasso", e.toString())
+ }
+
+ override fun onPrepareLoad(placeHolderDrawable: Drawable?) {}
+ }
+
+ Picasso.get().load(
+ "https://ponies.equestria.horse/api/raindrops-img-round"
+ ).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
+ } else {
+ if (twoFronters) {
+ canvas.drawBitmap(bmp2, mCenterX + 6f/2f, 20f, mBackgroundPaint)
+ canvas.drawBitmap(bmp, mCenterX - 102f/2f, 20f, mBackgroundPaint)
+ } else {
+ canvas.drawBitmap(bmp, mCenterX - 48f/2f, 20f, mBackgroundPaint)
+ }
+ }
+ }
+
+ override fun onVisibilityChanged(visible: Boolean) {
+ super.onVisibilityChanged(visible)
+
+ if (visible) {
+ registerReceiver()
+ /* Update time zone in case it changed while we weren"t visible. */
+ mCalendar.timeZone = TimeZone.getDefault()
+ invalidate()
+ } else {
+ unregisterReceiver()
+ }
+
+ /* Check and trigger whether or not timer should be running (only in active mode). */
+ updateTimer()
+ }
+
+ private fun registerReceiver() {
+ if (mRegisteredTimeZoneReceiver) {
+ return
+ }
+ mRegisteredTimeZoneReceiver = true
+ val filter = IntentFilter(Intent.ACTION_TIMEZONE_CHANGED)
+ this@GlitterFace.registerReceiver(mTimeZoneReceiver, filter)
+ }
+
+ private fun unregisterReceiver() {
+ if (!mRegisteredTimeZoneReceiver) {
+ return
+ }
+ mRegisteredTimeZoneReceiver = false
+ this@GlitterFace.unregisterReceiver(mTimeZoneReceiver)
+ }
+
+ /**
+ * Starts/stops the [.mUpdateTimeHandler] timer based on the state of the watch face.
+ */
+ private fun updateTimer() {
+ mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME)
+ if (shouldTimerBeRunning()) {
+ mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME)
+ }
+ }
+
+ /**
+ * Returns whether the [.mUpdateTimeHandler] timer should be running. The timer
+ * should only run in active mode.
+ */
+ private fun shouldTimerBeRunning(): Boolean {
+ return isVisible && !mAmbient
+ }
+
+ /**
+ * Handle updating the time periodically in interactive mode.
+ */
+ fun handleUpdateTimeMessage() {
+ invalidate()
+ if (shouldTimerBeRunning()) {
+ val timeMs = System.currentTimeMillis()
+ val delayMs = INTERACTIVE_UPDATE_RATE_MS - timeMs % INTERACTIVE_UPDATE_RATE_MS
+ mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs)
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/dev/equestria/ponywatch/UnityFace.kt b/app/src/main/java/dev/equestria/ponywatch/UnityFace.kt
new file mode 100644
index 0000000..475804d
--- /dev/null
+++ b/app/src/main/java/dev/equestria/ponywatch/UnityFace.kt
@@ -0,0 +1,675 @@
+package dev.equestria.ponywatch
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorMatrix
+import android.graphics.ColorMatrixColorFilter
+import android.graphics.Paint
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.os.Message
+import android.support.wearable.watchface.CanvasWatchFaceService
+import android.support.wearable.watchface.WatchFaceStyle
+import android.util.Log
+import android.view.SurfaceHolder
+import android.widget.Toast
+import com.android.volley.toolbox.JsonObjectRequest
+import com.android.volley.toolbox.Volley
+import com.squareup.picasso.MemoryPolicy
+import com.squareup.picasso.Picasso
+import com.squareup.picasso.Target
+import java.lang.ref.WeakReference
+import java.util.Calendar
+import java.util.TimeZone
+
+
+/**
+ * Updates rate in milliseconds for interactive mode. We update once a second to advance the
+ * second hand.
+ */
+private const val INTERACTIVE_UPDATE_RATE_MS = 16
+
+/**
+ * Handler message id for updating the time periodically in interactive mode.
+ */
+private const val MSG_UPDATE_TIME = 0
+
+private const val HOUR_STROKE_WIDTH = 35f
+private const val MINUTE_STROKE_WIDTH = 7f
+private const val SECOND_TICK_STROKE_WIDTH = 3f
+
+private const val CENTER_GAP_AND_CIRCLE_RADIUS = 4f
+
+private const val SHADOW_RADIUS = 3f
+
+/**
+ * Analog watch face with a ticking second hand. In ambient mode, the second hand isn"t
+ * shown. On devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient
+ * mode. The watch face is drawn with less contrast in mute mode.
+ *
+ *
+ * Important Note: Because watch face apps do not have a default Activity in
+ * their project, you will need to set your Configurations to
+ * "Do not launch Activity" for both the Wear and/or Application modules. If you
+ * are unsure how to do this, please review the "Run Starter project" section
+ * in the Google Watch Face Code Lab:
+ * https://codelabs.developers.google.com/codelabs/watchface/index.html#0
+ */
+class UnityFace : CanvasWatchFaceService() {
+
+ override fun onCreateEngine(): Engine {
+ return Engine()
+ }
+
+ private class EngineHandler(reference: UnityFace.Engine) : Handler(Looper.myLooper()!!) {
+ private val mWeakReference: WeakReference<Engine> = WeakReference(reference)
+
+ override fun handleMessage(msg: Message) {
+ val engine = mWeakReference.get()
+ if (engine != null) {
+ when (msg.what) {
+ MSG_UPDATE_TIME -> engine.handleUpdateTimeMessage()
+ }
+ }
+ }
+ }
+
+ inner class Engine : CanvasWatchFaceService.Engine() {
+
+ private lateinit var mCalendar: Calendar
+
+ private var mRegisteredTimeZoneReceiver = false
+ private var mMuteMode: Boolean = false
+ private var mCenterX: Float = 0F
+ private var mCenterY: Float = 0F
+ private var mHeight: Float = 0F
+
+ private var lastRefreshMinute: Int = -1
+
+ private var mSecondHandLength: Float = 0F
+ private var sMinuteHandLength: Float = 0F
+ private var sHourHandLength: Float = 0F
+
+ /* Colors for all hands (hour, minute, seconds, ticks) based on photo loaded. */
+ private var mWatchHandColor: Int = 0
+ private var mWatchHandHighlightColor: Int = 0
+ private var mWatchHandShadowColor: Int = 0
+
+ private lateinit var mHourPaint: Paint
+ private lateinit var mMinutePaint: Paint
+ private lateinit var mSecondPaint: Paint
+
+ private lateinit var mBackgroundPaint: Paint
+ private lateinit var mBackgroundBitmap: Bitmap
+ private lateinit var mGrayBackgroundBitmap: Bitmap
+
+ private var mAmbient: Boolean = false
+ private var mLowBitAmbient: Boolean = false
+ private var mBurnInProtection: Boolean = false
+
+ private var currentDay: Int = 0
+ private var twoFronters: Boolean = false
+ private var bmp: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.default_pony)
+ private var bmp2: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.default_pony)
+
+ /* Handler to update the time once a second in interactive mode. */
+ private val mUpdateTimeHandler = EngineHandler(this)
+
+ private val mTimeZoneReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ mCalendar.timeZone = TimeZone.getDefault()
+ invalidate()
+ }
+ }
+
+ override fun onCreate(holder: SurfaceHolder) {
+ super.onCreate(holder)
+
+ setWatchFaceStyle(
+ WatchFaceStyle.Builder(this@UnityFace)
+ .setAcceptsTapEvents(true)
+ .build()
+ )
+
+ mCalendar = Calendar.getInstance()
+
+ initializeBackground()
+ initializeWatchFace()
+ }
+
+ private fun initializeBackground() {
+ mBackgroundPaint = Paint().apply {
+ color = Color.BLACK
+ }
+ mBackgroundBitmap =
+ BitmapFactory.decodeResource(resources, when (currentDay) {
+ 0 -> R.drawable.watchface_unity_bg_0
+ 1 -> R.drawable.watchface_unity_bg_1
+ 2 -> R.drawable.watchface_unity_bg_2
+ 3 -> R.drawable.watchface_unity_bg_3
+ 4 -> R.drawable.watchface_unity_bg_4
+ 5 -> R.drawable.watchface_unity_bg_5
+ 6 -> R.drawable.watchface_unity_bg_6
+ else -> R.drawable.watchface_unity_bg_0
+ })
+ }
+
+ private fun initializeWatchFace() {
+ /* Set defaults for colors */
+ mWatchHandShadowColor = R.color.cutiemark_shadow
+
+ mHourPaint = Paint().apply {
+ color = resources.getColor(when (currentDay) {
+ 0 -> R.color.unity_0_hours
+ 1 -> R.color.unity_1_hours
+ 2 -> R.color.unity_2_hours
+ 3 -> R.color.unity_3_hours
+ 4 -> R.color.unity_4_hours
+ 5 -> R.color.unity_5_hours
+ 6 -> R.color.unity_6_hours
+ else -> R.color.unity_0_hours
+ })
+ strokeWidth = HOUR_STROKE_WIDTH
+ isAntiAlias = true
+ strokeCap = Paint.Cap.ROUND
+ setShadowLayer(
+ SHADOW_RADIUS, 5f, 0f, mWatchHandShadowColor
+ )
+ }
+
+ mMinutePaint = Paint().apply {
+ color = resources.getColor(R.color.cutiemark_minutes)
+ strokeWidth = MINUTE_STROKE_WIDTH
+ isAntiAlias = true
+ strokeCap = Paint.Cap.ROUND
+ setShadowLayer(
+ SHADOW_RADIUS, 5f, 0f, mWatchHandShadowColor
+ )
+ }
+
+ mSecondPaint = Paint().apply {
+ color = resources.getColor(when (currentDay) {
+ 0 -> R.color.unity_0_seconds
+ 1 -> R.color.unity_1_seconds
+ 2 -> R.color.unity_2_seconds
+ 3 -> R.color.unity_3_seconds
+ 4 -> R.color.unity_4_seconds
+ 5 -> R.color.unity_5_seconds
+ 6 -> R.color.unity_6_seconds
+ else -> R.color.unity_0_seconds
+ })
+ strokeWidth = SECOND_TICK_STROKE_WIDTH
+ isAntiAlias = true
+ strokeCap = Paint.Cap.ROUND
+ strokeJoin = Paint.Join.ROUND
+ setShadowLayer(
+ SHADOW_RADIUS, 5f, 0f, mWatchHandShadowColor
+ )
+ }
+ }
+
+ override fun onDestroy() {
+ mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME)
+ super.onDestroy()
+ }
+
+ override fun onPropertiesChanged(properties: Bundle) {
+ super.onPropertiesChanged(properties)
+ mLowBitAmbient = properties.getBoolean(
+ PROPERTY_LOW_BIT_AMBIENT, false
+ )
+ mBurnInProtection = properties.getBoolean(
+ PROPERTY_BURN_IN_PROTECTION, false
+ )
+ }
+
+ override fun onTimeTick() {
+ super.onTimeTick()
+ invalidate()
+ }
+
+ override fun onAmbientModeChanged(inAmbientMode: Boolean) {
+ super.onAmbientModeChanged(inAmbientMode)
+ mAmbient = inAmbientMode
+
+ updateWatchHandStyle()
+
+ // Check and trigger whether or not timer should be running (only
+ // in active mode).
+ updateTimer()
+ }
+
+ private fun updateWatchHandStyle() {
+ if (mAmbient) {
+ mHourPaint.color = Color.WHITE
+ mMinutePaint.color = Color.WHITE
+ mSecondPaint.color = Color.WHITE
+
+ mHourPaint.isAntiAlias = false
+ mMinutePaint.isAntiAlias = false
+ mSecondPaint.isAntiAlias = false
+
+ mHourPaint.clearShadowLayer()
+ mMinutePaint.clearShadowLayer()
+ mSecondPaint.clearShadowLayer()
+
+ } else {
+ mHourPaint.color = mWatchHandColor
+ mMinutePaint.color = mWatchHandColor
+ mSecondPaint.color = mWatchHandHighlightColor
+
+ mHourPaint.isAntiAlias = true
+ mMinutePaint.isAntiAlias = true
+ mSecondPaint.isAntiAlias = true
+
+ mHourPaint.setShadowLayer(
+ SHADOW_RADIUS, 0f, 0f, mWatchHandShadowColor
+ )
+ mMinutePaint.setShadowLayer(
+ SHADOW_RADIUS, 0f, 0f, mWatchHandShadowColor
+ )
+ mSecondPaint.setShadowLayer(
+ SHADOW_RADIUS, 0f, 0f, mWatchHandShadowColor
+ )
+ }
+ }
+
+ override fun onInterruptionFilterChanged(interruptionFilter: Int) {
+ super.onInterruptionFilterChanged(interruptionFilter)
+ val inMuteMode = interruptionFilter == INTERRUPTION_FILTER_NONE
+
+ /* Dim display in mute mode. */
+ if (mMuteMode != inMuteMode) {
+ mMuteMode = inMuteMode
+ mHourPaint.alpha = if (inMuteMode) 100 else 255
+ mMinutePaint.alpha = if (inMuteMode) 100 else 255
+ mSecondPaint.alpha = if (inMuteMode) 80 else 255
+ invalidate()
+ }
+ }
+
+ override fun onSurfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
+ super.onSurfaceChanged(holder, format, width, height)
+
+ /*
+ * Find the coordinates of the center point on the screen, and ignore the window
+ * insets, so that, on round watches with a "chin", the watch face is centered on the
+ * entire screen, not just the usable portion.
+ */
+ mCenterX = width / 2f
+ mCenterY = height / 2f
+ mHeight = height.toFloat()
+
+ /*
+ * Calculate lengths of different hands based on watch screen size.
+ */
+ mSecondHandLength = (mCenterX * 0.875).toFloat()
+ sMinuteHandLength = (mCenterX * 0.75).toFloat()
+ sHourHandLength = (mCenterX * 0.5).toFloat()
+
+ /* Scale loaded background image (more efficient) if surface dimensions change. */
+ val scale = width.toFloat() / mBackgroundBitmap.width.toFloat()
+
+ mBackgroundBitmap = Bitmap.createScaledBitmap(
+ mBackgroundBitmap,
+ (mBackgroundBitmap.width * scale).toInt(),
+ (mBackgroundBitmap.height * scale).toInt(), true
+ )
+
+ /*
+ * Create a gray version of the image only if it will look nice on the device in
+ * ambient mode. That means we don"t want devices that support burn-in
+ * protection (slight movements in pixels, not great for images going all the way to
+ * edges) and low ambient mode (degrades image quality).
+ *
+ * Also, if your watch face will know about all images ahead of time (users aren"t
+ * selecting their own photos for the watch face), it will be more
+ * efficient to create a black/white version (png, etc.) and load that when you need it.
+ */
+ if (!mBurnInProtection && !mLowBitAmbient) {
+ initGrayBackgroundBitmap()
+ }
+ }
+
+ private fun initGrayBackgroundBitmap() {
+ mGrayBackgroundBitmap = Bitmap.createBitmap(
+ mBackgroundBitmap.width,
+ mBackgroundBitmap.height,
+ Bitmap.Config.ARGB_8888
+ )
+ val canvas = Canvas(mGrayBackgroundBitmap)
+ val grayPaint = Paint()
+ val colorMatrix = ColorMatrix()
+ colorMatrix.setSaturation(0f)
+ val filter = ColorMatrixColorFilter(colorMatrix)
+ grayPaint.colorFilter = filter
+ canvas.drawBitmap(mBackgroundBitmap, 0f, 0f, grayPaint)
+ }
+
+ /**
+ * Captures tap event (and tap type). The [WatchFaceService.TAP_TYPE_TAP] case can be
+ * used for implementing specific logic to handle the gesture.
+ */
+ override fun onTapCommand(tapType: Int, x: Int, y: Int, eventTime: Long) {
+ when (tapType) {
+ TAP_TYPE_TOUCH -> {
+ // The user has started touching the screen.
+ }
+ TAP_TYPE_TOUCH_CANCEL -> {
+ // The user has started a different gesture or otherwise cancelled the tap.
+ }
+ TAP_TYPE_TAP ->
+ // The user has completed the tap gesture.
+ // TODO: Add code to handle the tap gesture.
+ Toast.makeText(applicationContext, "tap", Toast.LENGTH_SHORT)
+ .show()
+ }
+ invalidate()
+ }
+
+ override fun onDraw(canvas: Canvas, bounds: Rect) {
+ val now = System.currentTimeMillis()
+ mCalendar.timeInMillis = now
+
+ drawBackground(canvas)
+ drawWatchFace(canvas)
+ }
+
+ private fun drawBackground(canvas: Canvas) {
+
+ if (mAmbient && (mLowBitAmbient || mBurnInProtection)) {
+ canvas.drawColor(Color.BLACK)
+ } else if (mAmbient) {
+ canvas.drawBitmap(mGrayBackgroundBitmap, 0f, 0f, mBackgroundPaint)
+ } else {
+ canvas.drawBitmap(mBackgroundBitmap, 0f, 0f, mBackgroundPaint)
+ }
+ }
+
+ private fun drawWatchFace(canvas: Canvas) {
+
+ /*
+ * Draw ticks. Usually you will want to bake this directly into the photo, but in
+ * cases where you want to allow users to select their own photos, this dynamically
+ * creates them on top of the photo.
+ */
+ /*val innerTickRadius = mCenterX - 10
+ val outerTickRadius = mCenterX
+ for (tickIndex in 0..11) {
+ val tickRot = (tickIndex.toDouble() * Math.PI * 2.0 / 12).toFloat()
+ val innerX = Math.sin(tickRot.toDouble()).toFloat() * innerTickRadius
+ val innerY = (-Math.cos(tickRot.toDouble())).toFloat() * innerTickRadius
+ val outerX = Math.sin(tickRot.toDouble()).toFloat() * outerTickRadius
+ val outerY = (-Math.cos(tickRot.toDouble())).toFloat() * outerTickRadius
+ canvas.drawLine(
+ mCenterX + innerX, mCenterY + innerY,
+ mCenterX + outerX, mCenterY + outerY, mTickAndCirclePaint
+ )
+ }*/
+
+ /*
+ * These calculations reflect the rotation in degrees per unit of time, e.g.,
+ * 360 / 60 = 6 and 360 / 12 = 30.
+ */
+ val seconds =
+ mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND) / 1000f
+ val secondsRotation = seconds * 6f
+
+ val minutes = mCalendar.get(Calendar.MINUTE) + (seconds / 60f)
+ val minutesRotation = minutes * 6f
+
+ val currentDayFetched = mCalendar.get(Calendar.DAY_OF_WEEK) - 1
+
+ if (currentDayFetched > currentDay || currentDayFetched < currentDay) {
+ currentDay = currentDayFetched
+ initializeBackground()
+ initializeWatchFace()
+ }
+
+ val hourHandOffset = mCalendar.get(Calendar.MINUTE) / 2f
+ val hoursRotation = mCalendar.get(Calendar.HOUR) * 30 + hourHandOffset
+
+ /*
+ * Save the canvas state before we can begin to rotate it.
+ */
+ canvas.save()
+
+ canvas.rotate(hoursRotation, mCenterX, mCenterY)
+ canvas.drawLine(
+ mCenterX,
+ mCenterY - CENTER_GAP_AND_CIRCLE_RADIUS,
+ mCenterX,
+ mCenterY - sHourHandLength,
+ mHourPaint
+ )
+
+ canvas.rotate(minutesRotation - hoursRotation, mCenterX, mCenterY)
+ canvas.drawLine(
+ mCenterX,
+ mCenterY - CENTER_GAP_AND_CIRCLE_RADIUS,
+ mCenterX,
+ mCenterY - sMinuteHandLength,
+ mMinutePaint
+ )
+
+ /*
+ * Ensure the "seconds" hand is drawn only when we are in interactive mode.
+ * Otherwise, we only update the watch face once a minute.
+ */
+ if (!mAmbient) {
+ canvas.rotate(secondsRotation - minutesRotation, mCenterX, mCenterY)
+ canvas.drawLine(
+ mCenterX,
+ mCenterY - CENTER_GAP_AND_CIRCLE_RADIUS,
+ mCenterX,
+ mCenterY - mSecondHandLength,
+ mSecondPaint
+ )
+
+ }
+ canvas.drawCircle(
+ mCenterX,
+ mCenterY,
+ CENTER_GAP_AND_CIRCLE_RADIUS,
+ mSecondPaint
+ )
+
+ /* Restore the canvas" original orientation. */
+ canvas.restore()
+
+ val paint = Paint().apply {
+ color = Color.WHITE
+ isAntiAlias = true
+ setShadowLayer(
+ SHADOW_RADIUS, 5f, 0f, mWatchHandShadowColor
+ )
+ }
+
+ paint.textAlign = Paint.Align.CENTER
+ paint.textSize = 24f
+ paint.typeface = resources.getFont(R.font.general)
+
+ val h = mCalendar.get(Calendar.HOUR_OF_DAY)
+ val hs = if (h < 10) {
+ "0$h"
+ } else {
+ h.toString()
+ }
+
+ val m = mCalendar.get(Calendar.MINUTE)
+ val ms = if (m < 10) {
+ "0$m"
+ } else {
+ m.toString()
+ }
+
+ canvas.drawText("$hs:$ms", (canvas.width / 2).toFloat(), 40f, paint)
+
+ if (mCalendar.get(Calendar.MINUTE) != lastRefreshMinute) {
+ lastRefreshMinute = mCalendar.get(Calendar.MINUTE)
+
+ val target = object : Target {
+ override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) {
+ try {
+ if (bitmap != null) {
+ bmp = bitmap
+ bmp = Bitmap.createScaledBitmap(
+ bmp,
+ 48,
+ 48, true
+ )
+
+ canvas.drawBitmap(bmp, mCenterX - 48f/2f, 49f, mBackgroundPaint)
+ }
+ } catch (ex: IllegalArgumentException) {
+ Log.e("Picasso", ex.toString())
+ }
+
+ val volleyQueue = Volley.newRequestQueue(baseContext)
+
+ val jsonObjectRequest =
+ JsonObjectRequest("https://ponies.equestria.horse/api/raindrops-two",
+
+ { response ->
+ Log.i("HTTPRequest", response.toString())
+ twoFronters = response!!.get("multiple") as Boolean
+
+ if (twoFronters) {
+ val target = object : Target {
+ override fun onBitmapLoaded(
+ bitmap: Bitmap?,
+ from: Picasso.LoadedFrom?
+ ) {
+ try {
+ if (bitmap != null) {
+ bmp2 = bitmap
+ bmp2 = Bitmap.createScaledBitmap(
+ bmp2,
+ 48,
+ 48, true
+ )
+ }
+ } catch (ex: IllegalArgumentException) {
+ Log.e("Picasso", ex.toString())
+ }
+ }
+
+ override fun onBitmapFailed(
+ e: Exception?,
+ errorDrawable: Drawable?
+ ) {
+ Log.e("Picasso", e.toString())
+ }
+
+ override fun onPrepareLoad(placeHolderDrawable: Drawable?) {}
+ }
+
+ Picasso.get().load(
+ "https://ponies.equestria.horse/api/raindrops-img2-round"
+ ).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
+ }
+ },
+
+ { error ->
+ Log.e(
+ "HTTPRequest",
+ "Request error: ${error.localizedMessage}"
+ )
+ })
+
+ volleyQueue.add(jsonObjectRequest)
+ }
+
+ override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) {
+ Log.e("Picasso", e.toString())
+ }
+
+ override fun onPrepareLoad(placeHolderDrawable: Drawable?) {}
+ }
+
+ Picasso.get().load(
+ "https://ponies.equestria.horse/api/raindrops-img-round"
+ ).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
+ } else {
+ if (twoFronters) {
+ canvas.drawBitmap(bmp2, mCenterX + 6f/2f, 49f, mBackgroundPaint)
+ canvas.drawBitmap(bmp, mCenterX - 102f/2f, 49f, mBackgroundPaint)
+ } else {
+ canvas.drawBitmap(bmp, mCenterX - 48f/2f, 49f, mBackgroundPaint)
+ }
+ }
+ }
+
+ override fun onVisibilityChanged(visible: Boolean) {
+ super.onVisibilityChanged(visible)
+
+ if (visible) {
+ registerReceiver()
+ /* Update time zone in case it changed while we weren"t visible. */
+ mCalendar.timeZone = TimeZone.getDefault()
+ invalidate()
+ } else {
+ unregisterReceiver()
+ }
+
+ /* Check and trigger whether or not timer should be running (only in active mode). */
+ updateTimer()
+ }
+
+ private fun registerReceiver() {
+ if (mRegisteredTimeZoneReceiver) {
+ return
+ }
+ mRegisteredTimeZoneReceiver = true
+ val filter = IntentFilter(Intent.ACTION_TIMEZONE_CHANGED)
+ this@UnityFace.registerReceiver(mTimeZoneReceiver, filter)
+ }
+
+ private fun unregisterReceiver() {
+ if (!mRegisteredTimeZoneReceiver) {
+ return
+ }
+ mRegisteredTimeZoneReceiver = false
+ this@UnityFace.unregisterReceiver(mTimeZoneReceiver)
+ }
+
+ /**
+ * Starts/stops the [.mUpdateTimeHandler] timer based on the state of the watch face.
+ */
+ private fun updateTimer() {
+ mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME)
+ if (shouldTimerBeRunning()) {
+ mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME)
+ }
+ }
+
+ /**
+ * Returns whether the [.mUpdateTimeHandler] timer should be running. The timer
+ * should only run in active mode.
+ */
+ private fun shouldTimerBeRunning(): Boolean {
+ return isVisible && !mAmbient
+ }
+
+ /**
+ * Handle updating the time periodically in interactive mode.
+ */
+ fun handleUpdateTimeMessage() {
+ invalidate()
+ if (shouldTimerBeRunning()) {
+ val timeMs = System.currentTimeMillis()
+ val delayMs = INTERACTIVE_UPDATE_RATE_MS - timeMs % INTERACTIVE_UPDATE_RATE_MS
+ mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs)
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/app/src/main/res/drawable-nodpi/preview_feathers.png b/app/src/main/res/drawable-nodpi/preview_feathers.png
new file mode 100644
index 0000000..e6bf097
--- /dev/null
+++ b/app/src/main/res/drawable-nodpi/preview_feathers.png
Binary files differ
diff --git a/app/src/main/res/drawable-nodpi/preview_glitter.png b/app/src/main/res/drawable-nodpi/preview_glitter.png
new file mode 100644
index 0000000..51b4c62
--- /dev/null
+++ b/app/src/main/res/drawable-nodpi/preview_glitter.png
Binary files differ
diff --git a/app/src/main/res/drawable-nodpi/preview_unity.png b/app/src/main/res/drawable-nodpi/preview_unity.png
new file mode 100644
index 0000000..9539a09
--- /dev/null
+++ b/app/src/main/res/drawable-nodpi/preview_unity.png
Binary files differ
diff --git a/app/src/main/res/drawable-nodpi/watchface_feathers_bg.png b/app/src/main/res/drawable-nodpi/watchface_feathers_bg.png
new file mode 100644
index 0000000..e81598a
--- /dev/null
+++ b/app/src/main/res/drawable-nodpi/watchface_feathers_bg.png
Binary files differ
diff --git a/app/src/main/res/drawable-nodpi/watchface_glitter_bg.png b/app/src/main/res/drawable-nodpi/watchface_glitter_bg.png
new file mode 100644
index 0000000..d3bcb99
--- /dev/null
+++ b/app/src/main/res/drawable-nodpi/watchface_glitter_bg.png
Binary files differ
diff --git a/app/src/main/res/drawable-nodpi/watchface_unity_bg_0.png b/app/src/main/res/drawable-nodpi/watchface_unity_bg_0.png
new file mode 100644
index 0000000..ce55b45
--- /dev/null
+++ b/app/src/main/res/drawable-nodpi/watchface_unity_bg_0.png
Binary files differ
diff --git a/app/src/main/res/drawable-nodpi/watchface_unity_bg_1.png b/app/src/main/res/drawable-nodpi/watchface_unity_bg_1.png
new file mode 100644
index 0000000..b0b4443
--- /dev/null
+++ b/app/src/main/res/drawable-nodpi/watchface_unity_bg_1.png
Binary files differ
diff --git a/app/src/main/res/drawable-nodpi/watchface_unity_bg_2.png b/app/src/main/res/drawable-nodpi/watchface_unity_bg_2.png
new file mode 100644
index 0000000..a29b988
--- /dev/null
+++ b/app/src/main/res/drawable-nodpi/watchface_unity_bg_2.png
Binary files differ
diff --git a/app/src/main/res/drawable-nodpi/watchface_unity_bg_3.png b/app/src/main/res/drawable-nodpi/watchface_unity_bg_3.png
new file mode 100644
index 0000000..3740d37
--- /dev/null
+++ b/app/src/main/res/drawable-nodpi/watchface_unity_bg_3.png
Binary files differ
diff --git a/app/src/main/res/drawable-nodpi/watchface_unity_bg_4.png b/app/src/main/res/drawable-nodpi/watchface_unity_bg_4.png
new file mode 100644
index 0000000..b253d87
--- /dev/null
+++ b/app/src/main/res/drawable-nodpi/watchface_unity_bg_4.png
Binary files differ
diff --git a/app/src/main/res/drawable-nodpi/watchface_unity_bg_5.png b/app/src/main/res/drawable-nodpi/watchface_unity_bg_5.png
new file mode 100644
index 0000000..295ef21
--- /dev/null
+++ b/app/src/main/res/drawable-nodpi/watchface_unity_bg_5.png
Binary files differ
diff --git a/app/src/main/res/drawable-nodpi/watchface_unity_bg_6.png b/app/src/main/res/drawable-nodpi/watchface_unity_bg_6.png
new file mode 100644
index 0000000..189a1d9
--- /dev/null
+++ b/app/src/main/res/drawable-nodpi/watchface_unity_bg_6.png
Binary files differ
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..32f6c4f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,26 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="1000"
+ android:viewportHeight="1000">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M0,0h1000v1000h-1000z"/>
+ <path
+ android:pathData="M0,0h1000v1000h-1000z"
+ android:strokeAlpha="0.5"
+ android:fillAlpha="0.5">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:startX="500"
+ android:startY="0"
+ android:endX="500"
+ android:endY="1000"
+ android:type="linear">
+ <item android:offset="0" android:color="#FF746FC4"/>
+ <item android:offset="1" android:color="#FF085AAE"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+</vector>
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..1933e9c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,92 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="1000"
+ android:viewportHeight="1000">
+ <group android:scaleX="0.73"
+ android:scaleY="0.73"
+ android:translateX="135"
+ android:translateY="135">
+ <path
+ android:pathData="M597.6,329.8c0,0 -20.9,-35.4 -60.7,-62.4c1,51.6 23.5,107.2 23.5,107.2s-35.3,-32.3 -76.5,-35.7c4,29.4 35.8,77.7 35.8,77.7s-34.8,-5.7 -68.7,19.9c12.4,11.2 44,18.8 44,18.8s-32.2,6.6 -48.1,31.8c13.9,9.5 43.5,12.6 43.5,12.6s-24.5,19.9 -38.3,51.2c11.5,0.3 40.8,-1.3 40.8,-1.3s-28.2,34.4 -28.8,67.4c20.8,-5.4 42.6,-18.7 42.6,-18.7s-23.6,51.1 -22.3,87.1c32.9,-17.7 49.6,-33.6 49.6,-33.6s1.6,59.4 24.1,94.6c24.7,-18.3 46.7,-48.9 46.7,-48.9s20.7,52.6 50,71.4c17.4,-27 26.8,-71.9 26.8,-71.9s18.5,26.2 52.1,36.7c8.7,-33.9 6.1,-85.3 6.1,-85.3s16.6,23.3 39.9,28.6c10.4,-52.9 -2.9,-115.7 -2.9,-115.7s12.4,11.2 26.9,9.6c-3.2,-38.9 -29.7,-83 -29.7,-83s24.8,3.6 40.9,0.3c-27.9,-40.9 -65.4,-60.4 -65.4,-60.4s21.2,-0.7 38.3,-12c-33.6,-30.9 -82.6,-50.7 -82.6,-50.7s19.5,-20.9 23.6,-51.1c-45.5,3.3 -74.6,26.9 -74.6,26.9s-2.1,-45.2 -11.5,-80.3C612,285.9 597.6,329.8 597.6,329.8L597.6,329.8z"
+ android:fillColor="#0B4E8E"/>
+ <path
+ android:pathData="M670.5,361.1c-14.8,18.8 -12.4,48.4 -3.4,58.4C663.8,399.5 667,378.7 670.5,361.1z"
+ android:fillColor="#083B6A"/>
+ <path
+ android:pathData="M779.1,470.1c-16.1,-17.1 -45.8,-20.2 -58.2,-12.5C740.4,457 760.7,464.3 779.1,470.1z"
+ android:fillColor="#083B6A"/>
+ <path
+ android:pathData="M764.5,551.6c-7,-25.9 -38,-44.5 -56.2,-47.3C729,516.3 748.6,536.1 764.5,551.6z"
+ android:fillColor="#083B6A"/>
+ <path
+ android:pathData="M518.8,584.4c2.7,-26.9 30.1,-51.8 47.5,-58.4C546.4,542.2 531.8,564.2 518.8,584.4z"
+ android:fillColor="#083B6A"/>
+ <path
+ android:pathData="M538.4,384.8c12.1,28.5 43.8,75.2 43.8,75.2S566.2,405.3 538.4,384.8z"
+ android:fillColor="#083B6A"/>
+ <path
+ android:pathData="M658.4,572.5c0,0 36.7,8.6 49.7,47.9C681.3,593.5 658.4,572.5 658.4,572.5z"
+ android:fillColor="#083B6A"/>
+ <path
+ android:pathData="M640,707.7c0,0 31.6,-33.2 -21.7,-65.1C643.7,672.8 640,707.7 640,707.7z"
+ android:fillColor="#083B6A"/>
+ <path
+ android:pathData="M537.4,472.7c-6.2,-35.4 -30.2,-29.7 -30.2,-29.7S519.2,449.5 537.4,472.7z"
+ android:fillColor="#083B6A"/>
+ <path
+ android:pathData="M736.8,571.9c0,0 27.9,1.7 17.8,37.3C752.5,584.3 736.8,571.9 736.8,571.9z"
+ android:fillColor="#083B6A"/>
+ <path
+ android:pathData="M616.2,577c0,0 -60.6,19.1 -55.7,78.1C565.7,618.5 616.2,577 616.2,577z"
+ android:fillColor="#083B6A"/>
+ <path
+ android:pathData="M736.6,410.4c0,0 -33,-23.1 -39.5,16.8C708.4,405.6 736.6,410.4 736.6,410.4z"
+ android:fillColor="#083B6A"/>
+ <path
+ android:pathData="M602.3,366.9c0,0 -15.7,-12.4 -14.9,-42.3C604.1,349.4 602.3,366.9 602.3,366.9z"
+ android:fillColor="#083B6A"/>
+ <path
+ android:pathData="M319.6,441.7c0,0 1.3,-39.6 22.8,-81c24.5,43.2 30.9,100.4 30.9,100.4s16.4,-43.7 51.9,-66.9c10.7,26.6 5.6,81 5.6,81s27.9,-22 69.7,-16.3c-6.9,15.3 -30.2,36.5 -30.2,36.5s31.4,-9.9 59,4.4c-8.4,15.6 -32.5,32.2 -32.5,32.2s32.2,4.1 59,23.3c-10,6.4 -36.3,18.6 -36.3,18.6s41.9,15.1 59,42.2c-20.9,6.6 -46.5,4.6 -46.5,4.6s46,30.2 62,60.6c-37.5,1.5 -62.1,-3.9 -62.1,-3.9s27.3,50.6 24.5,90.4c-31.9,-2.6 -66.1,-18.9 -66.1,-18.9s7.4,53.9 -9.7,83.5c-29.1,-14.1 -58.2,-47 -58.2,-47s-4.4,30.6 -28.6,55.2c-24.5,-24.3 -45.9,-68 -45.9,-68s-4.9,27.6 -22.8,43.2c-34.7,-38.3 -55.4,-96.3 -55.4,-96.3s-6.9,15.3 -20.2,20.7c-16.6,-33.5 -15.3,-82.5 -15.3,-82.5s-19.4,15.8 -37.6,20.4c4.6,-48 28.9,-82 28.9,-82s-20.4,9.7 -40.9,9.9c15.1,-41.9 49.3,-82.2 49.3,-82.2s-28.1,-7.9 -46,-30.2c42.7,-18.1 79.7,-13.2 79.7,-13.2s-20.4,-37.6 -29.3,-72.3C284.9,412.8 319.6,441.7 319.6,441.7L319.6,441.7z"
+ android:fillColor="#085AAE"/>
+ <path
+ android:pathData="M270.8,501.5c22.5,8.5 34.8,34 31.7,46.6C296.3,531.3 282.6,514.4 270.8,501.5z"
+ android:fillColor="#064688"/>
+ <path
+ android:pathData="M227.4,643.6c6,-21.8 31,-38.6 45.8,-38.3C255.5,614.3 239.5,630.6 227.4,643.6z"
+ android:fillColor="#064688"/>
+ <path
+ android:pathData="M280.4,704c-6.4,-24.8 13.6,-55.6 26.9,-66C293.3,658.3 287.1,683.5 280.4,704z"
+ android:fillColor="#064688"/>
+ <path
+ android:pathData="M515.9,613.2c-15.6,-21 -52.3,-28.4 -71.1,-25.5C469.1,592.2 495,604.1 515.9,613.2z"
+ android:fillColor="#064688"/>
+ <path
+ android:pathData="M400.4,457.7c3.1,29.3 -2.1,83.1 -2.1,83.1S385.6,488 400.4,457.7z"
+ android:fillColor="#064688"/>
+ <path
+ android:pathData="M385.5,670.3c-11.2,11.1 -26.5,59.3 14.8,64.9C370.2,718.5 385.5,670.3 385.5,670.3z"
+ android:fillColor="#064688"/>
+ <path
+ android:pathData="M468.3,773.2c0,0 -18,-29.7 -12.6,-64.1C464.1,737.8 468.3,773.2 468.3,773.2z"
+ android:fillColor="#064688"/>
+ <path
+ android:pathData="M444.4,529.8c-11.9,-32.2 12.4,-39 12.4,-39S449.3,502 444.4,529.8z"
+ android:fillColor="#064688"/>
+ <path
+ android:pathData="M315.2,707.4c0,0 -24.1,14.8 0.9,39.9C306.3,727.2 315.2,707.4 315.2,707.4z"
+ android:fillColor="#064688"/>
+ <path
+ android:pathData="M426,655.2c0,0 63.4,-13.3 88.1,37.8C489.3,664.5 426,655.2 426,655.2z"
+ android:fillColor="#064688"/>
+ <path
+ android:pathData="M234.5,574.6c0,0 24.1,-26.1 50,-2.9C257.9,569.8 234.5,574.6 234.5,574.6z"
+ android:fillColor="#064688"/>
+ <path
+ android:pathData="M334.5,473.6c0,0 7.9,-17.8 -7.5,-42.1C323.5,462 334.5,473.6 334.5,473.6z"
+ android:fillColor="#064688"/>
+ <path
+ android:pathData="M305.6,286.1l-120.7,17l23.5,87.2l58.8,-20.3c0,0 102.7,469.1 302.4,400.2c200.7,-70.9 -12.1,-502 -12.1,-502l60.2,-20.8l-36.7,-82.7l-105,62.5c0,0 145.8,365.5 45,401.9C419.6,664.2 306.1,287.5 305.6,286.1L305.6,286.1z"
+ android:fillColor="#746FC4"/>
+ </group>
+</vector>
diff --git a/app/src/main/res/font/general.ttf b/app/src/main/res/font/general.ttf
new file mode 100755
index 0000000..e2c69c3
--- /dev/null
+++ b/app/src/main/res/font/general.ttf
Binary files differ
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..bbd3e02
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background"/>
+ <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon> \ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..bbd3e02
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background"/>
+ <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon> \ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..9b03181
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
deleted file mode 100644
index c209e78..0000000
--- a/app/src/main/res/mipmap-hdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..9b03181
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..508472c
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
deleted file mode 100644
index 4f0f1d6..0000000
--- a/app/src/main/res/mipmap-mdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..508472c
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..930bc54
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
deleted file mode 100644
index 948a307..0000000
--- a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..930bc54
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..bf12570
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
deleted file mode 100644
index 28d4b77..0000000
--- a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..bf12570
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..0dfd335
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
deleted file mode 100644
index aa7d642..0000000
--- a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..0dfd335
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index a7a61af..315d1fe 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,7 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
+ <color name="cutiemark_shadow">#77000000</color>
+
<color name="cutiemark_hours">#f4c3ff</color>
<color name="cutiemark_minutes">#ffffff</color>
<color name="cutiemark_seconds">#c549ff</color>
- <color name="cutiemark_shadow">#77000000</color>
+
+ <color name="unity_0_hours">#f4c3ff</color>
+ <color name="unity_0_seconds">#c549ff</color>
+
+ <color name="unity_1_hours">#C3C6FF</color>
+ <color name="unity_1_seconds">#4A53FF</color>
+
+ <color name="unity_2_hours">#E3C3FF</color>
+ <color name="unity_2_seconds">#AB4AFF</color>
+
+ <color name="unity_3_hours">#C3FAFF</color>
+ <color name="unity_3_seconds">#4AF0FF</color>
+
+ <color name="unity_4_hours">#FFC3D4</color>
+ <color name="unity_4_seconds">#FF4A7D</color>
+
+ <color name="unity_5_hours">#FFF9C3</color>
+ <color name="unity_5_seconds">#FFED4A</color>
+
+ <color name="unity_6_hours">#C3FFE7</color>
+ <color name="unity_6_seconds">#4AFFB7</color>
</resources> \ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 3aa8eea..b2ebb1e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
- id 'com.android.application' version '7.4.0' apply false
- id 'com.android.library' version '7.4.0' apply false
+ id 'com.android.application' version '7.4.1' apply false
+ id 'com.android.library' version '7.4.1' apply false
id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
} \ No newline at end of file