Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wear os scenes and scripts support #1763

Merged
merged 3 commits into from
Oct 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package io.homeassistant.companion.android.home
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.button.MaterialButton
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.wear.widget.WearableRecyclerView
import io.homeassistant.companion.android.DaggerPresenterComponent
import io.homeassistant.companion.android.PresenterModule
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.onboarding.OnboardingActivity
import io.homeassistant.companion.android.onboarding.integration.MobileAppIntegrationActivity
import javax.inject.Inject
Expand All @@ -19,6 +20,8 @@ class HomeActivity : AppCompatActivity(), HomeView {
@Inject
lateinit var presenter: HomePresenter

private lateinit var adapter: HomeListAdapter

companion object {
private const val TAG = "HomeActivity"

Expand All @@ -39,8 +42,13 @@ class HomeActivity : AppCompatActivity(), HomeView {
.build()
.inject(this)

findViewById<MaterialButton>(R.id.btn_logout).setOnClickListener {
presenter.onLogoutClicked()
adapter = HomeListAdapter()
adapter.onSceneClicked = { entity -> presenter.onEntityClicked(entity) }
adapter.onButtonClicked = { id -> presenter.onButtonClicked(id) }

findViewById<WearableRecyclerView>(R.id.home_list)?.apply {
layoutManager = LinearLayoutManager(this@HomeActivity)
adapter = this@HomeActivity.adapter
}

presenter.onViewReady()
Expand All @@ -51,14 +59,12 @@ class HomeActivity : AppCompatActivity(), HomeView {
super.onDestroy()
}

override fun showHomeAssistantVersion(version: String) {
val txtVersion = findViewById<TextView>(R.id.txt_version)
txtVersion.text = getString(R.string.version, version)
}

override fun showEntitiesCount(count: Int) {
val txtEntities = findViewById<TextView>(R.id.txt_entities)
txtEntities.text = resources.getQuantityString(R.plurals.entities_found, count, count)
override fun showHomeList(scenes: List<Entity<Any>>, scripts: List<Entity<Any>>) {
adapter.scenes.clear()
adapter.scenes.addAll(scenes)
adapter.scripts.clear()
adapter.scripts.addAll(scripts)
adapter.notifyDataSetChanged()
}

override fun displayOnBoarding() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package io.homeassistant.companion.android.home

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.viewHolders.ButtonViewHolder
import io.homeassistant.companion.android.viewHolders.EntityButtonViewHolder
import io.homeassistant.companion.android.viewHolders.HeaderViewHolder
import io.homeassistant.companion.android.viewHolders.LoadingViewHolder
import kotlin.math.max

class HomeListAdapter() : RecyclerView.Adapter<ViewHolder>() {

lateinit var onSceneClicked: (Entity<Any>) -> Unit
lateinit var onButtonClicked: (String) -> Unit

val scenes = arrayListOf<Entity<Any>>()
val scripts = arrayListOf<Entity<Any>>()

companion object {
private const val TYPE_SCENE = 1 // Used for scenes and scripts
private const val TYPE_HEADER = 2
private const val TYPE_LOADING = 3
private const val TYPE_BUTTON = 4

const val BUTTON_ID_LOGOUT: String = "logout"
}

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ViewHolder {
return when (viewType) {
TYPE_SCENE -> {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.listitem_entity_button, parent, false)
EntityButtonViewHolder(view, onSceneClicked)
}
TYPE_HEADER -> {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.listitem_header, parent, false)
HeaderViewHolder(view)
}
TYPE_BUTTON -> {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.listitem_button, parent, false)
ButtonViewHolder(view, onButtonClicked)
}
else -> {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.listitem_loading, parent, false)
LoadingViewHolder(view)
}
}
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (holder is EntityButtonViewHolder) {
if (position < scenes.size + 1)
holder.entity = scenes[position - 1]
else
holder.entity = scripts[position - 2 - scenes.size]
} else if (holder is HeaderViewHolder) {
when (position) {
0 -> holder.headerTextView.setText(R.string.scenes)
scenes.size + 1 -> holder.headerTextView.setText(R.string.scripts)
else -> holder.headerTextView.setText(R.string.other)
}
} else if (holder is ButtonViewHolder) {
holder.txtName.setText(R.string.logout)
holder.id = BUTTON_ID_LOGOUT
holder.color = R.color.colorWarning
}
}

override fun getItemCount() = max(scenes.size + scripts.size + 4, 6)

override fun getItemViewType(position: Int): Int {
/*
Current layout contains of three sections:
# Scenes
- scene 1
- scene 2
- etc
# Scripts
- script 1
- script 2
- etc
# Other
- Logout

Envisioned final layout:
# Scenes
- 3 favorite scenes
- More scenes button
# Devices
- 3 favorite devices
- More devices button
# Scripts
- 3 favorite scripts
- More scripts button
# Other
- Settings
*/
return when {
position == 0 || position == scenes.size + 1 || position == itemCount - 2 -> TYPE_HEADER
position == itemCount - 1 -> TYPE_BUTTON
position < scenes.size + 1 && scenes.size > 0 -> TYPE_SCENE
position > scenes.size + 1 && scripts.size > 0 -> TYPE_SCENE
else -> TYPE_LOADING
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package io.homeassistant.companion.android.home

import io.homeassistant.companion.android.common.data.integration.Entity

interface HomePresenter {

fun onViewReady()
fun onEntityClicked(entity: Entity<Any>)
fun onButtonClicked(id: String)
fun onLogoutClicked()
fun onFinish()
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.homeassistant.companion.android.BuildConfig
import io.homeassistant.companion.android.common.data.authentication.AuthenticationRepository
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.Entity
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand All @@ -30,9 +31,8 @@ class HomePresenterImpl @Inject constructor(
val sessionValid = authenticationUseCase.getSessionState() == SessionState.CONNECTED
if (sessionValid && integrationUseCase.isRegistered()) {
resyncRegistration()
// We'll stay on HomeActivity, so start loading
view.showHomeAssistantVersion(integrationUseCase.getHomeAssistantVersion())
view.showEntitiesCount(integrationUseCase.getEntities().size)
// We'll stay on HomeActivity, so start loading entities
processEntities(integrationUseCase.getEntities())
} else if (sessionValid) {
view.displayMobileAppIntegration()
} else {
Expand All @@ -41,6 +41,22 @@ class HomePresenterImpl @Inject constructor(
}
}

override fun onEntityClicked(entity: Entity<Any>) {
mainScope.launch {
integrationUseCase.callService(
entity.entityId.split(".")[0],
"turn_on",
hashMapOf("entity_id" to entity.entityId)
)
}
}

override fun onButtonClicked(id: String) {
if (id == HomeListAdapter.BUTTON_ID_LOGOUT) {
onLogoutClicked()
}
}

override fun onLogoutClicked() {
mainScope.launch {
authenticationUseCase.revokeSession()
Expand All @@ -52,6 +68,12 @@ class HomePresenterImpl @Inject constructor(
mainScope.cancel()
}

private fun processEntities(entities: Array<Entity<Any>>) {
val scenes = entities.filter { it.entityId.split(".")[0] == "scene" }
val scripts = entities.filter { it.entityId.split(".")[0] == "script" }
view.showHomeList(scenes, scripts)
}

private fun resyncRegistration() {
mainScope.launch {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package io.homeassistant.companion.android.home

import io.homeassistant.companion.android.common.data.integration.Entity

interface HomeView {
fun showHomeAssistantVersion(version: String)
fun showEntitiesCount(count: Int)
fun showHomeList(scenes: List<Entity<Any>>, scripts: List<Entity<Any>>)

fun displayOnBoarding()
fun displayMobileAppIntegration()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.onboarding.viewHolders.HeaderViewHolder
import io.homeassistant.companion.android.onboarding.viewHolders.InstanceViewHolder
import io.homeassistant.companion.android.onboarding.viewHolders.LoadingViewHolder
import io.homeassistant.companion.android.onboarding.viewHolders.ManualSetupViewHolder
import io.homeassistant.companion.android.viewHolders.HeaderViewHolder
import io.homeassistant.companion.android.viewHolders.InstanceViewHolder
import io.homeassistant.companion.android.viewHolders.LoadingViewHolder
import io.homeassistant.companion.android.viewHolders.ManualSetupViewHolder
import kotlin.math.min

class ServerListAdapter(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.homeassistant.companion.android.viewHolders

import android.view.View
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import io.homeassistant.companion.android.R

class ButtonViewHolder(val v: View, val onClick: (String) -> Unit) :
RecyclerView.ViewHolder(v) {

var txtName: TextView = v.findViewById(R.id.txt_name)

var color: Int? = null
set(value) {
v.background.mutate().setTint(ContextCompat.getColor(v.context, value!!))
field = value
}

var id: String = ""

init {
v.setOnClickListener {
onClick(id)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.homeassistant.companion.android.viewHolders

import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.common.data.integration.Entity

class EntityButtonViewHolder(v: View, val onClick: (Entity<Any>) -> Unit) :
RecyclerView.ViewHolder(v) {

private var txtName: TextView = v.findViewById(R.id.txt_name)
private var imgIcon: ImageView = v.findViewById(R.id.img_icon)

var entity: Entity<Any>? = null
set(value) {
val entityAttributes = value?.attributes as Map<String, String>
txtName.text = entityAttributes["friendly_name"]

// Set default icon
if (value.entityId.split(".")[0] == "script") {
imgIcon.setImageResource(R.drawable.ic_scripts)
} else {
imgIcon.setImageResource(R.drawable.ic_scenes)
}
/*if (entityAttributes.containsKey("icon")) {
Need to implement dynamic icon loading here
The default library used (com.maltaisn:icondialog) does not allow to get icons by the mdi: string
Alternative library: https://github.com/outadoc/mdi-android
This one requires a github access token...
}*/
field = value
}

init {
v.setOnClickListener {
entity?.let { onClick(it) }
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.homeassistant.companion.android.onboarding.viewHolders
package io.homeassistant.companion.android.viewHolders

import android.view.View
import android.widget.TextView
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.homeassistant.companion.android.onboarding.viewHolders
package io.homeassistant.companion.android.viewHolders

import android.util.Log
import android.view.View
Expand All @@ -10,7 +10,7 @@ import io.homeassistant.companion.android.onboarding.HomeAssistantInstance
class InstanceViewHolder(v: View, val onClick: (HomeAssistantInstance) -> Unit) :
RecyclerView.ViewHolder(v), View.OnClickListener {

private val name: TextView = v.findViewById(R.id.name)
private val name: TextView = v.findViewById(R.id.txt_name)
var server: HomeAssistantInstance? = null
set(value) {
name.text = value?.name
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.homeassistant.companion.android.onboarding.viewHolders
package io.homeassistant.companion.android.viewHolders

import android.view.View
import androidx.recyclerview.widget.RecyclerView
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.homeassistant.companion.android.onboarding.viewHolders
package io.homeassistant.companion.android.viewHolders

import android.view.View
import android.widget.TextView
Expand All @@ -8,7 +8,7 @@ import io.homeassistant.companion.android.R
class ManualSetupViewHolder(v: View, val onClick: () -> Unit) :
RecyclerView.ViewHolder(v) {

val text: TextView = v.findViewById(R.id.name)
val text: TextView = v.findViewById(R.id.txt_name)

init {
// Set onclick listener
Expand Down
10 changes: 10 additions & 0 deletions wear/src/main/res/drawable/ic_scenes.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9c0.83,0 1.5,-0.67 1.5,-1.5 0,-0.39 -0.15,-0.74 -0.39,-1.01 -0.23,-0.26 -0.38,-0.61 -0.38,-0.99 0,-0.83 0.67,-1.5 1.5,-1.5L16,16c2.76,0 5,-2.24 5,-5 0,-4.42 -4.03,-8 -9,-8zM6.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,9 6.5,9 8,9.67 8,10.5 7.33,12 6.5,12zM9.5,8C8.67,8 8,7.33 8,6.5S8.67,5 9.5,5s1.5,0.67 1.5,1.5S10.33,8 9.5,8zM14.5,8c-0.83,0 -1.5,-0.67 -1.5,-1.5S13.67,5 14.5,5s1.5,0.67 1.5,1.5S15.33,8 14.5,8zM17.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S16.67,9 17.5,9s1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
</vector>