From 1f5e5bca3a900db4d71ce80238e280967b2a3dd3 Mon Sep 17 00:00:00 2001 From: Chris Kaczor Date: Tue, 7 Sep 2021 10:27:21 -0400 Subject: [PATCH] More work on Wear OS --- WearOS/src/build.gradle | 9 +- .../homemonitor/wear/TilePreviewActivity.kt | 2 +- WearOS/src/src/main/AndroidManifest.xml | 6 +- .../homemonitor/wear/GoalsTileService.kt | 211 ------------------ .../homemonitor/wear/PowerTileService.kt | 199 +++++++++++++++++ WearOS/src/src/main/res/drawable/ic_plug.xml | 19 ++ .../src/src/main/res/drawable/ic_refresh.xml | 21 ++ WearOS/src/src/main/res/drawable/ic_run.xml | 24 -- WearOS/src/src/main/res/drawable/ic_sun.xml | 10 + WearOS/src/src/main/res/values/colors.xml | 16 +- WearOS/src/src/main/res/values/strings.xml | 6 +- 11 files changed, 258 insertions(+), 265 deletions(-) delete mode 100644 WearOS/src/src/main/java/com/chriskaczor/homemonitor/wear/GoalsTileService.kt create mode 100644 WearOS/src/src/main/java/com/chriskaczor/homemonitor/wear/PowerTileService.kt create mode 100644 WearOS/src/src/main/res/drawable/ic_plug.xml create mode 100644 WearOS/src/src/main/res/drawable/ic_refresh.xml delete mode 100644 WearOS/src/src/main/res/drawable/ic_run.xml create mode 100644 WearOS/src/src/main/res/drawable/ic_sun.xml diff --git a/WearOS/src/build.gradle b/WearOS/src/build.gradle index e976cc8..9ace641 100644 --- a/WearOS/src/build.gradle +++ b/WearOS/src/build.gradle @@ -43,14 +43,11 @@ android { 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' + implementation 'androidx.appcompat:appcompat:1.3.1' + + debugImplementation "androidx.wear.tiles:tiles-renderer:1.0.0-alpha11" } diff --git a/WearOS/src/src/debug/java/com/chriskaczor/homemonitor/wear/TilePreviewActivity.kt b/WearOS/src/src/debug/java/com/chriskaczor/homemonitor/wear/TilePreviewActivity.kt index 81fbc6a..312a86a 100644 --- a/WearOS/src/src/debug/java/com/chriskaczor/homemonitor/wear/TilePreviewActivity.kt +++ b/WearOS/src/src/debug/java/com/chriskaczor/homemonitor/wear/TilePreviewActivity.kt @@ -16,7 +16,7 @@ class TilePreviewActivity : ComponentActivity() { tileUiClient = TileUiClient( context = this, - component = ComponentName(this, GoalsTileService::class.java), + component = ComponentName(this, PowerTileService::class.java), parentView = rootLayout ) tileUiClient.connect() diff --git a/WearOS/src/src/main/AndroidManifest.xml b/WearOS/src/src/main/AndroidManifest.xml index d8b7a72..0610587 100644 --- a/WearOS/src/src/main/AndroidManifest.xml +++ b/WearOS/src/src/main/AndroidManifest.xml @@ -18,10 +18,10 @@ android:value="true" /> diff --git a/WearOS/src/src/main/java/com/chriskaczor/homemonitor/wear/GoalsTileService.kt b/WearOS/src/src/main/java/com/chriskaczor/homemonitor/wear/GoalsTileService.kt deleted file mode 100644 index 10e3e80..0000000 --- a/WearOS/src/src/main/java/com/chriskaczor/homemonitor/wear/GoalsTileService.kt +++ /dev/null @@ -1,211 +0,0 @@ -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() -} diff --git a/WearOS/src/src/main/java/com/chriskaczor/homemonitor/wear/PowerTileService.kt b/WearOS/src/src/main/java/com/chriskaczor/homemonitor/wear/PowerTileService.kt new file mode 100644 index 0000000..3491513 --- /dev/null +++ b/WearOS/src/src/main/java/com/chriskaczor/homemonitor/wear/PowerTileService.kt @@ -0,0 +1,199 @@ +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.dp +import androidx.wear.tiles.DimensionBuilders.expand +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 + +private const val RESOURCES_VERSION = "1" + +// dimensions +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) + +// identifiers +private const val ID_IMAGE_REFRESH = "image_refresh" +private const val ID_IMAGE_GENERATION = "image_generation" +private const val ID_IMAGE_CONSUMPTION = "image_consumption" +private const val ID_CLICK_REFRESH = "click_refresh" + +class PowerTileService : TileService() { + private val serviceScope = CoroutineScope(Dispatchers.IO) + + override fun onTileRequest(requestParams: TileRequest) = serviceScope.future { + val powerStatus = PowerRepository.getPowerStatus() + + val deviceParams = requestParams.deviceParameters!! + + Tile.Builder() + .setResourcesVersion(RESOURCES_VERSION) + .setTimeline( + Timeline.Builder() + .addTimelineEntry( + TimelineEntry.Builder() + .setLayout( + Layout.Builder() + .setRoot( + layout(powerStatus, deviceParams) + ).build() + ).build() + ).build() + ).build() + } + + override fun onResourcesRequest(requestParams: ResourcesRequest) = serviceScope.future { + Resources.Builder() + .setVersion(RESOURCES_VERSION) + .addIdToImageMapping( + ID_IMAGE_REFRESH, + ImageResource.Builder() + .setAndroidResourceByResId( + AndroidImageResourceByResId.Builder() + .setResourceId(R.drawable.ic_refresh) + .build() + ).build() + ) + .addIdToImageMapping( + ID_IMAGE_GENERATION, + ImageResource.Builder() + .setAndroidResourceByResId( + AndroidImageResourceByResId.Builder() + .setResourceId(R.drawable.ic_sun) + .build() + ).build() + ) + .addIdToImageMapping( + ID_IMAGE_CONSUMPTION, + ImageResource.Builder() + .setAndroidResourceByResId( + AndroidImageResourceByResId.Builder() + .setResourceId(R.drawable.ic_plug) + .build() + ).build() + ).build() + } + + override fun onDestroy() { + super.onDestroy() + serviceScope.cancel() + } + + private fun layout(goalProgress: PowerStatus, deviceParameters: DeviceParameters) = + Box.Builder() + .setWidth(expand()) + .setHeight(expand()) + .addContent( + Column.Builder() + .addContent( + generationLayout(goalProgress.generation, deviceParameters) + ) + .addContent( + consumptionLayout(goalProgress.consumption, deviceParameters) + ) + .addContent(Spacer.Builder().setHeight(VERTICAL_SPACING_HEIGHT).build()) + .addContent(refreshButton()) + .build() + ).build() + + private fun generationLayout(generation: Int, deviceParameters: DeviceParameters) = + Row.Builder() + .addContent( + Image.Builder() + .setHeight(dp(36f)) + .setWidth(dp(36f)) + .setModifiers( + Modifiers.Builder() + .setPadding( + Padding.Builder() + .setStart(dp(0f)) + .setEnd(dp(10f)) + .setTop(dp(1f)) + .setBottom(dp(0f)) + .build() + ) + .build() + ) + .setResourceId(ID_IMAGE_GENERATION) + .build() + ) + .addContent( + Text.Builder() + .setText(generation.toString()) + .setFontStyle(FontStyles.display3(deviceParameters).build()) + .build() + ).build() + + private fun consumptionLayout(consumption: Int, deviceParameters: DeviceParameters) = + Row.Builder() + .addContent( + Image.Builder() + .setHeight(dp(36f)) + .setWidth(dp(36f)) + .setModifiers( + Modifiers.Builder() + .setPadding( + Padding.Builder() + .setStart(dp(0f)) + .setEnd(dp(10f)) + .setTop(dp(1f)) + .setBottom(dp(0f)) + .build() + ) + .build() + ) + .setResourceId(ID_IMAGE_CONSUMPTION) + .build() + ) + .addContent( + Text.Builder() + .setText(consumption.toString()) + .setFontStyle(FontStyles.display3(deviceParameters).build()) + .build() + ).build() + + private fun refreshButton() = + Image.Builder() + .setWidth(BUTTON_SIZE) + .setHeight(BUTTON_SIZE) + .setResourceId(ID_IMAGE_REFRESH) + .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() + ) + .setClickable( + Clickable.Builder() + .setId(ID_CLICK_REFRESH) + .setOnClick(ActionBuilders.LoadAction.Builder().build()) + .build() + ).build() + ).build() +} diff --git a/WearOS/src/src/main/res/drawable/ic_plug.xml b/WearOS/src/src/main/res/drawable/ic_plug.xml new file mode 100644 index 0000000..c29ad04 --- /dev/null +++ b/WearOS/src/src/main/res/drawable/ic_plug.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/WearOS/src/src/main/res/drawable/ic_refresh.xml b/WearOS/src/src/main/res/drawable/ic_refresh.xml new file mode 100644 index 0000000..46036fc --- /dev/null +++ b/WearOS/src/src/main/res/drawable/ic_refresh.xml @@ -0,0 +1,21 @@ + + + + \ No newline at end of file diff --git a/WearOS/src/src/main/res/drawable/ic_run.xml b/WearOS/src/src/main/res/drawable/ic_run.xml deleted file mode 100644 index 728125c..0000000 --- a/WearOS/src/src/main/res/drawable/ic_run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - diff --git a/WearOS/src/src/main/res/drawable/ic_sun.xml b/WearOS/src/src/main/res/drawable/ic_sun.xml new file mode 100644 index 0000000..8880e07 --- /dev/null +++ b/WearOS/src/src/main/res/drawable/ic_sun.xml @@ -0,0 +1,10 @@ + + + diff --git a/WearOS/src/src/main/res/values/colors.xml b/WearOS/src/src/main/res/values/colors.xml index e8654e0..97fd946 100644 --- a/WearOS/src/src/main/res/values/colors.xml +++ b/WearOS/src/src/main/res/values/colors.xml @@ -1,18 +1,4 @@ - + #C58AF9 #3B294B diff --git a/WearOS/src/src/main/res/values/strings.xml b/WearOS/src/src/main/res/values/strings.xml index aaebadd..af3adc2 100644 --- a/WearOS/src/src/main/res/values/strings.xml +++ b/WearOS/src/src/main/res/values/strings.xml @@ -3,9 +3,5 @@ Home Monitor Home Monitor - - Step progress - / %d steps - - Time to create a tile! + Power