mirror of
https://github.com/home-assistant/android
synced 2024-09-06 17:23:32 +00:00
Add 'toggle' tap action to entity state widget (#3798)
* [WIP] Widget tap action: toggle entity * Add feedback on press and failure * Share code for pressing on entities * Align cover press action - Toggle will stop if possible when opening/closing if supported so prefer toggle instead of open/close * Toggle by default if supported - Set the default tap action for supported entities to toggle instead of refresh * Update widget description
This commit is contained in:
parent
e3ce9ed5b0
commit
7f14582909
|
@ -22,7 +22,9 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||||
import dagger.hilt.android.EntryPointAccessors
|
import dagger.hilt.android.EntryPointAccessors
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import io.homeassistant.companion.android.common.data.integration.Entity
|
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||||
|
import io.homeassistant.companion.android.common.data.integration.EntityExt
|
||||||
import io.homeassistant.companion.android.common.data.integration.getIcon
|
import io.homeassistant.companion.android.common.data.integration.getIcon
|
||||||
|
import io.homeassistant.companion.android.common.data.integration.onEntityPressedWithoutState
|
||||||
import io.homeassistant.companion.android.common.data.servers.ServerManager
|
import io.homeassistant.companion.android.common.data.servers.ServerManager
|
||||||
import io.homeassistant.companion.android.database.qs.TileDao
|
import io.homeassistant.companion.android.database.qs.TileDao
|
||||||
import io.homeassistant.companion.android.database.qs.TileEntity
|
import io.homeassistant.companion.android.database.qs.TileEntity
|
||||||
|
@ -236,22 +238,9 @@ abstract class TileExtensions : TileService() {
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
serverManager.integrationRepository(tileData.serverId).callService(
|
onEntityPressedWithoutState(
|
||||||
tileData.entityId.split(".")[0],
|
tileData.entityId,
|
||||||
when (tileData.entityId.split(".")[0]) {
|
serverManager.integrationRepository(tileData.serverId)
|
||||||
"button", "input_button" -> "press"
|
|
||||||
in toggleDomains -> "toggle"
|
|
||||||
"lock" -> {
|
|
||||||
val state = serverManager.integrationRepository(tileData.serverId).getEntity(tileData.entityId)
|
|
||||||
if (state?.state == "locked") {
|
|
||||||
"unlock"
|
|
||||||
} else {
|
|
||||||
"lock"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> "turn_on"
|
|
||||||
},
|
|
||||||
hashMapOf("entity_id" to tileData.entityId)
|
|
||||||
)
|
)
|
||||||
Log.d(TAG, "Service call sent for tile ID: $tileId")
|
Log.d(TAG, "Service call sent for tile ID: $tileId")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -342,11 +331,7 @@ abstract class TileExtensions : TileService() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "TileExtensions"
|
private const val TAG = "TileExtensions"
|
||||||
private val toggleDomains = listOf(
|
private val toggleDomainsWithLock = EntityExt.DOMAINS_TOGGLE
|
||||||
"automation", "cover", "fan", "humidifier", "input_boolean", "light",
|
|
||||||
"media_player", "remote", "siren", "switch"
|
|
||||||
)
|
|
||||||
private val toggleDomainsWithLock = toggleDomains.plus("lock")
|
|
||||||
private val validActiveStates = listOf("on", "open", "locked")
|
private val validActiveStates = listOf("on", "open", "locked")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import androidx.fragment.app.viewModels
|
||||||
import com.google.accompanist.themeadapter.material.MdcTheme
|
import com.google.accompanist.themeadapter.material.MdcTheme
|
||||||
import com.mikepenz.iconics.typeface.IIcon
|
import com.mikepenz.iconics.typeface.IIcon
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import io.homeassistant.companion.android.common.data.integration.EntityExt
|
||||||
import io.homeassistant.companion.android.settings.addHelpMenuProvider
|
import io.homeassistant.companion.android.settings.addHelpMenuProvider
|
||||||
import io.homeassistant.companion.android.settings.qs.views.ManageTilesView
|
import io.homeassistant.companion.android.settings.qs.views.ManageTilesView
|
||||||
import io.homeassistant.companion.android.util.icondialog.IconDialog
|
import io.homeassistant.companion.android.util.icondialog.IconDialog
|
||||||
|
@ -25,10 +26,7 @@ class ManageTilesFragment : Fragment() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "TileFragment"
|
private const val TAG = "TileFragment"
|
||||||
val validDomains = listOf(
|
val validDomains = EntityExt.APP_PRESS_ACTION_DOMAINS
|
||||||
"automation", "button", "cover", "fan", "humidifier", "input_boolean", "input_button", "light",
|
|
||||||
"lock", "media_player", "remote", "siren", "scene", "script", "switch"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val viewModel: ManageTilesViewModel by viewModels()
|
val viewModel: ManageTilesViewModel by viewModels()
|
||||||
|
|
|
@ -12,6 +12,7 @@ import android.util.Log
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.toColorInt
|
import androidx.core.graphics.toColorInt
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
|
@ -20,9 +21,11 @@ import io.homeassistant.companion.android.R
|
||||||
import io.homeassistant.companion.android.common.data.integration.Entity
|
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||||
import io.homeassistant.companion.android.common.data.integration.canSupportPrecision
|
import io.homeassistant.companion.android.common.data.integration.canSupportPrecision
|
||||||
import io.homeassistant.companion.android.common.data.integration.friendlyState
|
import io.homeassistant.companion.android.common.data.integration.friendlyState
|
||||||
|
import io.homeassistant.companion.android.common.data.integration.onEntityPressedWithoutState
|
||||||
import io.homeassistant.companion.android.database.widget.StaticWidgetDao
|
import io.homeassistant.companion.android.database.widget.StaticWidgetDao
|
||||||
import io.homeassistant.companion.android.database.widget.StaticWidgetEntity
|
import io.homeassistant.companion.android.database.widget.StaticWidgetEntity
|
||||||
import io.homeassistant.companion.android.database.widget.WidgetBackgroundType
|
import io.homeassistant.companion.android.database.widget.WidgetBackgroundType
|
||||||
|
import io.homeassistant.companion.android.database.widget.WidgetTapAction
|
||||||
import io.homeassistant.companion.android.util.getAttribute
|
import io.homeassistant.companion.android.util.getAttribute
|
||||||
import io.homeassistant.companion.android.widgets.BaseWidgetProvider
|
import io.homeassistant.companion.android.widgets.BaseWidgetProvider
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -34,6 +37,8 @@ class EntityWidget : BaseWidgetProvider() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "StaticWidget"
|
private const val TAG = "StaticWidget"
|
||||||
|
internal const val TOGGLE_ENTITY =
|
||||||
|
"io.homeassistant.companion.android.widgets.entity.EntityWidget.TOGGLE_ENTITY"
|
||||||
|
|
||||||
internal const val EXTRA_SERVER_ID = "EXTRA_SERVER_ID"
|
internal const val EXTRA_SERVER_ID = "EXTRA_SERVER_ID"
|
||||||
internal const val EXTRA_ENTITY_ID = "EXTRA_ENTITY_ID"
|
internal const val EXTRA_ENTITY_ID = "EXTRA_ENTITY_ID"
|
||||||
|
@ -42,6 +47,7 @@ class EntityWidget : BaseWidgetProvider() {
|
||||||
internal const val EXTRA_TEXT_SIZE = "EXTRA_TEXT_SIZE"
|
internal const val EXTRA_TEXT_SIZE = "EXTRA_TEXT_SIZE"
|
||||||
internal const val EXTRA_STATE_SEPARATOR = "EXTRA_STATE_SEPARATOR"
|
internal const val EXTRA_STATE_SEPARATOR = "EXTRA_STATE_SEPARATOR"
|
||||||
internal const val EXTRA_ATTRIBUTE_SEPARATOR = "EXTRA_ATTRIBUTE_SEPARATOR"
|
internal const val EXTRA_ATTRIBUTE_SEPARATOR = "EXTRA_ATTRIBUTE_SEPARATOR"
|
||||||
|
internal const val EXTRA_TAP_ACTION = "EXTRA_TAP_ACTION"
|
||||||
internal const val EXTRA_BACKGROUND_TYPE = "EXTRA_BACKGROUND_TYPE"
|
internal const val EXTRA_BACKGROUND_TYPE = "EXTRA_BACKGROUND_TYPE"
|
||||||
internal const val EXTRA_TEXT_COLOR = "EXTRA_TEXT_COLOR"
|
internal const val EXTRA_TEXT_COLOR = "EXTRA_TEXT_COLOR"
|
||||||
|
|
||||||
|
@ -55,12 +61,13 @@ class EntityWidget : BaseWidgetProvider() {
|
||||||
ComponentName(context, EntityWidget::class.java)
|
ComponentName(context, EntityWidget::class.java)
|
||||||
|
|
||||||
override suspend fun getWidgetRemoteViews(context: Context, appWidgetId: Int, suggestedEntity: Entity<Map<String, Any>>?): RemoteViews {
|
override suspend fun getWidgetRemoteViews(context: Context, appWidgetId: Int, suggestedEntity: Entity<Map<String, Any>>?): RemoteViews {
|
||||||
|
val widget = staticWidgetDao.get(appWidgetId)
|
||||||
|
|
||||||
val intent = Intent(context, EntityWidget::class.java).apply {
|
val intent = Intent(context, EntityWidget::class.java).apply {
|
||||||
action = UPDATE_VIEW
|
action = if (widget?.tapAction == WidgetTapAction.TOGGLE) TOGGLE_ENTITY else UPDATE_VIEW
|
||||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
|
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
|
||||||
}
|
}
|
||||||
|
|
||||||
val widget = staticWidgetDao.get(appWidgetId)
|
|
||||||
val useDynamicColors = widget?.backgroundType == WidgetBackgroundType.DYNAMICCOLOR && DynamicColors.isDynamicColorAvailable()
|
val useDynamicColors = widget?.backgroundType == WidgetBackgroundType.DYNAMICCOLOR && DynamicColors.isDynamicColorAvailable()
|
||||||
val views = RemoteViews(context.packageName, if (useDynamicColors) R.layout.widget_static_wrapper_dynamiccolor else R.layout.widget_static_wrapper_default).apply {
|
val views = RemoteViews(context.packageName, if (useDynamicColors) R.layout.widget_static_wrapper_dynamiccolor else R.layout.widget_static_wrapper_default).apply {
|
||||||
if (widget != null) {
|
if (widget != null) {
|
||||||
|
@ -83,6 +90,14 @@ class EntityWidget : BaseWidgetProvider() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
|
setViewVisibility(
|
||||||
|
R.id.widgetTextLayout,
|
||||||
|
View.VISIBLE
|
||||||
|
)
|
||||||
|
setViewVisibility(
|
||||||
|
R.id.widgetProgressBar,
|
||||||
|
View.INVISIBLE
|
||||||
|
)
|
||||||
val resolvedText = resolveTextToShow(
|
val resolvedText = resolveTextToShow(
|
||||||
context,
|
context,
|
||||||
serverId,
|
serverId,
|
||||||
|
@ -194,6 +209,12 @@ class EntityWidget : BaseWidgetProvider() {
|
||||||
val textSizeSelection: String? = extras.getString(EXTRA_TEXT_SIZE)
|
val textSizeSelection: String? = extras.getString(EXTRA_TEXT_SIZE)
|
||||||
val stateSeparatorSelection: String? = extras.getString(EXTRA_STATE_SEPARATOR)
|
val stateSeparatorSelection: String? = extras.getString(EXTRA_STATE_SEPARATOR)
|
||||||
val attributeSeparatorSelection: String? = extras.getString(EXTRA_ATTRIBUTE_SEPARATOR)
|
val attributeSeparatorSelection: String? = extras.getString(EXTRA_ATTRIBUTE_SEPARATOR)
|
||||||
|
val tapActionSelection: WidgetTapAction = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
extras.getSerializable(EXTRA_TAP_ACTION, WidgetTapAction::class.java)
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
extras.getSerializable(EXTRA_TAP_ACTION) as? WidgetTapAction
|
||||||
|
} ?: WidgetTapAction.REFRESH
|
||||||
val backgroundTypeSelection: WidgetBackgroundType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
val backgroundTypeSelection: WidgetBackgroundType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
extras.getSerializable(EXTRA_BACKGROUND_TYPE, WidgetBackgroundType::class.java)
|
extras.getSerializable(EXTRA_BACKGROUND_TYPE, WidgetBackgroundType::class.java)
|
||||||
} else {
|
} else {
|
||||||
|
@ -224,6 +245,7 @@ class EntityWidget : BaseWidgetProvider() {
|
||||||
textSizeSelection?.toFloatOrNull() ?: 30F,
|
textSizeSelection?.toFloatOrNull() ?: 30F,
|
||||||
stateSeparatorSelection ?: "",
|
stateSeparatorSelection ?: "",
|
||||||
attributeSeparatorSelection ?: "",
|
attributeSeparatorSelection ?: "",
|
||||||
|
tapActionSelection,
|
||||||
staticWidgetDao.get(appWidgetId)?.lastUpdate ?: "",
|
staticWidgetDao.get(appWidgetId)?.lastUpdate ?: "",
|
||||||
backgroundTypeSelection,
|
backgroundTypeSelection,
|
||||||
textColorSelection
|
textColorSelection
|
||||||
|
@ -241,6 +263,45 @@ class EntityWidget : BaseWidgetProvider() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun toggleEntity(context: Context, appWidgetId: Int) {
|
||||||
|
widgetScope?.launch {
|
||||||
|
// Show progress bar as feedback
|
||||||
|
val appWidgetManager = AppWidgetManager.getInstance(context)
|
||||||
|
val loadingViews = RemoteViews(context.packageName, R.layout.widget_static)
|
||||||
|
loadingViews.setViewVisibility(R.id.widgetProgressBar, View.VISIBLE)
|
||||||
|
loadingViews.setViewVisibility(R.id.widgetTextLayout, View.GONE)
|
||||||
|
appWidgetManager.partiallyUpdateAppWidget(appWidgetId, loadingViews)
|
||||||
|
|
||||||
|
var success = false
|
||||||
|
staticWidgetDao.get(appWidgetId)?.let {
|
||||||
|
try {
|
||||||
|
onEntityPressedWithoutState(
|
||||||
|
it.entityId,
|
||||||
|
serverManager.integrationRepository(it.serverId)
|
||||||
|
)
|
||||||
|
success = true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Unable to send toggle service call", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
Toast.makeText(context, commonR.string.service_call_failure, Toast.LENGTH_LONG).show()
|
||||||
|
|
||||||
|
val views = getWidgetRemoteViews(context, appWidgetId)
|
||||||
|
appWidgetManager.updateAppWidget(appWidgetId, views)
|
||||||
|
} // else update will be triggered by websocket subscription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
|
||||||
|
super.onReceive(context, intent)
|
||||||
|
when (lastIntent) {
|
||||||
|
TOGGLE_ENTITY -> toggleEntity(context, appWidgetId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
|
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
|
||||||
widgetScope?.launch {
|
widgetScope?.launch {
|
||||||
staticWidgetDao.deleteAll(appWidgetIds)
|
staticWidgetDao.deleteAll(appWidgetIds)
|
||||||
|
|
|
@ -23,8 +23,11 @@ import androidx.lifecycle.lifecycleScope
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.homeassistant.companion.android.common.data.integration.Entity
|
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||||
|
import io.homeassistant.companion.android.common.data.integration.EntityExt
|
||||||
|
import io.homeassistant.companion.android.common.data.integration.domain
|
||||||
import io.homeassistant.companion.android.database.widget.StaticWidgetDao
|
import io.homeassistant.companion.android.database.widget.StaticWidgetDao
|
||||||
import io.homeassistant.companion.android.database.widget.WidgetBackgroundType
|
import io.homeassistant.companion.android.database.widget.WidgetBackgroundType
|
||||||
|
import io.homeassistant.companion.android.database.widget.WidgetTapAction
|
||||||
import io.homeassistant.companion.android.databinding.WidgetStaticConfigureBinding
|
import io.homeassistant.companion.android.databinding.WidgetStaticConfigureBinding
|
||||||
import io.homeassistant.companion.android.settings.widgets.ManageWidgetsViewModel
|
import io.homeassistant.companion.android.settings.widgets.ManageWidgetsViewModel
|
||||||
import io.homeassistant.companion.android.util.getHexForColor
|
import io.homeassistant.companion.android.util.getHexForColor
|
||||||
|
@ -122,6 +125,9 @@ class EntityWidgetConfigureActivity : BaseWidgetConfigureActivity() {
|
||||||
|
|
||||||
val staticWidget = staticWidgetDao.get(appWidgetId)
|
val staticWidget = staticWidgetDao.get(appWidgetId)
|
||||||
|
|
||||||
|
val tapActionValues = listOf(getString(commonR.string.widget_tap_action_toggle), getString(commonR.string.refresh))
|
||||||
|
binding.tapActionList.adapter = ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, tapActionValues)
|
||||||
|
|
||||||
val backgroundTypeValues = mutableListOf(
|
val backgroundTypeValues = mutableListOf(
|
||||||
getString(commonR.string.widget_background_type_daynight),
|
getString(commonR.string.widget_background_type_daynight),
|
||||||
getString(commonR.string.widget_background_type_transparent)
|
getString(commonR.string.widget_background_type_transparent)
|
||||||
|
@ -162,6 +168,10 @@ class EntityWidgetConfigureActivity : BaseWidgetConfigureActivity() {
|
||||||
setupAttributes()
|
setupAttributes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val toggleable = entity?.domain in EntityExt.APP_PRESS_ACTION_DOMAINS
|
||||||
|
binding.tapAction.isVisible = toggleable
|
||||||
|
binding.tapActionList.setSelection(if (toggleable && staticWidget.tapAction == WidgetTapAction.TOGGLE) 0 else 1)
|
||||||
|
|
||||||
binding.backgroundType.setSelection(
|
binding.backgroundType.setSelection(
|
||||||
when {
|
when {
|
||||||
staticWidget.backgroundType == WidgetBackgroundType.DYNAMICCOLOR && DynamicColors.isDynamicColorAvailable() ->
|
staticWidget.backgroundType == WidgetBackgroundType.DYNAMICCOLOR && DynamicColors.isDynamicColorAvailable() ->
|
||||||
|
@ -272,6 +282,9 @@ class EntityWidgetConfigureActivity : BaseWidgetConfigureActivity() {
|
||||||
attributesAdapter.addAll(*fetchedAttributes?.keys.orEmpty().toTypedArray())
|
attributesAdapter.addAll(*fetchedAttributes?.keys.orEmpty().toTypedArray())
|
||||||
binding.widgetTextConfigAttribute.setTokenizer(CommaTokenizer())
|
binding.widgetTextConfigAttribute.setTokenizer(CommaTokenizer())
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
|
val toggleable = selectedEntity?.domain in EntityExt.APP_PRESS_ACTION_DOMAINS
|
||||||
|
binding.tapAction.isVisible = toggleable
|
||||||
|
binding.tapActionList.setSelection(if (toggleable) 0 else 1)
|
||||||
attributesAdapter.notifyDataSetChanged()
|
attributesAdapter.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -342,6 +355,14 @@ class EntityWidgetConfigureActivity : BaseWidgetConfigureActivity() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
intent.putExtra(
|
||||||
|
EntityWidget.EXTRA_TAP_ACTION,
|
||||||
|
when (binding.tapActionList.selectedItemPosition) {
|
||||||
|
0 -> WidgetTapAction.TOGGLE
|
||||||
|
else -> WidgetTapAction.REFRESH
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
intent.putExtra(
|
intent.putExtra(
|
||||||
EntityWidget.EXTRA_BACKGROUND_TYPE,
|
EntityWidget.EXTRA_BACKGROUND_TYPE,
|
||||||
when (binding.backgroundType.selectedItem as String?) {
|
when (binding.backgroundType.selectedItem as String?) {
|
||||||
|
|
|
@ -56,4 +56,14 @@
|
||||||
android:maxLines="2" />
|
android:maxLines="2" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/widgetProgressBar"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:visibility="invisible"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:indeterminateTint="@color/colorAccent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
/>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
|
@ -196,6 +196,28 @@
|
||||||
android:inputType="text" />
|
android:inputType="text" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/tap_action"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:text="@string/widget_tap_action_label" />
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/tap_action_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -42,6 +42,17 @@ object EntityExt {
|
||||||
const val LIGHT_SUPPORT_BRIGHTNESS_DEPR = 1
|
const val LIGHT_SUPPORT_BRIGHTNESS_DEPR = 1
|
||||||
const val LIGHT_SUPPORT_COLOR_TEMP_DEPR = 2
|
const val LIGHT_SUPPORT_COLOR_TEMP_DEPR = 2
|
||||||
const val ALARM_CONTROL_PANEL_SUPPORT_ARM_AWAY = 2
|
const val ALARM_CONTROL_PANEL_SUPPORT_ARM_AWAY = 2
|
||||||
|
|
||||||
|
val DOMAINS_PRESS = listOf("button", "input_button")
|
||||||
|
val DOMAINS_TOGGLE = listOf(
|
||||||
|
"automation", "cover", "fan", "humidifier", "input_boolean", "light", "lock",
|
||||||
|
"media_player", "remote", "siren", "switch"
|
||||||
|
)
|
||||||
|
|
||||||
|
val APP_PRESS_ACTION_DOMAINS = DOMAINS_PRESS + DOMAINS_TOGGLE + listOf(
|
||||||
|
"scene",
|
||||||
|
"script"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val <T> Entity<T>.domain: String
|
val <T> Entity<T>.domain: String
|
||||||
|
@ -601,14 +612,10 @@ suspend fun <T> Entity<T>.onPressed(
|
||||||
"lock" -> {
|
"lock" -> {
|
||||||
if (state == "unlocked") "lock" else "unlock"
|
if (state == "unlocked") "lock" else "unlock"
|
||||||
}
|
}
|
||||||
"cover" -> {
|
|
||||||
if (state == "open") "close_cover" else "open_cover"
|
|
||||||
}
|
|
||||||
"alarm_control_panel" -> {
|
"alarm_control_panel" -> {
|
||||||
if (state != "disarmed") "alarm_disarm" else "alarm_arm_away"
|
if (state != "disarmed") "alarm_disarm" else "alarm_arm_away"
|
||||||
}
|
}
|
||||||
"button",
|
in EntityExt.DOMAINS_PRESS -> "press"
|
||||||
"input_button" -> "press"
|
|
||||||
"fan",
|
"fan",
|
||||||
"input_boolean",
|
"input_boolean",
|
||||||
"script",
|
"script",
|
||||||
|
@ -626,6 +633,36 @@ suspend fun <T> Entity<T>.onPressed(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute an app press action like [Entity.onPressed], but without a current state if possible to
|
||||||
|
* speed up the execution.
|
||||||
|
* @throws IntegrationException on network errors
|
||||||
|
*/
|
||||||
|
suspend fun onEntityPressedWithoutState(
|
||||||
|
entityId: String,
|
||||||
|
integrationRepository: IntegrationRepository
|
||||||
|
) {
|
||||||
|
val domain = entityId.split(".")[0]
|
||||||
|
val service = when (domain) {
|
||||||
|
"lock" -> {
|
||||||
|
val lockEntity = try {
|
||||||
|
integrationRepository.getEntity(entityId)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
if (lockEntity?.state == "locked") "unlock" else "lock"
|
||||||
|
}
|
||||||
|
in EntityExt.DOMAINS_PRESS -> "press"
|
||||||
|
in EntityExt.DOMAINS_TOGGLE -> "toggle"
|
||||||
|
else -> "turn_on"
|
||||||
|
}
|
||||||
|
integrationRepository.callService(
|
||||||
|
domain = domain,
|
||||||
|
service = service,
|
||||||
|
serviceData = hashMapOf("entity_id" to entityId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val <T> Entity<T>.friendlyName: String
|
val <T> Entity<T>.friendlyName: String
|
||||||
get() = (attributes as? Map<*, *>)?.get("friendly_name")?.toString() ?: entityId
|
get() = (attributes as? Map<*, *>)?.get("friendly_name")?.toString() ?: entityId
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,7 @@ import io.homeassistant.companion.android.database.widget.StaticWidgetEntity
|
||||||
import io.homeassistant.companion.android.database.widget.TemplateWidgetDao
|
import io.homeassistant.companion.android.database.widget.TemplateWidgetDao
|
||||||
import io.homeassistant.companion.android.database.widget.TemplateWidgetEntity
|
import io.homeassistant.companion.android.database.widget.TemplateWidgetEntity
|
||||||
import io.homeassistant.companion.android.database.widget.WidgetBackgroundTypeConverter
|
import io.homeassistant.companion.android.database.widget.WidgetBackgroundTypeConverter
|
||||||
|
import io.homeassistant.companion.android.database.widget.WidgetTapActionConverter
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import io.homeassistant.companion.android.common.R as commonR
|
import io.homeassistant.companion.android.common.R as commonR
|
||||||
|
@ -90,7 +91,7 @@ import io.homeassistant.companion.android.common.R as commonR
|
||||||
Server::class,
|
Server::class,
|
||||||
Setting::class
|
Setting::class
|
||||||
],
|
],
|
||||||
version = 42,
|
version = 43,
|
||||||
autoMigrations = [
|
autoMigrations = [
|
||||||
AutoMigration(from = 24, to = 25),
|
AutoMigration(from = 24, to = 25),
|
||||||
AutoMigration(from = 25, to = 26),
|
AutoMigration(from = 25, to = 26),
|
||||||
|
@ -108,7 +109,8 @@ import io.homeassistant.companion.android.common.R as commonR
|
||||||
AutoMigration(from = 37, to = 38, spec = AppDatabase.Companion.Migration37to38::class),
|
AutoMigration(from = 37, to = 38, spec = AppDatabase.Companion.Migration37to38::class),
|
||||||
AutoMigration(from = 38, to = 39),
|
AutoMigration(from = 38, to = 39),
|
||||||
AutoMigration(from = 39, to = 40),
|
AutoMigration(from = 39, to = 40),
|
||||||
AutoMigration(from = 41, to = 42)
|
AutoMigration(from = 41, to = 42),
|
||||||
|
AutoMigration(from = 42, to = 43)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@TypeConverters(
|
@TypeConverters(
|
||||||
|
@ -116,7 +118,8 @@ import io.homeassistant.companion.android.common.R as commonR
|
||||||
LocalSensorSettingConverter::class,
|
LocalSensorSettingConverter::class,
|
||||||
EntriesTypeConverter::class,
|
EntriesTypeConverter::class,
|
||||||
SensorSettingTypeConverter::class,
|
SensorSettingTypeConverter::class,
|
||||||
WidgetBackgroundTypeConverter::class
|
WidgetBackgroundTypeConverter::class,
|
||||||
|
WidgetTapActionConverter::class
|
||||||
)
|
)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
abstract fun authenticationDao(): AuthenticationDao
|
abstract fun authenticationDao(): AuthenticationDao
|
||||||
|
|
|
@ -22,6 +22,8 @@ data class StaticWidgetEntity(
|
||||||
val stateSeparator: String = "",
|
val stateSeparator: String = "",
|
||||||
@ColumnInfo(name = "attribute_separator")
|
@ColumnInfo(name = "attribute_separator")
|
||||||
val attributeSeparator: String = "",
|
val attributeSeparator: String = "",
|
||||||
|
@ColumnInfo(name = "tap_action", defaultValue = "REFRESH")
|
||||||
|
val tapAction: WidgetTapAction,
|
||||||
@ColumnInfo(name = "last_update")
|
@ColumnInfo(name = "last_update")
|
||||||
val lastUpdate: String,
|
val lastUpdate: String,
|
||||||
@ColumnInfo(name = "background_type", defaultValue = "DAYNIGHT")
|
@ColumnInfo(name = "background_type", defaultValue = "DAYNIGHT")
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package io.homeassistant.companion.android.database.widget
|
||||||
|
|
||||||
|
import androidx.room.TypeConverter
|
||||||
|
|
||||||
|
enum class WidgetTapAction {
|
||||||
|
REFRESH, TOGGLE
|
||||||
|
}
|
||||||
|
|
||||||
|
class WidgetTapActionConverter {
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun toWidgetTapAction(setting: String): WidgetTapAction = WidgetTapAction.valueOf(setting)
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun fromWidgetBackgroundType(setting: WidgetTapAction): String = setting.name
|
||||||
|
}
|
|
@ -224,7 +224,7 @@
|
||||||
<string name="entity_attribute_checkbox">Append attribute value</string>
|
<string name="entity_attribute_checkbox">Append attribute value</string>
|
||||||
<string name="entity_id">Entity ID</string>
|
<string name="entity_id">Entity ID</string>
|
||||||
<string name="entity_id_name">Entity ID: %1$s</string>
|
<string name="entity_id_name">Entity ID: %1$s</string>
|
||||||
<string name="entity_widget_desc">Current state and attribute of any entity</string>
|
<string name="entity_widget_desc">View any entity\'s current state and toggle it</string>
|
||||||
<string name="error_auth_revoked">It appears that your authorization was revoked, please reconnect to Home Assistant.</string>
|
<string name="error_auth_revoked">It appears that your authorization was revoked, please reconnect to Home Assistant.</string>
|
||||||
<string name="error_connection_failed">Unable to connect to Home Assistant.</string>
|
<string name="error_connection_failed">Unable to connect to Home Assistant.</string>
|
||||||
<string name="error_loading_entities">Error loading entities</string>
|
<string name="error_loading_entities">Error loading entities</string>
|
||||||
|
@ -956,6 +956,8 @@
|
||||||
<string name="widget_spinner_server">Server:</string>
|
<string name="widget_spinner_server">Server:</string>
|
||||||
<string name="widget_state_separator_label">State and attribute separator:</string>
|
<string name="widget_state_separator_label">State and attribute separator:</string>
|
||||||
<string name="widget_static_image_description">Entity state</string>
|
<string name="widget_static_image_description">Entity state</string>
|
||||||
|
<string name="widget_tap_action_label">Tap action:</string>
|
||||||
|
<string name="widget_tap_action_toggle">Toggle</string>
|
||||||
<string name="widget_template_error">Unable to render the template</string>
|
<string name="widget_template_error">Unable to render the template</string>
|
||||||
<string name="widget_text_hint_label">Label</string>
|
<string name="widget_text_hint_label">Label</string>
|
||||||
<string name="widget_text_hint_service_data">Entity ID</string>
|
<string name="widget_text_hint_service_data">Entity ID</string>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import io.homeassistant.companion.android.BuildConfig
|
||||||
import io.homeassistant.companion.android.common.data.authentication.SessionState
|
import io.homeassistant.companion.android.common.data.authentication.SessionState
|
||||||
import io.homeassistant.companion.android.common.data.integration.DeviceRegistration
|
import io.homeassistant.companion.android.common.data.integration.DeviceRegistration
|
||||||
import io.homeassistant.companion.android.common.data.integration.Entity
|
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||||
|
import io.homeassistant.companion.android.common.data.integration.EntityExt
|
||||||
import io.homeassistant.companion.android.common.data.prefs.WearPrefsRepository
|
import io.homeassistant.companion.android.common.data.prefs.WearPrefsRepository
|
||||||
import io.homeassistant.companion.android.common.data.servers.ServerManager
|
import io.homeassistant.companion.android.common.data.servers.ServerManager
|
||||||
import io.homeassistant.companion.android.common.data.websocket.WebSocketState
|
import io.homeassistant.companion.android.common.data.websocket.WebSocketState
|
||||||
|
@ -31,10 +32,6 @@ class HomePresenterImpl @Inject constructor(
|
||||||
) : HomePresenter {
|
) : HomePresenter {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val toggleDomains = listOf(
|
|
||||||
"cover", "fan", "humidifier", "input_boolean", "light", "lock",
|
|
||||||
"media_player", "remote", "siren", "switch"
|
|
||||||
)
|
|
||||||
val domainsWithNames = mapOf(
|
val domainsWithNames = mapOf(
|
||||||
"button" to commonR.string.buttons,
|
"button" to commonR.string.buttons,
|
||||||
"cover" to commonR.string.covers,
|
"cover" to commonR.string.covers,
|
||||||
|
@ -97,7 +94,7 @@ class HomePresenterImpl @Inject constructor(
|
||||||
"lock"
|
"lock"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
in toggleDomains -> "toggle"
|
in EntityExt.DOMAINS_TOGGLE -> "toggle"
|
||||||
else -> "turn_on"
|
else -> "turn_on"
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -41,7 +41,6 @@ import io.homeassistant.companion.android.common.data.integration.getLightBright
|
||||||
import io.homeassistant.companion.android.common.data.integration.supportsFanSetSpeed
|
import io.homeassistant.companion.android.common.data.integration.supportsFanSetSpeed
|
||||||
import io.homeassistant.companion.android.common.data.integration.supportsLightBrightness
|
import io.homeassistant.companion.android.common.data.integration.supportsLightBrightness
|
||||||
import io.homeassistant.companion.android.common.data.integration.supportsLightColorTemperature
|
import io.homeassistant.companion.android.common.data.integration.supportsLightColorTemperature
|
||||||
import io.homeassistant.companion.android.home.HomePresenterImpl
|
|
||||||
import io.homeassistant.companion.android.theme.WearAppTheme
|
import io.homeassistant.companion.android.theme.WearAppTheme
|
||||||
import io.homeassistant.companion.android.util.getColorTemperature
|
import io.homeassistant.companion.android.util.getColorTemperature
|
||||||
import io.homeassistant.companion.android.util.onEntityClickedFeedback
|
import io.homeassistant.companion.android.util.onEntityClickedFeedback
|
||||||
|
@ -84,7 +83,7 @@ fun DetailsPanelView(
|
||||||
val friendlyName = attributes["friendly_name"].toString()
|
val friendlyName = attributes["friendly_name"].toString()
|
||||||
Text(friendlyName)
|
Text(friendlyName)
|
||||||
|
|
||||||
if (entity.domain in HomePresenterImpl.toggleDomains) {
|
if (entity.domain in EntityExt.DOMAINS_TOGGLE) {
|
||||||
val isChecked = entity.state in listOf("on", "locked", "open", "opening")
|
val isChecked = entity.state in listOf("on", "locked", "open", "opening")
|
||||||
ToggleButton(
|
ToggleButton(
|
||||||
checked = isChecked,
|
checked = isChecked,
|
||||||
|
|
|
@ -23,9 +23,9 @@ import com.mikepenz.iconics.compose.Image
|
||||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||||
import io.homeassistant.companion.android.common.R
|
import io.homeassistant.companion.android.common.R
|
||||||
import io.homeassistant.companion.android.common.data.integration.Entity
|
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||||
|
import io.homeassistant.companion.android.common.data.integration.EntityExt
|
||||||
import io.homeassistant.companion.android.common.data.integration.domain
|
import io.homeassistant.companion.android.common.data.integration.domain
|
||||||
import io.homeassistant.companion.android.common.data.integration.getIcon
|
import io.homeassistant.companion.android.common.data.integration.getIcon
|
||||||
import io.homeassistant.companion.android.home.HomePresenterImpl
|
|
||||||
import io.homeassistant.companion.android.theme.wearColorPalette
|
import io.homeassistant.companion.android.theme.wearColorPalette
|
||||||
import io.homeassistant.companion.android.util.WearToggleChip
|
import io.homeassistant.companion.android.util.WearToggleChip
|
||||||
import io.homeassistant.companion.android.util.onEntityClickedFeedback
|
import io.homeassistant.companion.android.util.onEntityClickedFeedback
|
||||||
|
@ -46,7 +46,7 @@ fun EntityUi(
|
||||||
val iconBitmap = entity.getIcon(LocalContext.current)
|
val iconBitmap = entity.getIcon(LocalContext.current)
|
||||||
val friendlyName = attributes["friendly_name"].toString()
|
val friendlyName = attributes["friendly_name"].toString()
|
||||||
|
|
||||||
if (entity.domain in HomePresenterImpl.toggleDomains) {
|
if (entity.domain in EntityExt.DOMAINS_TOGGLE) {
|
||||||
val isChecked = entity.state in listOf("on", "locked", "open", "opening")
|
val isChecked = entity.state in listOf("on", "locked", "open", "opening")
|
||||||
ToggleChip(
|
ToggleChip(
|
||||||
checked = isChecked,
|
checked = isChecked,
|
||||||
|
|
|
@ -10,9 +10,9 @@ import android.os.VibratorManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import io.homeassistant.companion.android.common.data.integration.onEntityPressedWithoutState
|
||||||
import io.homeassistant.companion.android.common.data.prefs.WearPrefsRepository
|
import io.homeassistant.companion.android.common.data.prefs.WearPrefsRepository
|
||||||
import io.homeassistant.companion.android.common.data.servers.ServerManager
|
import io.homeassistant.companion.android.common.data.servers.ServerManager
|
||||||
import io.homeassistant.companion.android.home.HomePresenterImpl
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -46,30 +46,10 @@ class TileActionReceiver : BroadcastReceiver() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val domain = entityId.split(".")[0]
|
|
||||||
val serviceName = when (domain) {
|
|
||||||
"button", "input_button" -> "press"
|
|
||||||
"lock" -> {
|
|
||||||
val lockEntity = try {
|
|
||||||
serverManager.integrationRepository().getEntity(entityId)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
if (lockEntity?.state == "locked") {
|
|
||||||
"unlock"
|
|
||||||
} else {
|
|
||||||
"lock"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
in HomePresenterImpl.toggleDomains -> "toggle"
|
|
||||||
else -> "turn_on"
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
serverManager.integrationRepository().callService(
|
onEntityPressedWithoutState(
|
||||||
domain,
|
entityId = entityId,
|
||||||
serviceName,
|
integrationRepository = serverManager.integrationRepository()
|
||||||
hashMapOf("entity_id" to entityId)
|
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Cannot call tile service", e)
|
Log.e(TAG, "Cannot call tile service", e)
|
||||||
|
|
Loading…
Reference in a new issue