mirror of
https://github.com/ckaczor/HomeMonitor.git
synced 2026-01-14 01:25:38 -05:00
More Wear OS work
This commit is contained in:
@@ -5,6 +5,7 @@ import android.os.Bundle
|
|||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.wear.tiles.manager.TileUiClient
|
import androidx.wear.tiles.manager.TileUiClient
|
||||||
|
import com.chriskaczor.homemonitor.wear.power.PowerTileService
|
||||||
|
|
||||||
class TilePreviewActivity : ComponentActivity() {
|
class TilePreviewActivity : ComponentActivity() {
|
||||||
lateinit var tileUiClient: TileUiClient
|
lateinit var tileUiClient: TileUiClient
|
||||||
|
|||||||
@@ -18,9 +18,9 @@
|
|||||||
android:value="true" />
|
android:value="true" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name="com.chriskaczor.homemonitor.wear.PowerTileService"
|
android:name="com.chriskaczor.homemonitor.wear.power.PowerTileService"
|
||||||
android:description="@string/tile_description"
|
android:description="@string/tile_description"
|
||||||
android:icon="@drawable/ic_refresh"
|
android:icon="@drawable/ic_plug"
|
||||||
android:label="@string/power_tile_label"
|
android:label="@string/power_tile_label"
|
||||||
android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
|
android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@@ -29,7 +29,22 @@
|
|||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="androidx.wear.tiles.PREVIEW"
|
android:name="androidx.wear.tiles.PREVIEW"
|
||||||
android:resource="@drawable/tile_goals" />
|
android:resource="@drawable/tile_power" />
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="com.chriskaczor.homemonitor.wear.weather.WeatherTileService"
|
||||||
|
android:description="@string/tile_description"
|
||||||
|
android:icon="@drawable/ic_sun"
|
||||||
|
android:label="@string/weather_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_weather" />
|
||||||
</service>
|
</service>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.chriskaczor.homemonitor.wear
|
package com.chriskaczor.homemonitor.wear.power
|
||||||
|
|
||||||
import com.beust.klaxon.Json
|
import com.beust.klaxon.Json
|
||||||
import com.beust.klaxon.Klaxon
|
import com.beust.klaxon.Klaxon
|
||||||
@@ -17,16 +17,11 @@ data class PowerStatus(
|
|||||||
)
|
)
|
||||||
|
|
||||||
object PowerRepository {
|
object PowerRepository {
|
||||||
suspend fun getPowerStatus(): PowerStatus {
|
suspend fun getPowerStatus(): PowerStatus? {
|
||||||
val json = URL("http://home.kaczorzoo.net/api/power/status/recent").readText();
|
val json = URL("http://home.kaczorzoo.net/api/power/status/recent").readText();
|
||||||
|
|
||||||
val data = Klaxon().parse<PowerStatus>(json) ?: return powerStatus;
|
val powerStatus = Klaxon().parse<PowerStatus>(json);
|
||||||
|
|
||||||
powerStatus = powerStatus.copy(generation = data.generation, consumption = data.consumption, timestamp = data.timestamp);
|
|
||||||
|
|
||||||
return powerStatus;
|
return powerStatus;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var powerStatus =
|
|
||||||
PowerStatus(generation = 0, consumption = 0, timestamp = Timestamp(System.currentTimeMillis()).toString())
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.chriskaczor.homemonitor.wear
|
package com.chriskaczor.homemonitor.wear.power
|
||||||
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.wear.tiles.ActionBuilders
|
import androidx.wear.tiles.ActionBuilders
|
||||||
@@ -15,6 +15,7 @@ import androidx.wear.tiles.TileBuilders.Tile
|
|||||||
import androidx.wear.tiles.TileService
|
import androidx.wear.tiles.TileService
|
||||||
import androidx.wear.tiles.TimelineBuilders.Timeline
|
import androidx.wear.tiles.TimelineBuilders.Timeline
|
||||||
import androidx.wear.tiles.TimelineBuilders.TimelineEntry
|
import androidx.wear.tiles.TimelineBuilders.TimelineEntry
|
||||||
|
import com.chriskaczor.homemonitor.wear.R
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
@@ -95,17 +96,17 @@ class PowerTileService : TileService() {
|
|||||||
serviceScope.cancel()
|
serviceScope.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun layout(goalProgress: PowerStatus, deviceParameters: DeviceParameters) =
|
private fun layout(goalProgress: PowerStatus?, deviceParameters: DeviceParameters) =
|
||||||
Box.Builder()
|
Box.Builder()
|
||||||
.setWidth(expand())
|
.setWidth(expand())
|
||||||
.setHeight(expand())
|
.setHeight(expand())
|
||||||
.addContent(
|
.addContent(
|
||||||
Column.Builder()
|
Column.Builder()
|
||||||
.addContent(
|
.addContent(
|
||||||
generationLayout(goalProgress.generation, deviceParameters)
|
generationLayout(goalProgress?.generation ?: -1, deviceParameters)
|
||||||
)
|
)
|
||||||
.addContent(
|
.addContent(
|
||||||
consumptionLayout(goalProgress.consumption, deviceParameters)
|
consumptionLayout(goalProgress?.consumption ?: -1, deviceParameters)
|
||||||
)
|
)
|
||||||
.addContent(Spacer.Builder().setHeight(VERTICAL_SPACING_HEIGHT).build())
|
.addContent(Spacer.Builder().setHeight(VERTICAL_SPACING_HEIGHT).build())
|
||||||
.addContent(refreshButton())
|
.addContent(refreshButton())
|
||||||
@@ -135,7 +136,7 @@ class PowerTileService : TileService() {
|
|||||||
)
|
)
|
||||||
.addContent(
|
.addContent(
|
||||||
Text.Builder()
|
Text.Builder()
|
||||||
.setText(generation.toString())
|
.setText(if (generation <= 0) "0" else generation.toString())
|
||||||
.setFontStyle(FontStyles.display3(deviceParameters).build())
|
.setFontStyle(FontStyles.display3(deviceParameters).build())
|
||||||
.build()
|
.build()
|
||||||
).build()
|
).build()
|
||||||
@@ -163,7 +164,7 @@ class PowerTileService : TileService() {
|
|||||||
)
|
)
|
||||||
.addContent(
|
.addContent(
|
||||||
Text.Builder()
|
Text.Builder()
|
||||||
.setText(consumption.toString())
|
.setText(if (consumption <= 0) "0" else consumption.toString())
|
||||||
.setFontStyle(FontStyles.display3(deviceParameters).build())
|
.setFontStyle(FontStyles.display3(deviceParameters).build())
|
||||||
.build()
|
.build()
|
||||||
).build()
|
).build()
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.chriskaczor.homemonitor.wear.weather
|
||||||
|
|
||||||
|
import com.beust.klaxon.Json
|
||||||
|
import com.beust.klaxon.Klaxon
|
||||||
|
import java.net.URL
|
||||||
|
import java.sql.Timestamp
|
||||||
|
|
||||||
|
data class WeatherStatus(
|
||||||
|
@Json(name = "humidity")
|
||||||
|
val humidity: Double,
|
||||||
|
|
||||||
|
@Json(name = "pressure")
|
||||||
|
val pressure: Double,
|
||||||
|
)
|
||||||
|
|
||||||
|
object WeatherRepository {
|
||||||
|
suspend fun getWeatherStatus(): WeatherStatus? {
|
||||||
|
var json = URL("http://home.kaczorzoo.net/api/weather/readings/recent").readText();
|
||||||
|
|
||||||
|
var weatherStatus = Klaxon().parse<WeatherStatus>(json);
|
||||||
|
|
||||||
|
return weatherStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
package com.chriskaczor.homemonitor.wear.weather
|
||||||
|
|
||||||
|
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 com.chriskaczor.homemonitor.wear.R
|
||||||
|
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 WeatherTileService : TileService() {
|
||||||
|
private val serviceScope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
|
override fun onTileRequest(requestParams: TileRequest) = serviceScope.future {
|
||||||
|
val powerStatus = WeatherRepository.getWeatherStatus()
|
||||||
|
|
||||||
|
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: WeatherStatus?, deviceParameters: DeviceParameters) =
|
||||||
|
Box.Builder()
|
||||||
|
.setWidth(expand())
|
||||||
|
.setHeight(expand())
|
||||||
|
.addContent(
|
||||||
|
Column.Builder()
|
||||||
|
.addContent(
|
||||||
|
generationLayout(goalProgress?.humidity ?: -1.0, deviceParameters)
|
||||||
|
)
|
||||||
|
.addContent(
|
||||||
|
consumptionLayout(goalProgress?.pressure ?: -1.0, deviceParameters)
|
||||||
|
)
|
||||||
|
.addContent(Spacer.Builder().setHeight(VERTICAL_SPACING_HEIGHT).build())
|
||||||
|
.addContent(refreshButton())
|
||||||
|
.build()
|
||||||
|
).build()
|
||||||
|
|
||||||
|
private fun generationLayout(generation: Double, 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(if (generation <= 0) "0" else generation.toString())
|
||||||
|
.setFontStyle(FontStyles.display3(deviceParameters).build())
|
||||||
|
.build()
|
||||||
|
).build()
|
||||||
|
|
||||||
|
private fun consumptionLayout(consumption: Double, 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(if (consumption <= 0) "0" else 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()
|
||||||
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
BIN
WearOS/src/src/main/res/drawable/tile_power.png
Normal file
BIN
WearOS/src/src/main/res/drawable/tile_power.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
WearOS/src/src/main/res/drawable/tile_weather.png
Normal file
BIN
WearOS/src/src/main/res/drawable/tile_weather.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -4,4 +4,5 @@
|
|||||||
<string name="tile_description">Home Monitor</string>
|
<string name="tile_description">Home Monitor</string>
|
||||||
|
|
||||||
<string name="power_tile_label">Power</string>
|
<string name="power_tile_label">Power</string>
|
||||||
|
<string name="weather_tile_label">Weather</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user