mirror of
https://github.com/home-assistant/android
synced 2024-10-04 23:29:31 +00:00
Initial Android Auto Support (#3211)
* Initial work on Android Auto support. * Get some toggling working, add a main view that is useful. * Make sure we can test this on every commit. * Migrate to full flavor.
This commit is contained in:
parent
df47acb83c
commit
ee4272a168
64
.github/workflows/beta.yml
vendored
64
.github/workflows/beta.yml
vendored
|
@ -90,3 +90,67 @@ jobs:
|
|||
with:
|
||||
version: io.homeassistant.companion.android@${{ steps.rel_number.outputs.version }}
|
||||
environment: Beta
|
||||
|
||||
play_publish:
|
||||
name: Play Publish
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3.9.0
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '11'
|
||||
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
|
||||
- uses: ./.github/actions/create-release-number
|
||||
name: Create Release Number
|
||||
id: rel_number
|
||||
with:
|
||||
beta: true
|
||||
|
||||
- uses: ./.github/actions/inflate-secrets
|
||||
name: Inflate Secrets
|
||||
with:
|
||||
keystore: ${{ secrets.UPLOAD_KEYSTORE_FILE }}
|
||||
google-services: ${{ secrets.GOOGLESERVICES }}
|
||||
firebase-creds: ${{ secrets.FIREBASECREDS }}
|
||||
playstore-creds: ${{ secrets.PLAYSTORECREDS }}
|
||||
|
||||
- uses: ./.github/actions/create-release-notes
|
||||
name: Create Release Notes
|
||||
|
||||
- uses: ./.github/actions/download-translations
|
||||
name: Download Translations
|
||||
with:
|
||||
lokalise-project: ${{ secrets.LOKALISE_PROJECT }}
|
||||
lokalise-token: ${{ secrets.LOKALISE_TOKEN }}
|
||||
|
||||
- name: Build Release
|
||||
env:
|
||||
KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_FILE_PASSWORD }}
|
||||
KEYSTORE_ALIAS: ${{ secrets.UPLOAD_KEYSTORE_ALIAS }}
|
||||
KEYSTORE_ALIAS_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_ALIAS_PASSWORD }}
|
||||
VERSION: ${{ steps.rel_number.outputs.version }}
|
||||
VERSION_CODE: ${{ steps.rel_number.outputs.version-code }}
|
||||
run: ./gradlew bundleRelease
|
||||
|
||||
- name: Deploy to Playstore Internal
|
||||
env:
|
||||
KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_FILE_PASSWORD }}
|
||||
KEYSTORE_ALIAS: ${{ secrets.UPLOAD_KEYSTORE_ALIAS }}
|
||||
KEYSTORE_ALIAS_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_ALIAS_PASSWORD }}
|
||||
VERSION: ${{ steps.rel_number.outputs.version }}
|
||||
VERSION_CODE: ${{ steps.rel_number.outputs.version-code }}
|
||||
run: ./gradlew publishReleaseBundle
|
||||
|
|
11
.github/workflows/release.yml
vendored
11
.github/workflows/release.yml
vendored
|
@ -163,7 +163,7 @@ jobs:
|
|||
VERSION_CODE: ${{ steps.rel_number.outputs.version-code }}
|
||||
run: ./gradlew bundleRelease
|
||||
|
||||
- name: Deploy to Playstore Beta
|
||||
- name: Deploy to Playstore Internal
|
||||
env:
|
||||
KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_FILE_PASSWORD }}
|
||||
KEYSTORE_ALIAS: ${{ secrets.UPLOAD_KEYSTORE_ALIAS }}
|
||||
|
@ -172,6 +172,15 @@ jobs:
|
|||
VERSION_CODE: ${{ steps.rel_number.outputs.version-code }}
|
||||
run: ./gradlew publishReleaseBundle
|
||||
|
||||
- name: Promote to Internal to Beta
|
||||
env:
|
||||
KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_FILE_PASSWORD }}
|
||||
KEYSTORE_ALIAS: ${{ secrets.UPLOAD_KEYSTORE_ALIAS }}
|
||||
KEYSTORE_ALIAS_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_ALIAS_PASSWORD }}
|
||||
VERSION: ${{ steps.rel_number.outputs.version }}
|
||||
VERSION_CODE: ${{ steps.rel_number.outputs.version-code }}
|
||||
run: ./gradlew promoteArtifact --from-track internal --promote-track beta
|
||||
|
||||
- name: Promote to Beta to Production
|
||||
env:
|
||||
KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_FILE_PASSWORD }}
|
||||
|
|
41
.github/workflows/weekly.yaml
vendored
41
.github/workflows/weekly.yaml
vendored
|
@ -5,8 +5,8 @@ on:
|
|||
schedule:
|
||||
- cron: '0 4 * * 0'
|
||||
jobs:
|
||||
play_publish:
|
||||
name: Play Publish
|
||||
play_promote:
|
||||
name: Playstore Promote
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
@ -42,15 +42,6 @@ jobs:
|
|||
firebase-creds: ${{ secrets.FIREBASECREDS }}
|
||||
playstore-creds: ${{ secrets.PLAYSTORECREDS }}
|
||||
|
||||
- uses: ./.github/actions/create-release-notes
|
||||
name: Create Release Notes
|
||||
|
||||
- uses: ./.github/actions/download-translations
|
||||
name: Download Translations
|
||||
with:
|
||||
lokalise-project: ${{ secrets.LOKALISE_PROJECT }}
|
||||
lokalise-token: ${{ secrets.LOKALISE_TOKEN }}
|
||||
|
||||
- name: Build Release
|
||||
env:
|
||||
KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_FILE_PASSWORD }}
|
||||
|
@ -58,30 +49,4 @@ jobs:
|
|||
KEYSTORE_ALIAS_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_ALIAS_PASSWORD }}
|
||||
VERSION: ${{ steps.rel_number.outputs.version }}
|
||||
VERSION_CODE: ${{ steps.rel_number.outputs.version-code }}
|
||||
run: ./gradlew bundleRelease
|
||||
|
||||
- name: Check for build need
|
||||
run: |
|
||||
commits=$(git log --since="7 days ago" --oneline | wc -l)
|
||||
echo "commits=$commits" >> $GITHUB_ENV
|
||||
|
||||
- name: Deploy to Playstore Beta
|
||||
# Only run if a new commit is present. This should prevent a new beta being created
|
||||
# incorrectly when the release is generated.
|
||||
if: env.commits != '0' || github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_FILE_PASSWORD }}
|
||||
KEYSTORE_ALIAS: ${{ secrets.UPLOAD_KEYSTORE_ALIAS }}
|
||||
KEYSTORE_ALIAS_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_ALIAS_PASSWORD }}
|
||||
VERSION: ${{ steps.rel_number.outputs.version }}
|
||||
VERSION_CODE: ${{ steps.rel_number.outputs.version-code }}
|
||||
run: ./gradlew publishReleaseBundle || echo "Issue uploading Release, may just be nothing has changed"
|
||||
|
||||
- name: Deploy to Playstore Listing
|
||||
env:
|
||||
KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_FILE_PASSWORD }}
|
||||
KEYSTORE_ALIAS: ${{ secrets.UPLOAD_KEYSTORE_ALIAS }}
|
||||
KEYSTORE_ALIAS_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_ALIAS_PASSWORD }}
|
||||
VERSION: ${{ steps.rel_number.outputs.version }}
|
||||
VERSION_CODE: ${{ steps.rel_number.outputs.version-code }}
|
||||
run: ./gradlew publishListing
|
||||
run: ./gradlew promoteArtifact --from-track internal --promote-track beta
|
||||
|
|
|
@ -130,7 +130,7 @@ android {
|
|||
|
||||
play {
|
||||
serviceAccountCredentials.set(file("playStorePublishServiceCredentialsFile.json"))
|
||||
track.set("beta")
|
||||
track.set("internal")
|
||||
resolutionStrategy.set(ResolutionStrategy.IGNORE)
|
||||
// We will depend on the wear commit.
|
||||
commit.set(true)
|
||||
|
@ -211,6 +211,8 @@ dependencies {
|
|||
|
||||
"fullImplementation"("org.burnoutcrew.composereorderable:reorderable:0.9.6")
|
||||
implementation("com.github.AppDevNext:ChangeLog:3.4")
|
||||
|
||||
"fullImplementation"("androidx.car.app:app:1.3.0-rc01")
|
||||
}
|
||||
|
||||
// Disable to fix memory leak and be compatible with the configuration cache.
|
||||
|
|
|
@ -45,6 +45,21 @@
|
|||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_color"
|
||||
android:resource="@color/colorPrimary" />
|
||||
|
||||
<meta-data
|
||||
android:name="androidx.car.app.minCarApiLevel"
|
||||
android:value="1"/>
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.car.application"
|
||||
android:resource="@xml/automotive_app_desc"/>
|
||||
<service
|
||||
android:name=".vehicle.HaCarAppService"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="androidx.car.app.CarAppService" />
|
||||
<category android:name="androidx.car.app.category.IOT"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
package io.homeassistant.companion.android.vehicle
|
||||
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.model.Action
|
||||
import androidx.car.app.model.CarColor
|
||||
import androidx.car.app.model.CarIcon
|
||||
import androidx.car.app.model.GridItem
|
||||
import androidx.car.app.model.GridTemplate
|
||||
import androidx.car.app.model.ItemList
|
||||
import androidx.car.app.model.Template
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.mikepenz.iconics.IconicsDrawable
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||
import com.mikepenz.iconics.utils.toAndroidIconCompat
|
||||
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.common.data.integration.friendlyName
|
||||
import io.homeassistant.companion.android.common.data.integration.friendlyState
|
||||
import io.homeassistant.companion.android.common.data.integration.getIcon
|
||||
import io.homeassistant.companion.android.common.data.integration.onPressed
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
class EntityGridVehicleScreen(
|
||||
carContext: CarContext,
|
||||
val integrationRepository: IntegrationRepository,
|
||||
val title: String,
|
||||
val entities: MutableMap<String, Entity<*>>,
|
||||
) : Screen(carContext) {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "EntityGridVehicleScreen"
|
||||
}
|
||||
|
||||
init {
|
||||
lifecycleScope.launch {
|
||||
integrationRepository.getEntityUpdates()?.collect { entity ->
|
||||
if (entities.containsKey(entity.entityId)) {
|
||||
entities[entity.entityId] = entity
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGetTemplate(): Template {
|
||||
val listBuilder = ItemList.Builder()
|
||||
entities.forEach { (entityId, entity) ->
|
||||
val icon = entity.getIcon(carContext) ?: CommunityMaterial.Icon.cmd_cloud_question
|
||||
listBuilder.addItem(
|
||||
GridItem.Builder()
|
||||
.setLoading(false)
|
||||
.setTitle(entity.friendlyName)
|
||||
.setText(entity.friendlyState)
|
||||
.setImage(
|
||||
CarIcon.Builder(IconicsDrawable(carContext, icon).toAndroidIconCompat())
|
||||
.setTint(CarColor.DEFAULT)
|
||||
.build()
|
||||
)
|
||||
.setOnClickListener {
|
||||
Log.i(TAG, "$entityId clicked")
|
||||
lifecycleScope.launch {
|
||||
entity.onPressed(integrationRepository)
|
||||
}
|
||||
}
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
return GridTemplate.Builder()
|
||||
.setTitle(title)
|
||||
.setHeaderAction(Action.BACK)
|
||||
.setSingleList(listBuilder.build())
|
||||
.build()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package io.homeassistant.companion.android.vehicle
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.car.app.CarAppService
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.Session
|
||||
import androidx.car.app.SessionInfo
|
||||
import androidx.car.app.validation.HostValidator
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@AndroidEntryPoint
|
||||
class HaCarAppService : CarAppService() {
|
||||
|
||||
@Inject
|
||||
lateinit var integrationRepository: IntegrationRepository
|
||||
|
||||
override fun createHostValidator(): HostValidator {
|
||||
return if (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0) {
|
||||
HostValidator.ALLOW_ALL_HOSTS_VALIDATOR
|
||||
} else {
|
||||
HostValidator.Builder(applicationContext)
|
||||
.addAllowedHosts(R.array.hosts_allowlist)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateSession(sessionInfo: SessionInfo): Session {
|
||||
return object : Session() {
|
||||
override fun onCreateScreen(intent: Intent): Screen {
|
||||
return MainVehicleScreen(carContext, integrationRepository)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package io.homeassistant.companion.android.vehicle
|
||||
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.model.Action
|
||||
import androidx.car.app.model.ItemList
|
||||
import androidx.car.app.model.ListTemplate
|
||||
import androidx.car.app.model.Row
|
||||
import androidx.car.app.model.Template
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.common.data.integration.domain
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Locale
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
class MainVehicleScreen(
|
||||
carContext: CarContext,
|
||||
val integrationRepository: IntegrationRepository,
|
||||
) : Screen(carContext) {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "MainVehicleScreen"
|
||||
|
||||
private val SUPPORTED_DOMAINS = listOf(
|
||||
"button",
|
||||
"cover",
|
||||
"input_boolean",
|
||||
"light",
|
||||
"lock",
|
||||
"scene",
|
||||
"script",
|
||||
"switch",
|
||||
)
|
||||
}
|
||||
|
||||
private val domains = mutableSetOf<String>()
|
||||
private val entities = mutableMapOf<String, Entity<*>>()
|
||||
|
||||
init {
|
||||
lifecycleScope.launch {
|
||||
integrationRepository.getEntities()?.forEach { entity ->
|
||||
val domain = entity.entityId.split(".")[0]
|
||||
if (domain in SUPPORTED_DOMAINS) {
|
||||
entities[entity.entityId] = entity
|
||||
domains.add(domain)
|
||||
}
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGetTemplate(): Template {
|
||||
val listBuilder = ItemList.Builder()
|
||||
domains.forEach { domain ->
|
||||
val friendlyDomain = domain.split("_").joinToString(" ") { word ->
|
||||
word.replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
|
||||
}
|
||||
}
|
||||
listBuilder.addItem(
|
||||
Row.Builder()
|
||||
.setTitle(friendlyDomain)
|
||||
.setOnClickListener {
|
||||
Log.i(TAG, "$domain clicked")
|
||||
screenManager.push(
|
||||
EntityGridVehicleScreen(
|
||||
carContext,
|
||||
integrationRepository,
|
||||
friendlyDomain,
|
||||
entities.filter { it.value.domain == domain }.toMutableMap()
|
||||
)
|
||||
)
|
||||
}
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Add row for zones so we can start navigation?
|
||||
|
||||
return ListTemplate.Builder()
|
||||
.setTitle(carContext.getString(io.homeassistant.companion.android.common.R.string.app_name))
|
||||
.setHeaderAction(Action.APP_ICON)
|
||||
.setSingleList(listBuilder.build())
|
||||
.build()
|
||||
}
|
||||
}
|
|
@ -840,6 +840,7 @@
|
|||
android:name=".notifications.NotificationDeleteReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
@ -94,4 +94,18 @@
|
|||
<item>@string/tile_39</item>
|
||||
<item>@string/tile_40</item>
|
||||
</string-array>
|
||||
<string-array name="hosts_allowlist" translatable="false">
|
||||
<item>fdb00c43dbde8b51cb312aa81d3b5fa17713adb94b28f598d77f8eb89daceedf,
|
||||
com.google.android.projection.gearhead</item>
|
||||
<item>70811a3eacfd2e83e18da9bfede52df16ce91f2e69a44d21f18ab66991130771,
|
||||
com.google.android.projection.gearhead</item>
|
||||
<item>1975b2f17177bc89a5dff31f9e64a6cae281a53dc1d1d59b1d147fe1c82afa00,
|
||||
com.google.android.projection.gearhead</item>
|
||||
<item>c241ffbc8e287c4e9a4ad19632ba1b1351ad361d5177b7d7b29859bd2b7fc631,
|
||||
com.google.android.apps.automotive.templates.host</item>
|
||||
<item>dd66deaf312d8daec7adbe85a218ecc8c64f3b152f9b5998d5b29300c2623f61,
|
||||
com.google.android.apps.automotive.templates.host</item>
|
||||
<item>50e603d333c6049a37bd751375d08f3bd0abebd33facd30bd17b64b89658b421,
|
||||
com.google.android.apps.automotive.templates.host</item>
|
||||
</string-array>
|
||||
</resources>
|
3
app/src/main/res/xml/automotive_app_desc.xml
Normal file
3
app/src/main/res/xml/automotive_app_desc.xml
Normal file
|
@ -0,0 +1,3 @@
|
|||
<automotiveApp>
|
||||
<uses name="template" />
|
||||
</automotiveApp>
|
|
@ -8,6 +8,7 @@ import com.mikepenz.iconics.typeface.IIcon
|
|||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||
import io.homeassistant.companion.android.common.data.websocket.impl.entities.CompressedStateDiff
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
import kotlin.math.round
|
||||
|
||||
data class Entity<T>(
|
||||
|
@ -61,7 +62,8 @@ fun Entity<Map<String, Any>>.applyCompressedStateDiff(diff: CompressedStateDiff)
|
|||
newLastChanged = calendar
|
||||
newLastUpdated = calendar
|
||||
} ?: plus.lastUpdated?.let {
|
||||
newLastUpdated = Calendar.getInstance().apply { timeInMillis = round(it * 1000).toLong() }
|
||||
newLastUpdated =
|
||||
Calendar.getInstance().apply { timeInMillis = round(it * 1000).toLong() }
|
||||
}
|
||||
plus.attributes?.let {
|
||||
newAttributes = newAttributes.plus(it)
|
||||
|
@ -144,7 +146,9 @@ fun <T> Entity<T>.getFanSteps(): Int? {
|
|||
return calculateNumStep(percentageStep * 2)
|
||||
}
|
||||
|
||||
return calculateNumStep(((attributes as Map<*, *>)["percentage_step"] as? Double)?.toDouble() ?: 1.0) - 1
|
||||
return calculateNumStep(
|
||||
((attributes as Map<*, *>)["percentage_step"] as? Double)?.toDouble() ?: 1.0
|
||||
) - 1
|
||||
} catch (e: Exception) {
|
||||
Log.e(EntityExt.TAG, "Unable to get getFanSteps")
|
||||
null
|
||||
|
@ -157,7 +161,8 @@ fun <T> Entity<T>.supportsLightBrightness(): Boolean {
|
|||
|
||||
// On HA Core 2021.5 and later brightness detection has changed
|
||||
// to simplify things in the app lets use both methods for now
|
||||
val supportedColorModes = (attributes as Map<*, *>)["supported_color_modes"] as? List<String>
|
||||
val supportedColorModes =
|
||||
(attributes as Map<*, *>)["supported_color_modes"] as? List<String>
|
||||
val supportsBrightness =
|
||||
if (supportedColorModes == null) false else (supportedColorModes - EntityExt.LIGHT_MODE_NO_BRIGHTNESS_SUPPORT).isNotEmpty()
|
||||
val supportedFeatures = attributes["supported_features"] as Int
|
||||
|
@ -178,7 +183,8 @@ fun <T> Entity<T>.getLightBrightness(): EntityPosition? {
|
|||
val minValue = 0f
|
||||
val maxValue = 100f
|
||||
val currentValue =
|
||||
((attributes as Map<*, *>)["brightness"] as? Number)?.toFloat()?.div(255f)?.times(100)
|
||||
((attributes as Map<*, *>)["brightness"] as? Number)?.toFloat()?.div(255f)
|
||||
?.times(100)
|
||||
?: 0f
|
||||
|
||||
EntityPosition(
|
||||
|
@ -199,8 +205,10 @@ fun <T> Entity<T>.supportsLightColorTemperature(): Boolean {
|
|||
return try {
|
||||
if (domain != "light") return false
|
||||
|
||||
val supportedColorModes = (attributes as Map<*, *>)["supported_color_modes"] as? List<String>
|
||||
val supportsColorTemp = supportedColorModes?.contains(EntityExt.LIGHT_MODE_COLOR_TEMP) ?: false
|
||||
val supportedColorModes =
|
||||
(attributes as Map<*, *>)["supported_color_modes"] as? List<String>
|
||||
val supportsColorTemp =
|
||||
supportedColorModes?.contains(EntityExt.LIGHT_MODE_COLOR_TEMP) ?: false
|
||||
val supportedFeatures = attributes["supported_features"] as Int
|
||||
supportsColorTemp || (supportedFeatures and EntityExt.LIGHT_SUPPORT_COLOR_TEMP_DEPR == EntityExt.LIGHT_SUPPORT_COLOR_TEMP_DEPR)
|
||||
} catch (e: Exception) {
|
||||
|
@ -395,3 +403,41 @@ private fun coverIcon(state: String?, entity: Entity<Map<String, Any?>>): IIcon
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> Entity<T>.onPressed(
|
||||
integrationRepository: IntegrationRepository
|
||||
) {
|
||||
val service = when (domain) {
|
||||
"lock" -> {
|
||||
if (state == "unlocked") "lock" else "unlock"
|
||||
}
|
||||
"cover" -> {
|
||||
if (state == "open") "close_cover" else "open_cover"
|
||||
}
|
||||
"scene",
|
||||
"script",
|
||||
"button" -> "press"
|
||||
"fan",
|
||||
"input_boolean",
|
||||
"switch" -> {
|
||||
if (state == "on") "turn_off" else "turn_on"
|
||||
}
|
||||
else -> "toggle"
|
||||
}
|
||||
|
||||
integrationRepository.callService(
|
||||
domain = this.domain,
|
||||
service = service,
|
||||
serviceData = hashMapOf("entity_id" to entityId)
|
||||
)
|
||||
}
|
||||
|
||||
val <T> Entity<T>.friendlyName: String
|
||||
get() = (attributes as? Map<*, *>)?.get("friendly_name")?.toString() ?: entityId
|
||||
|
||||
val <T> Entity<T>.friendlyState: String
|
||||
get() = state.split("_").joinToString(" ") { word ->
|
||||
word.replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue