summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaindropsSys <raindrops@equestria.dev>2023-12-17 12:24:41 +0100
committerRaindropsSys <raindrops@equestria.dev>2023-12-17 12:24:41 +0100
commit9d6d58c902a6e1551336e124eac6c2747610dc19 (patch)
tree2502a67310229295f6a850e3b7f82bafce7f1e35
parent526d307826dfe3dc7f9e624913feaab3bee43b75 (diff)
downloadponywatch-9d6d58c902a6e1551336e124eac6c2747610dc19.tar.gz
ponywatch-9d6d58c902a6e1551336e124eac6c2747610dc19.tar.bz2
ponywatch-9d6d58c902a6e1551336e124eac6c2747610dc19.zip
Updated 13 files and added 7 files (automated)
-rw-r--r--app/build.gradle4
-rw-r--r--app/general/release/app-general-release.apkbin8948437 -> 13285932 bytes
-rw-r--r--app/general/release/output-metadata.json4
-rw-r--r--app/src/main/AndroidManifest.xml52
-rw-r--r--app/src/main/java/dev/equestria/ponywatch/AlicornFace.kt6
-rw-r--r--app/src/main/java/dev/equestria/ponywatch/BlueyBingoFace.kt588
-rw-r--r--app/src/main/java/dev/equestria/ponywatch/BlueyBlueyFace.kt588
-rw-r--r--app/src/main/java/dev/equestria/ponywatch/CutiemarkFace.kt6
-rw-r--r--app/src/main/java/dev/equestria/ponywatch/FeathersFace.kt6
-rw-r--r--app/src/main/java/dev/equestria/ponywatch/GlitterFace.kt6
-rw-r--r--app/src/main/java/dev/equestria/ponywatch/UnitedFace.kt8
-rw-r--r--app/src/main/java/dev/equestria/ponywatch/UnityFace.kt6
-rw-r--r--app/src/main/res/drawable-nodpi/preview_bluey_bingo.pngbin0 -> 170720 bytes
-rw-r--r--app/src/main/res/drawable-nodpi/preview_bluey_bluey.pngbin0 -> 188216 bytes
-rw-r--r--app/src/main/res/drawable-nodpi/watchface_bluey_bingo.pngbin0 -> 137914 bytes
-rw-r--r--app/src/main/res/drawable-nodpi/watchface_bluey_bluey.pngbin0 -> 151467 bytes
-rw-r--r--app/src/main/res/font/bluey_font.ttfbin0 -> 113764 bytes
-rw-r--r--app/src/main/res/values/colors.xml5
-rw-r--r--build.gradle4
-rw-r--r--gradle/wrapper/gradle-wrapper.properties2
20 files changed, 1259 insertions, 26 deletions
diff --git a/app/build.gradle b/app/build.gradle
index 2b7c6bf..bac5400 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -11,8 +11,8 @@ android {
applicationId "dev.equestria.ponywatch"
minSdk 30
targetSdk 33
- versionCode 8
- versionName "4.0"
+ versionCode 9
+ versionName "5.0"
}
diff --git a/app/general/release/app-general-release.apk b/app/general/release/app-general-release.apk
index 58e5872..182b89e 100644
--- a/app/general/release/app-general-release.apk
+++ b/app/general/release/app-general-release.apk
Binary files differ
diff --git a/app/general/release/output-metadata.json b/app/general/release/output-metadata.json
index e360245..915076d 100644
--- a/app/general/release/output-metadata.json
+++ b/app/general/release/output-metadata.json
@@ -11,8 +11,8 @@
"type": "SINGLE",
"filters": [],
"attributes": [],
- "versionCode": 4,
- "versionName": "2.1.general",
+ "versionCode": 9,
+ "versionName": "5.0.general",
"outputFile": "app-general-release.apk"
}
],
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 29e8a55..f9818b8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -176,6 +176,58 @@
</intent-filter>
</service>
+ <service
+ android:name=".BlueyBlueyFace"
+ android:exported="true"
+ android:label="Bluey"
+ 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_bluey_bluey" />
+ <meta-data
+ android:name="com.google.android.wearable.watchface.preview_circular"
+ android:resource="@drawable/preview_bluey_bluey" />
+
+ <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=".BlueyBingoFace"
+ android:exported="true"
+ android:label="Bingo"
+ 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_bluey_bingo" />
+ <meta-data
+ android:name="com.google.android.wearable.watchface.preview_circular"
+ android:resource="@drawable/preview_bluey_bingo" />
+
+ <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/java/dev/equestria/ponywatch/AlicornFace.kt b/app/src/main/java/dev/equestria/ponywatch/AlicornFace.kt
index 6a46dfd..5b09e4e 100644
--- a/app/src/main/java/dev/equestria/ponywatch/AlicornFace.kt
+++ b/app/src/main/java/dev/equestria/ponywatch/AlicornFace.kt
@@ -369,7 +369,7 @@ class AlicornFace : CanvasWatchFaceService() {
val volleyQueue = Volley.newRequestQueue(baseContext)
- val jsonObjectRequest = JsonObjectRequest("https://ponies.equestria.horse/api/raindrops-two",
+ val jsonObjectRequest = JsonObjectRequest("https://ponycule.equestria.horse/api/raindrops-two",
{ response ->
Log.i("HTTPRequest", response.toString())
@@ -400,7 +400,7 @@ class AlicornFace : CanvasWatchFaceService() {
}
Picasso.get().load(
- "https://ponies.equestria.horse/api/raindrops-img2-round"
+ "https://ponycule.equestria.horse/api/raindrops-img2-round"
).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
}
},
@@ -418,7 +418,7 @@ class AlicornFace : CanvasWatchFaceService() {
}
Picasso.get().load(
- "https://ponies.equestria.horse/api/raindrops-img-round"
+ "https://ponycule.equestria.horse/api/raindrops-img-round"
).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
} else {
if (twoFronters) {
diff --git a/app/src/main/java/dev/equestria/ponywatch/BlueyBingoFace.kt b/app/src/main/java/dev/equestria/ponywatch/BlueyBingoFace.kt
new file mode 100644
index 0000000..66c0cbb
--- /dev/null
+++ b/app/src/main/java/dev/equestria/ponywatch/BlueyBingoFace.kt
@@ -0,0 +1,588 @@
+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.RectF
+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 BlueyBingoFace : CanvasWatchFaceService() {
+
+ override fun onCreateEngine(): Engine {
+ return Engine()
+ }
+
+ private class EngineHandler(reference: BlueyBingoFace.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 showDate: 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 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 currentHour: 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@BlueyBingoFace)
+ .setAcceptsTapEvents(true)
+ .build()
+ )
+
+ mCalendar = Calendar.getInstance()
+
+ initializeBackground()
+ initializeWatchFace()
+ }
+
+ private fun initializeBackground() {
+ mBackgroundPaint = Paint().apply {
+ color = Color.BLACK
+ }
+ mBackgroundBitmap =
+ BitmapFactory.decodeResource(
+ resources, R.drawable.watchface_bluey_bingo
+ )
+ }
+
+ private fun initializeWatchFace() {
+ /* Set defaults for colors */
+ mWatchHandShadowColor = R.color.cutiemark_shadow
+ }
+
+ 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.
+ if (x < mCenterX / 4) showDate = true
+ }
+ TAP_TYPE_TOUCH_CANCEL -> {
+ // The user has started a different gesture or otherwise cancelled the tap.
+ showDate = false
+ }
+ 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()*/
+ showDate = false
+ }
+ }
+ 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
+ )
+ }*/
+
+ /*
+ * 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 = resources.getColor(R.color.bluey_bingo_text)
+ isAntiAlias = true
+ setShadowLayer(
+ 1.5f, 0f, 0f, mWatchHandShadowColor
+ )
+ }
+
+ paint.textAlign = Paint.Align.CENTER
+ paint.textSize = 96f
+ paint.typeface = resources.getFont(R.font.bluey_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()
+ }
+
+ val d = mCalendar.get(Calendar.DAY_OF_MONTH)
+ val i = mCalendar.get(Calendar.MONTH)
+
+ canvas.drawText("$hs:$ms", (canvas.width / 2).toFloat(), 116f, paint)
+
+ if (showDate) {
+ val it = when (i) {
+ 0 -> "jan"
+ 1 -> "feb"
+ 2 -> "mar"
+ 3 -> "apr"
+ 4 -> "may"
+ 5 -> "jun"
+ 6 -> "jul"
+ 7 -> "aug"
+ 8 -> "sep"
+ 9 -> "oct"
+ 10 -> "nov"
+ 11 -> "dec"
+ else -> i
+ }
+
+ paint.textSize = 36f
+ paint.alpha = 191
+
+ canvas.drawText("$d", canvas.width - 48f, (canvas.height / 2).toFloat() - 20f + 7f, paint)
+ canvas.drawText("$it", canvas.width - 48f, (canvas.height / 2).toFloat() + 20f + 7f, paint)
+ }
+
+ if (BuildConfig.ENABLE_PLURALITY) {
+ 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,
+ 36,
+ 36, true
+ )
+ bmp.prepareToDraw()
+
+ if (showDate) {
+ canvas.drawBitmap(bmp, mCenterX - 36f/2f, 0f, mBackgroundPaint)
+ }
+ }
+ } catch (ex: IllegalArgumentException) {
+ Log.e("Picasso", ex.toString())
+ }
+
+ val volleyQueue = Volley.newRequestQueue(baseContext)
+
+ val jsonObjectRequest =
+ JsonObjectRequest("https://ponycule.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,
+ 36,
+ 36, true
+ )
+ bmp2.prepareToDraw()
+ }
+ } 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://ponycule.equestria.horse/api/raindrops-img2-round2"
+ ).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://ponycule.equestria.horse/api/raindrops-img-round2"
+ ).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
+ } else {
+ if (showDate) {
+ if (twoFronters) {
+ canvas.drawBitmap(bmp2, mCenterX - 4f/2f, 0f, mBackgroundPaint)
+ canvas.drawBitmap(bmp, mCenterX - 88f/2f, 0f, mBackgroundPaint)
+ } else {
+ canvas.drawBitmap(bmp, mCenterX - 36f/2f, 0f, mBackgroundPaint)
+ }
+ }
+ }
+ }
+
+ val paintOut2 = Paint().apply {
+ color = resources.getColor(R.color.bluey_bingo_circle)
+ style = Paint.Style.STROKE
+ strokeWidth = 15f
+ strokeCap = Paint.Cap.ROUND
+ isAntiAlias = true
+ setShadowLayer(
+ 1.5f, 0f, 0f, resources.getColor(R.color.cutiemark_shadow)
+ )
+ }
+
+ val seconds =
+ mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND) / 1000f
+ val secondsRotation = seconds * 6f
+
+ val currentDayFetched = mCalendar.get(Calendar.DAY_OF_WEEK) - 1
+ val currentHourFetched = mCalendar.get(Calendar.HOUR_OF_DAY)
+
+ if (currentDayFetched > currentDay || currentDayFetched < currentDay) {
+ currentDay = currentDayFetched
+ initializeBackground()
+ initializeWatchFace()
+ }
+
+ if (currentHourFetched > currentHour || currentHourFetched < currentHour) {
+ currentHour = currentHourFetched
+ initializeBackground()
+ initializeWatchFace()
+ }
+
+ canvas.drawArc(10f, 10f, mCenterX * 2f - 10f, mCenterY * 2f - 10f, secondsRotation - 1f + 270f, 0.15f, false, paintOut2)
+ }
+
+ 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 {
+ showDate = false
+ 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@BlueyBingoFace.registerReceiver(mTimeZoneReceiver, filter)
+ }
+
+ private fun unregisterReceiver() {
+ if (!mRegisteredTimeZoneReceiver) {
+ return
+ }
+ mRegisteredTimeZoneReceiver = false
+ this@BlueyBingoFace.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/BlueyBlueyFace.kt b/app/src/main/java/dev/equestria/ponywatch/BlueyBlueyFace.kt
new file mode 100644
index 0000000..2dd109c
--- /dev/null
+++ b/app/src/main/java/dev/equestria/ponywatch/BlueyBlueyFace.kt
@@ -0,0 +1,588 @@
+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.RectF
+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 BlueyBlueyFace : CanvasWatchFaceService() {
+
+ override fun onCreateEngine(): Engine {
+ return Engine()
+ }
+
+ private class EngineHandler(reference: BlueyBlueyFace.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 showDate: 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 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 currentHour: 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@BlueyBlueyFace)
+ .setAcceptsTapEvents(true)
+ .build()
+ )
+
+ mCalendar = Calendar.getInstance()
+
+ initializeBackground()
+ initializeWatchFace()
+ }
+
+ private fun initializeBackground() {
+ mBackgroundPaint = Paint().apply {
+ color = Color.BLACK
+ }
+ mBackgroundBitmap =
+ BitmapFactory.decodeResource(
+ resources, R.drawable.watchface_bluey_bluey
+ )
+ }
+
+ private fun initializeWatchFace() {
+ /* Set defaults for colors */
+ mWatchHandShadowColor = R.color.cutiemark_shadow
+ }
+
+ 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.
+ if (x < mCenterX / 4) showDate = true
+ }
+ TAP_TYPE_TOUCH_CANCEL -> {
+ // The user has started a different gesture or otherwise cancelled the tap.
+ showDate = false
+ }
+ 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()*/
+ showDate = false
+ }
+ }
+ 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
+ )
+ }*/
+
+ /*
+ * 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 = resources.getColor(R.color.bluey_bluey_text)
+ isAntiAlias = true
+ setShadowLayer(
+ 1.5f, 0f, 0f, mWatchHandShadowColor
+ )
+ }
+
+ paint.textAlign = Paint.Align.CENTER
+ paint.textSize = 96f
+ paint.typeface = resources.getFont(R.font.bluey_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()
+ }
+
+ val d = mCalendar.get(Calendar.DAY_OF_MONTH)
+ val i = mCalendar.get(Calendar.MONTH)
+
+ canvas.drawText("$hs:$ms", (canvas.width / 2).toFloat(), 116f, paint)
+
+ if (showDate) {
+ val it = when (i) {
+ 0 -> "jan"
+ 1 -> "feb"
+ 2 -> "mar"
+ 3 -> "apr"
+ 4 -> "may"
+ 5 -> "jun"
+ 6 -> "jul"
+ 7 -> "aug"
+ 8 -> "sep"
+ 9 -> "oct"
+ 10 -> "nov"
+ 11 -> "dec"
+ else -> i
+ }
+
+ paint.textSize = 36f
+ paint.alpha = 191
+
+ canvas.drawText("$d", canvas.width - 48f, (canvas.height / 2).toFloat() - 20f + 7f, paint)
+ canvas.drawText("$it", canvas.width - 48f, (canvas.height / 2).toFloat() + 20f + 7f, paint)
+ }
+
+ if (BuildConfig.ENABLE_PLURALITY) {
+ 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,
+ 36,
+ 36, true
+ )
+ bmp.prepareToDraw()
+
+ if (showDate) {
+ canvas.drawBitmap(bmp, mCenterX - 36f/2f, 0f, mBackgroundPaint)
+ }
+ }
+ } catch (ex: IllegalArgumentException) {
+ Log.e("Picasso", ex.toString())
+ }
+
+ val volleyQueue = Volley.newRequestQueue(baseContext)
+
+ val jsonObjectRequest =
+ JsonObjectRequest("https://ponycule.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,
+ 36,
+ 36, true
+ )
+ bmp2.prepareToDraw()
+ }
+ } 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://ponycule.equestria.horse/api/raindrops-img2-round2"
+ ).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://ponycule.equestria.horse/api/raindrops-img-round2"
+ ).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
+ } else {
+ if (showDate) {
+ if (twoFronters) {
+ canvas.drawBitmap(bmp2, mCenterX - 4f/2f, 0f, mBackgroundPaint)
+ canvas.drawBitmap(bmp, mCenterX - 88f/2f, 0f, mBackgroundPaint)
+ } else {
+ canvas.drawBitmap(bmp, mCenterX - 36f/2f, 0f, mBackgroundPaint)
+ }
+ }
+ }
+ }
+
+ val paintOut2 = Paint().apply {
+ color = resources.getColor(R.color.bluey_bluey_circle)
+ style = Paint.Style.STROKE
+ strokeWidth = 15f
+ strokeCap = Paint.Cap.ROUND
+ isAntiAlias = true
+ setShadowLayer(
+ 1.5f, 0f, 0f, resources.getColor(R.color.cutiemark_shadow)
+ )
+ }
+
+ val seconds =
+ mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND) / 1000f
+ val secondsRotation = seconds * 6f
+
+ val currentDayFetched = mCalendar.get(Calendar.DAY_OF_WEEK) - 1
+ val currentHourFetched = mCalendar.get(Calendar.HOUR_OF_DAY)
+
+ if (currentDayFetched > currentDay || currentDayFetched < currentDay) {
+ currentDay = currentDayFetched
+ initializeBackground()
+ initializeWatchFace()
+ }
+
+ if (currentHourFetched > currentHour || currentHourFetched < currentHour) {
+ currentHour = currentHourFetched
+ initializeBackground()
+ initializeWatchFace()
+ }
+
+ canvas.drawArc(10f, 10f, mCenterX * 2f - 10f, mCenterY * 2f - 10f, secondsRotation - 1f + 270f, 0.15f, false, paintOut2)
+ }
+
+ 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 {
+ showDate = false
+ 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@BlueyBlueyFace.registerReceiver(mTimeZoneReceiver, filter)
+ }
+
+ private fun unregisterReceiver() {
+ if (!mRegisteredTimeZoneReceiver) {
+ return
+ }
+ mRegisteredTimeZoneReceiver = false
+ this@BlueyBlueyFace.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/CutiemarkFace.kt b/app/src/main/java/dev/equestria/ponywatch/CutiemarkFace.kt
index da876a6..ae5461a 100644
--- a/app/src/main/java/dev/equestria/ponywatch/CutiemarkFace.kt
+++ b/app/src/main/java/dev/equestria/ponywatch/CutiemarkFace.kt
@@ -494,7 +494,7 @@ class CutiemarkFace : CanvasWatchFaceService() {
val volleyQueue = Volley.newRequestQueue(baseContext)
- val jsonObjectRequest = JsonObjectRequest("https://ponies.equestria.horse/api/raindrops-two",
+ val jsonObjectRequest = JsonObjectRequest("https://ponycule.equestria.horse/api/raindrops-two",
{ response ->
Log.i("HTTPRequest", response.toString())
@@ -525,7 +525,7 @@ class CutiemarkFace : CanvasWatchFaceService() {
}
Picasso.get().load(
- "https://ponies.equestria.horse/api/raindrops-img2-round"
+ "https://ponycule.equestria.horse/api/raindrops-img2-round"
).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
}
},
@@ -543,7 +543,7 @@ class CutiemarkFace : CanvasWatchFaceService() {
}
Picasso.get().load(
- "https://ponies.equestria.horse/api/raindrops-img-round"
+ "https://ponycule.equestria.horse/api/raindrops-img-round"
).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
} else {
if (twoFronters) {
diff --git a/app/src/main/java/dev/equestria/ponywatch/FeathersFace.kt b/app/src/main/java/dev/equestria/ponywatch/FeathersFace.kt
index 1d1ea55..ebb4fc2 100644
--- a/app/src/main/java/dev/equestria/ponywatch/FeathersFace.kt
+++ b/app/src/main/java/dev/equestria/ponywatch/FeathersFace.kt
@@ -375,7 +375,7 @@ class FeathersFace : CanvasWatchFaceService() {
val volleyQueue = Volley.newRequestQueue(baseContext)
val jsonObjectRequest =
- JsonObjectRequest("https://ponies.equestria.horse/api/raindrops-two",
+ JsonObjectRequest("https://ponycule.equestria.horse/api/raindrops-two",
{ response ->
Log.i("HTTPRequest", response.toString())
@@ -412,7 +412,7 @@ class FeathersFace : CanvasWatchFaceService() {
}
Picasso.get().load(
- "https://ponies.equestria.horse/api/raindrops-img2-round"
+ "https://ponycule.equestria.horse/api/raindrops-img2-round"
).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
}
},
@@ -435,7 +435,7 @@ class FeathersFace : CanvasWatchFaceService() {
}
Picasso.get().load(
- "https://ponies.equestria.horse/api/raindrops-img-round"
+ "https://ponycule.equestria.horse/api/raindrops-img-round"
).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
} else {
if (twoFronters) {
diff --git a/app/src/main/java/dev/equestria/ponywatch/GlitterFace.kt b/app/src/main/java/dev/equestria/ponywatch/GlitterFace.kt
index bf2ed58..f58d5c4 100644
--- a/app/src/main/java/dev/equestria/ponywatch/GlitterFace.kt
+++ b/app/src/main/java/dev/equestria/ponywatch/GlitterFace.kt
@@ -375,7 +375,7 @@ class GlitterFace : CanvasWatchFaceService() {
val volleyQueue = Volley.newRequestQueue(baseContext)
val jsonObjectRequest =
- JsonObjectRequest("https://ponies.equestria.horse/api/raindrops-two",
+ JsonObjectRequest("https://ponycule.equestria.horse/api/raindrops-two",
{ response ->
Log.i("HTTPRequest", response.toString())
@@ -412,7 +412,7 @@ class GlitterFace : CanvasWatchFaceService() {
}
Picasso.get().load(
- "https://ponies.equestria.horse/api/raindrops-img2-round"
+ "https://ponycule.equestria.horse/api/raindrops-img2-round"
).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
}
},
@@ -435,7 +435,7 @@ class GlitterFace : CanvasWatchFaceService() {
}
Picasso.get().load(
- "https://ponies.equestria.horse/api/raindrops-img-round"
+ "https://ponycule.equestria.horse/api/raindrops-img-round"
).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
} else {
if (twoFronters) {
diff --git a/app/src/main/java/dev/equestria/ponywatch/UnitedFace.kt b/app/src/main/java/dev/equestria/ponywatch/UnitedFace.kt
index 4749b0b..87c4a7b 100644
--- a/app/src/main/java/dev/equestria/ponywatch/UnitedFace.kt
+++ b/app/src/main/java/dev/equestria/ponywatch/UnitedFace.kt
@@ -468,7 +468,7 @@ class UnitedFace : CanvasWatchFaceService() {
canvas.drawText("$it", canvas.width - 48f, (canvas.height / 2).toFloat() + 20f + 7f, paint)
}
- if (BuildConfig.ENABLE_PLURALITY || true) {
+ if (BuildConfig.ENABLE_PLURALITY) {
if (mCalendar.get(Calendar.MINUTE) != lastRefreshMinute) {
lastRefreshMinute = mCalendar.get(Calendar.MINUTE)
@@ -495,7 +495,7 @@ class UnitedFace : CanvasWatchFaceService() {
val volleyQueue = Volley.newRequestQueue(baseContext)
val jsonObjectRequest =
- JsonObjectRequest("https://ponies.equestria.horse/api/raindrops-two",
+ JsonObjectRequest("https://ponycule.equestria.horse/api/raindrops-two",
{ response ->
Log.i("HTTPRequest", response.toString())
@@ -533,7 +533,7 @@ class UnitedFace : CanvasWatchFaceService() {
}
Picasso.get().load(
- "https://ponies.equestria.horse/api/raindrops-img2-round2"
+ "https://ponycule.equestria.horse/api/raindrops-img2-round2"
).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
}
},
@@ -556,7 +556,7 @@ class UnitedFace : CanvasWatchFaceService() {
}
Picasso.get().load(
- "https://ponies.equestria.horse/api/raindrops-img-round2"
+ "https://ponycule.equestria.horse/api/raindrops-img-round2"
).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
} else {
if (showDate) {
diff --git a/app/src/main/java/dev/equestria/ponywatch/UnityFace.kt b/app/src/main/java/dev/equestria/ponywatch/UnityFace.kt
index 0dc634f..09f5507 100644
--- a/app/src/main/java/dev/equestria/ponywatch/UnityFace.kt
+++ b/app/src/main/java/dev/equestria/ponywatch/UnityFace.kt
@@ -538,7 +538,7 @@ class UnityFace : CanvasWatchFaceService() {
val volleyQueue = Volley.newRequestQueue(baseContext)
val jsonObjectRequest =
- JsonObjectRequest("https://ponies.equestria.horse/api/raindrops-two",
+ JsonObjectRequest("https://ponycule.equestria.horse/api/raindrops-two",
{ response ->
Log.i("HTTPRequest", response.toString())
@@ -575,7 +575,7 @@ class UnityFace : CanvasWatchFaceService() {
}
Picasso.get().load(
- "https://ponies.equestria.horse/api/raindrops-img2-round"
+ "https://ponycule.equestria.horse/api/raindrops-img2-round"
).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
}
},
@@ -598,7 +598,7 @@ class UnityFace : CanvasWatchFaceService() {
}
Picasso.get().load(
- "https://ponies.equestria.horse/api/raindrops-img-round"
+ "https://ponycule.equestria.horse/api/raindrops-img-round"
).memoryPolicy(MemoryPolicy.NO_CACHE).into(target)
} else {
if (twoFronters) {
diff --git a/app/src/main/res/drawable-nodpi/preview_bluey_bingo.png b/app/src/main/res/drawable-nodpi/preview_bluey_bingo.png
new file mode 100644
index 0000000..4df225a
--- /dev/null
+++ b/app/src/main/res/drawable-nodpi/preview_bluey_bingo.png
Binary files differ
diff --git a/app/src/main/res/drawable-nodpi/preview_bluey_bluey.png b/app/src/main/res/drawable-nodpi/preview_bluey_bluey.png
new file mode 100644
index 0000000..4beb66e
--- /dev/null
+++ b/app/src/main/res/drawable-nodpi/preview_bluey_bluey.png
Binary files differ
diff --git a/app/src/main/res/drawable-nodpi/watchface_bluey_bingo.png b/app/src/main/res/drawable-nodpi/watchface_bluey_bingo.png
new file mode 100644
index 0000000..4a91c9f
--- /dev/null
+++ b/app/src/main/res/drawable-nodpi/watchface_bluey_bingo.png
Binary files differ
diff --git a/app/src/main/res/drawable-nodpi/watchface_bluey_bluey.png b/app/src/main/res/drawable-nodpi/watchface_bluey_bluey.png
new file mode 100644
index 0000000..defb689
--- /dev/null
+++ b/app/src/main/res/drawable-nodpi/watchface_bluey_bluey.png
Binary files differ
diff --git a/app/src/main/res/font/bluey_font.ttf b/app/src/main/res/font/bluey_font.ttf
new file mode 100644
index 0000000..0ba0f96
--- /dev/null
+++ b/app/src/main/res/font/bluey_font.ttf
Binary files differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 1109144..ecacc70 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -59,4 +59,9 @@
<color name="united_text_5_1">#b3ebff</color>
<color name="united_text_6_0">#ffdbe6</color>
<color name="united_text_6_1">#e9bfff</color>
+
+ <color name="bluey_bluey_text">#BFEDFF</color>
+ <color name="bluey_bluey_circle">#536D78</color>
+ <color name="bluey_bingo_text">#FFD9BF</color>
+ <color name="bluey_bingo_circle">#786253</color>
</resources> \ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 9de7d04..d9f6c17 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 '8.1.2' apply false
- id 'com.android.library' version '8.1.2' apply false
+ id 'com.android.application' version '8.2.0' apply false
+ id 'com.android.library' version '8.2.0' apply false
id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
} \ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index edd2de2..7887531 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Wed Jan 11 10:34:07 CET 2023
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME