diff options
author | Minteck <contact@minteck.org> | 2023-02-22 18:05:01 +0100 |
---|---|---|
committer | Minteck <contact@minteck.org> | 2023-02-22 18:05:01 +0100 |
commit | e379e0e9d1188b9e04e6055736112df30d6c9591 (patch) | |
tree | f44f0fa22e73d31d87602e11ffaaa9120280021e | |
parent | 72011e3cc3ee500a2825204fe4dad9dcb9bfd923 (diff) | |
download | ponywatch-e379e0e9d1188b9e04e6055736112df30d6c9591.tar.gz ponywatch-e379e0e9d1188b9e04e6055736112df30d6c9591.tar.bz2 ponywatch-e379e0e9d1188b9e04e6055736112df30d6c9591.zip |
Updated 9 files, added 32 files and deleted 5 files (automated)
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 Binary files differnew file mode 100644 index 0000000..33dd8f1 --- /dev/null +++ b/app/src/main/ic_launcher-playstore.png 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 Binary files differnew file mode 100644 index 0000000..e6bf097 --- /dev/null +++ b/app/src/main/res/drawable-nodpi/preview_feathers.png diff --git a/app/src/main/res/drawable-nodpi/preview_glitter.png b/app/src/main/res/drawable-nodpi/preview_glitter.png Binary files differnew file mode 100644 index 0000000..51b4c62 --- /dev/null +++ b/app/src/main/res/drawable-nodpi/preview_glitter.png diff --git a/app/src/main/res/drawable-nodpi/preview_unity.png b/app/src/main/res/drawable-nodpi/preview_unity.png Binary files differnew file mode 100644 index 0000000..9539a09 --- /dev/null +++ b/app/src/main/res/drawable-nodpi/preview_unity.png diff --git a/app/src/main/res/drawable-nodpi/watchface_feathers_bg.png b/app/src/main/res/drawable-nodpi/watchface_feathers_bg.png Binary files differnew file mode 100644 index 0000000..e81598a --- /dev/null +++ b/app/src/main/res/drawable-nodpi/watchface_feathers_bg.png diff --git a/app/src/main/res/drawable-nodpi/watchface_glitter_bg.png b/app/src/main/res/drawable-nodpi/watchface_glitter_bg.png Binary files differnew file mode 100644 index 0000000..d3bcb99 --- /dev/null +++ b/app/src/main/res/drawable-nodpi/watchface_glitter_bg.png 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 Binary files differnew file mode 100644 index 0000000..ce55b45 --- /dev/null +++ b/app/src/main/res/drawable-nodpi/watchface_unity_bg_0.png 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 Binary files differnew file mode 100644 index 0000000..b0b4443 --- /dev/null +++ b/app/src/main/res/drawable-nodpi/watchface_unity_bg_1.png 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 Binary files differnew file mode 100644 index 0000000..a29b988 --- /dev/null +++ b/app/src/main/res/drawable-nodpi/watchface_unity_bg_2.png 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 Binary files differnew file mode 100644 index 0000000..3740d37 --- /dev/null +++ b/app/src/main/res/drawable-nodpi/watchface_unity_bg_3.png 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 Binary files differnew file mode 100644 index 0000000..b253d87 --- /dev/null +++ b/app/src/main/res/drawable-nodpi/watchface_unity_bg_4.png 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 Binary files differnew file mode 100644 index 0000000..295ef21 --- /dev/null +++ b/app/src/main/res/drawable-nodpi/watchface_unity_bg_5.png 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 Binary files differnew file mode 100644 index 0000000..189a1d9 --- /dev/null +++ b/app/src/main/res/drawable-nodpi/watchface_unity_bg_6.png 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 Binary files differnew file mode 100755 index 0000000..e2c69c3 --- /dev/null +++ b/app/src/main/res/font/general.ttf 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 Binary files differnew file mode 100644 index 0000000..9b03181 --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp Binary files differdeleted file mode 100644 index c209e78..0000000 --- a/app/src/main/res/mipmap-hdpi/ic_launcher.webp +++ /dev/null diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..9b03181 --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..508472c --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp Binary files differdeleted file mode 100644 index 4f0f1d6..0000000 --- a/app/src/main/res/mipmap-mdpi/ic_launcher.webp +++ /dev/null diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..508472c --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..930bc54 --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp Binary files differdeleted file mode 100644 index 948a307..0000000 --- a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +++ /dev/null diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..930bc54 --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..bf12570 --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp Binary files differdeleted file mode 100644 index 28d4b77..0000000 --- a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +++ /dev/null diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..bf12570 --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..0dfd335 --- /dev/null +++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp Binary files differdeleted file mode 100644 index aa7d642..0000000 --- a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +++ /dev/null diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..0dfd335 --- /dev/null +++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png 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 |