mirror of
https://github.com/ckaczor/HomeMonitor.git
synced 2026-01-14 01:25:38 -05:00
Initial WIP on Wear OS tile app
This commit is contained in:
1
WearOS/src/.gitignore
vendored
Normal file
1
WearOS/src/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
56
WearOS/src/build.gradle
Normal file
56
WearOS/src/build.gradle
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 31
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.chriskaczor.homemonitor.wear"
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 30
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'androidx.core:core-ktx:1.6.0'
|
||||
// General Wear functionality
|
||||
implementation 'androidx.wear:wear:1.1.0'
|
||||
// Tiles functionality
|
||||
implementation "androidx.wear.tiles:tiles:1.0.0-alpha11"
|
||||
// Preview Tiles in an Activity for testing purposes
|
||||
debugImplementation "androidx.wear.tiles:tiles-renderer:1.0.0-alpha11"
|
||||
// Helper library for transforming coroutines to ListenableFutures
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.5.1'
|
||||
|
||||
implementation 'com.beust:klaxon:5.5'
|
||||
}
|
||||
20
WearOS/src/src/debug/AndroidManifest.xml
Normal file
20
WearOS/src/src/debug/AndroidManifest.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.chriskaczor.homemonitor.wear">
|
||||
|
||||
<uses-feature android:name="android.hardware.type.watch" />
|
||||
|
||||
<application>
|
||||
|
||||
<activity
|
||||
android:name=".TilePreviewActivity"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.chriskaczor.homemonitor.wear
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.os.Bundle
|
||||
import android.widget.FrameLayout
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.wear.tiles.manager.TileUiClient
|
||||
|
||||
class TilePreviewActivity : ComponentActivity() {
|
||||
lateinit var tileUiClient: TileUiClient
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
val rootLayout = findViewById<FrameLayout>(R.id.tile_container)
|
||||
|
||||
tileUiClient = TileUiClient(
|
||||
context = this,
|
||||
component = ComponentName(this, GoalsTileService::class.java),
|
||||
parentView = rootLayout
|
||||
)
|
||||
tileUiClient.connect()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
tileUiClient.close()
|
||||
}
|
||||
}
|
||||
19
WearOS/src/src/debug/res/layout/activity_main.xml
Normal file
19
WearOS/src/src/debug/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2021 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/tile_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
35
WearOS/src/src/main/AndroidManifest.xml
Normal file
35
WearOS/src/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.chriskaczor.homemonitor.wear">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
|
||||
|
||||
<uses-feature android:name="android.hardware.type.watch" />
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:theme="@android:style/Theme.DeviceDefault">
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.wearable.standalone"
|
||||
android:value="true" />
|
||||
|
||||
<service
|
||||
android:name="com.chriskaczor.homemonitor.wear.GoalsTileService"
|
||||
android:description="@string/tile_description"
|
||||
android:icon="@drawable/ic_run"
|
||||
android:label="@string/fitness_tile_label"
|
||||
android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
|
||||
<intent-filter>
|
||||
<action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="androidx.wear.tiles.PREVIEW"
|
||||
android:resource="@drawable/tile_goals" />
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,211 @@
|
||||
package com.chriskaczor.homemonitor.wear
|
||||
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.wear.tiles.ActionBuilders
|
||||
import androidx.wear.tiles.ColorBuilders.argb
|
||||
import androidx.wear.tiles.DeviceParametersBuilders.DeviceParameters
|
||||
import androidx.wear.tiles.DimensionBuilders.*
|
||||
import androidx.wear.tiles.LayoutElementBuilders.*
|
||||
import androidx.wear.tiles.ModifiersBuilders.*
|
||||
import androidx.wear.tiles.RequestBuilders.ResourcesRequest
|
||||
import androidx.wear.tiles.RequestBuilders.TileRequest
|
||||
import androidx.wear.tiles.ResourceBuilders.*
|
||||
import androidx.wear.tiles.TileBuilders.Tile
|
||||
import androidx.wear.tiles.TileService
|
||||
import androidx.wear.tiles.TimelineBuilders.Timeline
|
||||
import androidx.wear.tiles.TimelineBuilders.TimelineEntry
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.guava.future
|
||||
|
||||
// TODO: Review Constants.
|
||||
// Updating this version triggers a new call to onResourcesRequest(). This is useful for dynamic
|
||||
// resources, the contents of which change even though their id stays the same (e.g. a graph).
|
||||
// In this sample, our resources are all fixed, so we use a constant value.
|
||||
private const val RESOURCES_VERSION = "1"
|
||||
|
||||
// dimensions
|
||||
private val PROGRESS_BAR_THICKNESS = dp(6f)
|
||||
private val BUTTON_SIZE = dp(48f)
|
||||
private val BUTTON_RADIUS = dp(24f)
|
||||
private val BUTTON_PADDING = dp(12f)
|
||||
private val VERTICAL_SPACING_HEIGHT = dp(8f)
|
||||
|
||||
// Complete degrees for a circle (relates to [Arc] component)
|
||||
private const val ARC_TOTAL_DEGREES = 360f
|
||||
|
||||
// identifiers
|
||||
private const val ID_IMAGE_START_RUN = "image_start_run"
|
||||
private const val ID_CLICK_START_RUN = "click_start_run"
|
||||
|
||||
/**
|
||||
* Creates a Fitness Tile, showing your progress towards a daily goal. The progress is defined
|
||||
* randomly, for demo purposes only. A new random progress is shown when the user taps the button.
|
||||
*/
|
||||
class GoalsTileService : TileService() {
|
||||
// For coroutines, use a custom scope we can cancel when the service is destroyed
|
||||
private val serviceScope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
// TODO: Build a Tile.
|
||||
override fun onTileRequest(requestParams: TileRequest) = serviceScope.future {
|
||||
|
||||
// Retrieves progress value to populate the Tile.
|
||||
val goalProgress = PowerRepository.getPowerStatus()
|
||||
// Retrieves device parameters to later retrieve font styles for any text in the Tile.
|
||||
val deviceParams = requestParams.deviceParameters!!
|
||||
|
||||
// Creates Tile.
|
||||
Tile.Builder()
|
||||
// If there are any graphics/images defined in the Tile's layout, the system will
|
||||
// retrieve them via onResourcesRequest() and match them with this version number.
|
||||
.setResourcesVersion(RESOURCES_VERSION)
|
||||
|
||||
// Creates a timeline to hold one or more tile entries for a specific time periods.
|
||||
.setTimeline(
|
||||
Timeline.Builder()
|
||||
.addTimelineEntry(
|
||||
TimelineEntry.Builder()
|
||||
.setLayout(
|
||||
Layout.Builder()
|
||||
.setRoot(
|
||||
// Creates the root [Box] [LayoutElement]
|
||||
layout(goalProgress, deviceParams)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
).build()
|
||||
}
|
||||
|
||||
// TODO: Supply resources (graphics) for the Tile.
|
||||
override fun onResourcesRequest(requestParams: ResourcesRequest) = serviceScope.future {
|
||||
Resources.Builder()
|
||||
.setVersion(RESOURCES_VERSION)
|
||||
.addIdToImageMapping(
|
||||
ID_IMAGE_START_RUN,
|
||||
ImageResource.Builder()
|
||||
.setAndroidResourceByResId(
|
||||
AndroidImageResourceByResId.Builder()
|
||||
.setResourceId(R.drawable.ic_run)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
// TODO: Review onDestroy() - cancellation of the serviceScope
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
// Cleans up the coroutine
|
||||
serviceScope.cancel()
|
||||
}
|
||||
|
||||
// TODO: Create root Box layout and content.
|
||||
// Creates a simple [Box] container that lays out its children one over the other. In our
|
||||
// case, an [Arc] that shows progress on top of a [Column] that includes the current steps
|
||||
// [Text], the total steps [Text], a [Spacer], and a running icon [Image].
|
||||
private fun layout(goalProgress: PowerStatus, deviceParameters: DeviceParameters) =
|
||||
Box.Builder()
|
||||
// Sets width and height to expand and take up entire Tile space.
|
||||
.setWidth(expand())
|
||||
.setHeight(expand())
|
||||
|
||||
// Adds an [Arc] via local function.
|
||||
//.addContent(progressArc(goalProgress.percentage))
|
||||
|
||||
// TODO: Add Column containing the rest of the data.
|
||||
// Adds a [Column] containing the two [Text] objects, a [Spacer], and a [Image].
|
||||
.addContent(
|
||||
Column.Builder()
|
||||
// Adds a [Text] via local function.
|
||||
.addContent(
|
||||
currentStepsText(goalProgress.generation.toString(), deviceParameters)
|
||||
)
|
||||
// Adds a [Text] via local function.
|
||||
.addContent(
|
||||
totalStepsText(
|
||||
goalProgress.consumption.toString(),
|
||||
deviceParameters
|
||||
)
|
||||
)
|
||||
// TODO: Add Spacer and Image representations of our step graphic.
|
||||
// Adds a [Spacer].
|
||||
.addContent(Spacer.Builder().setHeight(VERTICAL_SPACING_HEIGHT).build())
|
||||
// Adds an [Image] via local function.
|
||||
.addContent(startRunButton())
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
|
||||
// TODO: Create a function that constructs an Arc representation of the current step progress.
|
||||
// Creates an [Arc] representing current progress towards steps goal.
|
||||
private fun progressArc(percentage: Float) = Arc.Builder()
|
||||
.addContent(
|
||||
ArcLine.Builder()
|
||||
// Uses degrees() helper to build an [AngularDimension] which represents progress.
|
||||
.setLength(degrees(percentage * ARC_TOTAL_DEGREES))
|
||||
.setColor(argb(ContextCompat.getColor(this, R.color.primary)))
|
||||
.setThickness(PROGRESS_BAR_THICKNESS)
|
||||
.build()
|
||||
)
|
||||
// Element will start at 12 o'clock or 0 degree position in the circle.
|
||||
.setAnchorAngle(degrees(0.0f))
|
||||
// Aligns the contents of this container relative to anchor angle above.
|
||||
// ARC_ANCHOR_START - Anchors at the start of the elements. This will cause elements
|
||||
// added to an arc to begin at the given anchor_angle, and sweep around to the right.
|
||||
.setAnchorType(ARC_ANCHOR_START)
|
||||
.build()
|
||||
|
||||
// TODO: Create functions that construct/stylize Text representations of the step count & goal.
|
||||
// Creates a [Text] with current step count and stylizes it.
|
||||
private fun currentStepsText(current: String, deviceParameters: DeviceParameters) =
|
||||
Text.Builder()
|
||||
.setText(current)
|
||||
.setFontStyle(FontStyles.display2(deviceParameters).build())
|
||||
.build()
|
||||
|
||||
// Creates a [Text] with total step count goal and stylizes it.
|
||||
private fun totalStepsText(goal: String, deviceParameters: DeviceParameters) = Text.Builder()
|
||||
.setText(goal)
|
||||
.setFontStyle(FontStyles.title3(deviceParameters).build())
|
||||
.build()
|
||||
|
||||
// TODO: Create a function that constructs/stylizes a clickable Image of a running icon.
|
||||
// Creates a running icon [Image] that's also a button to refresh the tile.
|
||||
private fun startRunButton() =
|
||||
Image.Builder()
|
||||
.setWidth(BUTTON_SIZE)
|
||||
.setHeight(BUTTON_SIZE)
|
||||
.setResourceId(ID_IMAGE_START_RUN)
|
||||
.setModifiers(
|
||||
Modifiers.Builder()
|
||||
.setPadding(
|
||||
Padding.Builder()
|
||||
.setStart(BUTTON_PADDING)
|
||||
.setEnd(BUTTON_PADDING)
|
||||
.setTop(BUTTON_PADDING)
|
||||
.setBottom(BUTTON_PADDING)
|
||||
.build()
|
||||
)
|
||||
.setBackground(
|
||||
Background.Builder()
|
||||
.setCorner(Corner.Builder().setRadius(BUTTON_RADIUS).build())
|
||||
.setColor(argb(ContextCompat.getColor(this, R.color.primaryDark)))
|
||||
.build()
|
||||
)
|
||||
// TODO: Add click (START)
|
||||
.setClickable(
|
||||
Clickable.Builder()
|
||||
.setId(ID_CLICK_START_RUN)
|
||||
.setOnClick(ActionBuilders.LoadAction.Builder().build())
|
||||
.build()
|
||||
)
|
||||
// TODO: Add click (END)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.chriskaczor.homemonitor.wear
|
||||
|
||||
import com.beust.klaxon.Json
|
||||
import com.beust.klaxon.Klaxon
|
||||
import java.net.URL
|
||||
import java.sql.Timestamp
|
||||
|
||||
data class PowerStatus(
|
||||
@Json(name = "generation")
|
||||
val generation: Int,
|
||||
|
||||
@Json(name = "consumption")
|
||||
val consumption: Int,
|
||||
|
||||
@Json(name = "timestamp")
|
||||
val timestamp: String
|
||||
)
|
||||
|
||||
object PowerRepository {
|
||||
suspend fun getPowerStatus(): PowerStatus {
|
||||
val json = URL("http://home.kaczorzoo.net/api/power/status/recent").readText();
|
||||
|
||||
val data = Klaxon().parse<PowerStatus>(json) ?: return powerStatus;
|
||||
|
||||
powerStatus = powerStatus.copy(generation = data.generation, consumption = data.consumption, timestamp = data.timestamp);
|
||||
|
||||
return powerStatus;
|
||||
}
|
||||
}
|
||||
|
||||
var powerStatus =
|
||||
PowerStatus(generation = 0, consumption = 0, timestamp = Timestamp(System.currentTimeMillis()).toString())
|
||||
24
WearOS/src/src/main/res/drawable/ic_run.xml
Normal file
24
WearOS/src/src/main/res/drawable/ic_run.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2021 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="24dp">
|
||||
<path
|
||||
android:fillColor="@color/primary"
|
||||
android:pathData="M13.49,5.48c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM9.89,19.38l1,-4.4 2.1,2v6h2v-7.5l-2.1,-2 0.6,-3c1.3,1.5 3.3,2.5 5.5,2.5v-2c-1.9,0 -3.5,-1 -4.3,-2.4l-1,-1.6c-0.4,-0.6 -1,-1 -1.7,-1 -0.3,0 -0.5,0.1 -0.8,0.1l-5.2,2.2v4.7h2v-3.4l1.8,-0.7 -1.6,8.1 -4.9,-1 -0.4,2 7,1.4z" />
|
||||
</vector>
|
||||
BIN
WearOS/src/src/main/res/drawable/tile_goals.png
Normal file
BIN
WearOS/src/src/main/res/drawable/tile_goals.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
19
WearOS/src/src/main/res/values/colors.xml
Normal file
19
WearOS/src/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2021 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
<color name="primary">#C58AF9</color>
|
||||
<color name="primaryDark">#3B294B</color>
|
||||
</resources>
|
||||
11
WearOS/src/src/main/res/values/strings.xml
Normal file
11
WearOS/src/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Home Monitor</string>
|
||||
<string name="tile_description">Home Monitor</string>
|
||||
|
||||
|
||||
<string name="fitness_tile_label">Step progress</string>
|
||||
<string name="goal">/ %d steps</string>
|
||||
|
||||
<string name="placeholder_text">Time to create a tile!</string>
|
||||
</resources>
|
||||
6
WearOS/src/src/main/res/xml/network_security_config.xml
Normal file
6
WearOS/src/src/main/res/xml/network_security_config.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">home.kaczorzoo.net</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
Reference in New Issue
Block a user