From 72011e3cc3ee500a2825204fe4dad9dcb9bfd923 Mon Sep 17 00:00:00 2001 From: Minteck Date: Sat, 14 Jan 2023 12:07:52 +0100 Subject: Update - This is an automated commit --- app/build.gradle | 6 +- app/src/main/AndroidManifest.xml | 40 +- .../java/dev/equestria/ponywatch/AlicornFace.kt | 441 +++++++++++++++++++++ .../java/dev/equestria/ponywatch/CutiemarkFace.kt | 12 +- .../main/res/drawable-nodpi/preview_alicorn.png | Bin 0 -> 161125 bytes app/src/main/res/drawable-nodpi/preview_analog.png | Bin 83868 -> 0 bytes .../main/res/drawable-nodpi/preview_cutiemark.png | Bin 0 -> 83868 bytes .../res/drawable-nodpi/watchface_alicorn_bg.png | Bin 0 -> 130073 bytes .../res/drawable-nodpi/watchface_cutiemark_bg.png | Bin 0 -> 57674 bytes .../res/drawable-nodpi/watchface_service_bg.png | Bin 57674 -> 0 bytes app/src/main/res/font/alicorn_font.ttf | Bin 0 -> 60976 bytes app/src/main/res/values/colors.xml | 8 +- app/src/main/res/values/strings.xml | 5 - 13 files changed, 483 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/dev/equestria/ponywatch/AlicornFace.kt create mode 100644 app/src/main/res/drawable-nodpi/preview_alicorn.png delete mode 100644 app/src/main/res/drawable-nodpi/preview_analog.png create mode 100644 app/src/main/res/drawable-nodpi/preview_cutiemark.png create mode 100644 app/src/main/res/drawable-nodpi/watchface_alicorn_bg.png create mode 100644 app/src/main/res/drawable-nodpi/watchface_cutiemark_bg.png delete mode 100644 app/src/main/res/drawable-nodpi/watchface_service_bg.png create mode 100644 app/src/main/res/font/alicorn_font.ttf delete mode 100644 app/src/main/res/values/strings.xml diff --git a/app/build.gradle b/app/build.gradle index a04aa9b..aeacb81 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,13 +26,13 @@ android { dependencies { - implementation 'androidx.core:core-ktx:1.9.0' - implementation 'com.google.android.gms:play-services-wearable:18.0.0' + 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' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'com.google.android.support:wearable:2.9.0' - implementation 'com.google.android.gms:play-services-base:18.1.0' + implementation 'com.google.android.gms:play-services-base:18.0.1' implementation 'androidx.palette:palette-ktx:1.0.0' compileOnly 'com.google.android.wearable:wearable:2.9.0' implementation("com.squareup.picasso:picasso:2.8") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0b7d5e6..2c9eaa1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,7 +13,7 @@ - @@ -42,10 +60,10 @@ android:resource="@xml/watch_face" /> + android:resource="@drawable/preview_alicorn" /> + android:resource="@drawable/preview_alicorn" /> diff --git a/app/src/main/java/dev/equestria/ponywatch/AlicornFace.kt b/app/src/main/java/dev/equestria/ponywatch/AlicornFace.kt new file mode 100644 index 0000000..fdc69e9 --- /dev/null +++ b/app/src/main/java/dev/equestria/ponywatch/AlicornFace.kt @@ -0,0 +1,441 @@ +package dev.equestria.ponywatch + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.graphics.* +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.squareup.picasso.MemoryPolicy +import com.squareup.picasso.Picasso +import com.squareup.picasso.Target +import java.lang.ref.WeakReference +import java.util.* + +/** + * 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 AlicornFace : CanvasWatchFaceService() { + + override fun onCreateEngine(): Engine { + return Engine() + } + + private class EngineHandler(reference: AlicornFace.Engine) : Handler(Looper.myLooper()!!) { + private val mWeakReference: WeakReference = 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 bmp: 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@AlicornFace) + .setAcceptsTapEvents(true) + .build() + ) + + mCalendar = Calendar.getInstance() + + initializeBackground() + } + + private fun initializeBackground() { + mBackgroundPaint = Paint().apply { + color = Color.BLACK + } + mBackgroundBitmap = + BitmapFactory.decodeResource(resources, R.drawable.watchface_alicorn_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()) + } + } + + 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 { + 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@AlicornFace.registerReceiver(mTimeZoneReceiver, filter) + } + + private fun unregisterReceiver() { + if (!mRegisteredTimeZoneReceiver) { + return + } + mRegisteredTimeZoneReceiver = false + this@AlicornFace.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 efa6ea7..561f785 100644 --- a/app/src/main/java/dev/equestria/ponywatch/CutiemarkFace.kt +++ b/app/src/main/java/dev/equestria/ponywatch/CutiemarkFace.kt @@ -139,15 +139,15 @@ class CutiemarkFace : CanvasWatchFaceService() { color = Color.BLACK } mBackgroundBitmap = - BitmapFactory.decodeResource(resources, R.drawable.watchface_service_bg) + BitmapFactory.decodeResource(resources, R.drawable.watchface_cutiemark_bg) } private fun initializeWatchFace() { /* Set defaults for colors */ - mWatchHandShadowColor = R.color.shadow + mWatchHandShadowColor = R.color.cutiemark_shadow mHourPaint = Paint().apply { - color = resources.getColor(R.color.hours) + color = resources.getColor(R.color.cutiemark_hours) strokeWidth = HOUR_STROKE_WIDTH isAntiAlias = true strokeCap = Paint.Cap.ROUND @@ -157,7 +157,7 @@ class CutiemarkFace : CanvasWatchFaceService() { } mMinutePaint = Paint().apply { - color = resources.getColor(R.color.minutes) + color = resources.getColor(R.color.cutiemark_minutes) strokeWidth = MINUTE_STROKE_WIDTH isAntiAlias = true strokeCap = Paint.Cap.ROUND @@ -167,7 +167,7 @@ class CutiemarkFace : CanvasWatchFaceService() { } mSecondPaint = Paint().apply { - color = resources.getColor(R.color.seconds) + color = resources.getColor(R.color.cutiemark_seconds) strokeWidth = SECOND_TICK_STROKE_WIDTH isAntiAlias = true strokeCap = Paint.Cap.ROUND @@ -331,7 +331,7 @@ class CutiemarkFace : CanvasWatchFaceService() { WatchFaceService.TAP_TYPE_TAP -> // The user has completed the tap gesture. // TODO: Add code to handle the tap gesture. - Toast.makeText(applicationContext, R.string.message, Toast.LENGTH_SHORT) + Toast.makeText(applicationContext, "tap", Toast.LENGTH_SHORT) .show() } invalidate() diff --git a/app/src/main/res/drawable-nodpi/preview_alicorn.png b/app/src/main/res/drawable-nodpi/preview_alicorn.png new file mode 100644 index 0000000..84b894a Binary files /dev/null and b/app/src/main/res/drawable-nodpi/preview_alicorn.png differ diff --git a/app/src/main/res/drawable-nodpi/preview_analog.png b/app/src/main/res/drawable-nodpi/preview_analog.png deleted file mode 100644 index 3044b19..0000000 Binary files a/app/src/main/res/drawable-nodpi/preview_analog.png and /dev/null differ diff --git a/app/src/main/res/drawable-nodpi/preview_cutiemark.png b/app/src/main/res/drawable-nodpi/preview_cutiemark.png new file mode 100644 index 0000000..3044b19 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/preview_cutiemark.png differ diff --git a/app/src/main/res/drawable-nodpi/watchface_alicorn_bg.png b/app/src/main/res/drawable-nodpi/watchface_alicorn_bg.png new file mode 100644 index 0000000..ebe286e Binary files /dev/null and b/app/src/main/res/drawable-nodpi/watchface_alicorn_bg.png differ diff --git a/app/src/main/res/drawable-nodpi/watchface_cutiemark_bg.png b/app/src/main/res/drawable-nodpi/watchface_cutiemark_bg.png new file mode 100644 index 0000000..ce55b45 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/watchface_cutiemark_bg.png differ diff --git a/app/src/main/res/drawable-nodpi/watchface_service_bg.png b/app/src/main/res/drawable-nodpi/watchface_service_bg.png deleted file mode 100644 index ce55b45..0000000 Binary files a/app/src/main/res/drawable-nodpi/watchface_service_bg.png and /dev/null differ diff --git a/app/src/main/res/font/alicorn_font.ttf b/app/src/main/res/font/alicorn_font.ttf new file mode 100644 index 0000000..ced52e8 Binary files /dev/null and b/app/src/main/res/font/alicorn_font.ttf differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 3aeedfb..a7a61af 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,7 +1,7 @@ - #f4c3ff - #ffffff - #c549ff - #77000000 + #f4c3ff + #ffffff + #c549ff + #77000000 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml deleted file mode 100644 index 91d4099..0000000 --- a/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - Ponywatch - Watch face tapped - Cutie Mark - \ No newline at end of file -- cgit