Skip to content

Commit

Permalink
Jetpack Compose icon dialog (#3631)
Browse files Browse the repository at this point in the history
* Add icon dialog based on Jetpack Compose

TODO

* Migrate to new icon dialog

* Migrate old database

* Don't wrap with drawablecompat

* Rebase fixes and updates

 - Fix and update database migration
 - Fix dependencies
 - Fix shortcut icons
 - Fix ComposeView in AlertDialog not working by switching implementation to DialogFragment
 - Fix icons that no longer exist
 - ktlint

* Visual compatibility

 - Automotive asset
 - Handle icon ids in shortcuts to prevent users losing icons when updating shortcuts
 - Add padding, color filter to shortcut icons to keep icons consistent with older icons
 - Increase button widget icon padding to keep sizing consistent
 - Add tip to dialog about searching in non-English languages

* Fix line endings

---------

Co-authored-by: Tiger Oakes <contact@tigeroakes.com>
  • Loading branch information
jpelgrom and NotWoods authored Jul 4, 2023
1 parent 938c515 commit d1b17aa
Show file tree
Hide file tree
Showing 24 changed files with 1,861 additions and 423 deletions.
2 changes: 0 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,6 @@ dependencies {

implementation("com.github.Dimezis:BlurView:version-1.6.6")
implementation("org.altbeacon:android-beacon-library:2.19.5")
implementation("com.maltaisn:icondialog:3.3.0")
implementation("com.maltaisn:iconpack-community-material:5.3.45")

implementation("org.jetbrains.kotlin:kotlin-stdlib:1.8.22")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.22")
Expand Down
1 change: 1 addition & 0 deletions app/src/main/assets/mdi_id_map.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,8 @@ import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.graphics.drawable.toBitmap
import com.maltaisn.icondialog.pack.IconPack
import com.maltaisn.icondialog.pack.IconPackLoader
import com.maltaisn.iconpack.mdi.createMaterialDesignIconPack
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.sizeDp
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
Expand All @@ -35,6 +31,7 @@ import io.homeassistant.companion.android.database.qs.isSetup
import io.homeassistant.companion.android.database.qs.numberedId
import io.homeassistant.companion.android.settings.SettingsActivity
import io.homeassistant.companion.android.settings.qs.updateActiveTileServices
import io.homeassistant.companion.android.util.icondialog.getIconByMdiName
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
Expand Down Expand Up @@ -113,7 +110,7 @@ abstract class TileExtensions : TileService() {
serverManager.integrationRepository(tileData.serverId).getEntityUpdates(listOf(tileData.entityId))?.collect {
tile.state =
if (it.state in validActiveStates) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE
getTileIcon(tileData.iconId, it, applicationContext)?.let { icon ->
getTileIcon(tileData.iconName, it, applicationContext)?.let { icon ->
tile.icon = Icon.createWithBitmap(icon)
}
tile.updateTile()
Expand Down Expand Up @@ -147,7 +144,7 @@ abstract class TileExtensions : TileService() {
val state: Entity<*>? =
if (
tileData.entityId.split(".")[0] in toggleDomainsWithLock ||
tileData.iconId == null
tileData.iconName == null
) {
withContext(Dispatchers.IO) {
try {
Expand All @@ -170,7 +167,7 @@ abstract class TileExtensions : TileService() {
tile.state = Tile.STATE_INACTIVE
}

getTileIcon(tileData.iconId, state, context)?.let { icon ->
getTileIcon(tileData.iconName, state, context)?.let { icon ->
tile.icon = Icon.createWithBitmap(icon)
}
Log.d(TAG, "Tile data set for tile ID: $tileId")
Expand Down Expand Up @@ -308,7 +305,7 @@ abstract class TileExtensions : TileService() {
tileId = tileId,
added = true,
serverId = 0,
iconId = null,
iconName = null,
entityId = "",
label = "",
subtitle = null,
Expand All @@ -324,26 +321,17 @@ abstract class TileExtensions : TileService() {
updateActiveTileServices(highestInUse, applicationContext)
}

private fun getTileIcon(tileIconId: Int?, entity: Entity<*>?, context: Context): Bitmap? {
private fun getTileIcon(tileIconName: String?, entity: Entity<*>?, context: Context): Bitmap? {
// Create an icon pack and load all drawables.
if (tileIconId != null) {
if (iconPack == null) {
val loader = IconPackLoader(context)
iconPack = createMaterialDesignIconPack(loader)
iconPack!!.loadDrawables(loader.drawableLoader)
}

val iconDrawable = iconPack?.icons?.get(tileIconId)?.drawable
if (iconDrawable != null) {
return DrawableCompat.wrap(iconDrawable).toBitmap()
}
if (!tileIconName.isNullOrBlank()) {
val icon = CommunityMaterial.getIconByMdiName(tileIconName) ?: return null
val iconDrawable = IconicsDrawable(context, icon)
return iconDrawable.toBitmap()
} else {
entity?.getIcon(context)?.let {
return DrawableCompat.wrap(
IconicsDrawable(context, it).apply {
sizeDp = 48
}
).toBitmap()
return IconicsDrawable(context, it).apply {
sizeDp = 48
}.toBitmap()
}
}

Expand All @@ -352,7 +340,6 @@ abstract class TileExtensions : TileService() {

companion object {
private const val TAG = "TileExtensions"
private var iconPack: IconPack? = null
private val toggleDomains = listOf(
"automation", "cover", "fan", "humidifier", "input_boolean", "light",
"media_player", "remote", "siren", "switch"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,23 @@ import android.view.LayoutInflater
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.google.accompanist.themeadapter.material.MdcTheme
import com.maltaisn.icondialog.IconDialog
import com.maltaisn.icondialog.IconDialogSettings
import com.maltaisn.icondialog.pack.IconPack
import com.mikepenz.iconics.typeface.IIcon
import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.settings.qs.views.ManageTilesView
import io.homeassistant.companion.android.util.icondialog.IconDialog
import io.homeassistant.companion.android.common.R as commonR

@AndroidEntryPoint
class ManageTilesFragment : Fragment(), IconDialog.Callback {
class ManageTilesFragment : Fragment() {

companion object {
private const val TAG = "TileFragment"
Expand Down Expand Up @@ -53,17 +56,24 @@ class ManageTilesFragment : Fragment(), IconDialog.Callback {
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
val settings = IconDialogSettings {
searchVisibility = IconDialog.SearchVisibility.ALWAYS
}
val iconDialog = IconDialog.newInstance(settings)

setContent {
MdcTheme {
var showingDialog by remember { mutableStateOf(false) }

if (showingDialog) {
IconDialog(
onSelect = {
onIconDialogIconsSelected(it)
showingDialog = false
},
onDismissRequest = { showingDialog = false }
)
}

ManageTilesView(
viewModel = viewModel,
onShowIconDialog = { tag ->
iconDialog.show(childFragmentManager, tag)
onShowIconDialog = {
showingDialog = true
}
)
}
Expand All @@ -76,14 +86,8 @@ class ManageTilesFragment : Fragment(), IconDialog.Callback {
activity?.title = getString(commonR.string.tiles)
}

override val iconDialogIconPack: IconPack
get() = viewModel.iconPack

override fun onIconDialogIconsSelected(dialog: IconDialog, icons: List<com.maltaisn.icondialog.data.Icon>) {
Log.d(TAG, "Selected icon: ${icons.firstOrNull()}")
val selectedIcon = icons.firstOrNull()
if (selectedIcon != null) {
viewModel.selectIcon(selectedIcon)
}
private fun onIconDialogIconsSelected(selectedIcon: IIcon) {
Log.d(TAG, "Selected icon: ${selectedIcon.name}")
viewModel.selectIcon(selectedIcon)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,19 @@ import android.annotation.SuppressLint
import android.app.Application
import android.app.StatusBarManager
import android.content.ComponentName
import android.graphics.drawable.Icon
import android.os.Build
import android.util.Log
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.core.content.getSystemService
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.graphics.drawable.toBitmapOrNull
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.maltaisn.icondialog.data.Icon
import com.maltaisn.icondialog.pack.IconPack
import com.maltaisn.icondialog.pack.IconPackLoader
import com.maltaisn.iconpack.mdi.createMaterialDesignIconPack
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.utils.sizeDp
import com.mikepenz.iconics.typeface.IIcon
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import dagger.hilt.android.lifecycle.HiltViewModel
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.domain
Expand Down Expand Up @@ -72,6 +67,8 @@ import io.homeassistant.companion.android.qs.Tile6Service
import io.homeassistant.companion.android.qs.Tile7Service
import io.homeassistant.companion.android.qs.Tile8Service
import io.homeassistant.companion.android.qs.Tile9Service
import io.homeassistant.companion.android.util.icondialog.getIconByMdiName
import io.homeassistant.companion.android.util.icondialog.mdiName
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
Expand Down Expand Up @@ -139,8 +136,6 @@ class ManageTilesViewModel @Inject constructor(
)
}

lateinit var iconPack: IconPack

private val app = application

val slots = loadTileSlots(application.resources)
Expand All @@ -154,9 +149,7 @@ class ManageTilesViewModel @Inject constructor(
private set
var selectedServerId by mutableStateOf(ServerManager.SERVER_ID_ACTIVE)
private set
var selectedIconId by mutableStateOf<Int?>(null)
private set
var selectedIconDrawable by mutableStateOf(AppCompatResources.getDrawable(application, commonR.drawable.ic_stat_ic_notification))
var selectedIconId by mutableStateOf<String?>(null)
private set
var selectedEntityId by mutableStateOf("")
var tileLabel by mutableStateOf("")
Expand All @@ -165,6 +158,8 @@ class ManageTilesViewModel @Inject constructor(
private set
var selectedShouldVibrate by mutableStateOf(false)
var tileAuthRequired by mutableStateOf(false)

var selectedIcon: IIcon? = null
private var selectedTileId = 0
private var selectedTileAdded = false

Expand Down Expand Up @@ -202,16 +197,6 @@ class ManageTilesViewModel @Inject constructor(
selectTile(slots.indexOf(selectedTile))
}
}

viewModelScope.launch(Dispatchers.IO) {
val loader = IconPackLoader(getApplication())
iconPack = createMaterialDesignIconPack(loader)
iconPack.loadDrawables(loader.drawableLoader)
withContext(Dispatchers.Main) {
// The icon pack might not have been initialized when the tile data was loaded
selectTile(slots.indexOf(selectedTile))
}
}
}

fun selectTile(index: Int) {
Expand Down Expand Up @@ -259,21 +244,9 @@ class ManageTilesViewModel @Inject constructor(
if (selectedIconId == null) selectIcon(null) // trigger drawable update
}

fun selectIcon(icon: Icon?) {
selectedIconId = icon?.id
selectedIconDrawable = if (icon != null) {
icon.drawable?.let { DrawableCompat.wrap(it) }
} else {
sortedEntities.firstOrNull { it.entityId == selectedEntityId }?.let {
it.getIcon(app)?.let { iIcon ->
DrawableCompat.wrap(
IconicsDrawable(app, iIcon).apply {
sizeDp = 20
}
)
}
}
}
fun selectIcon(icon: IIcon?) {
selectedIconId = icon?.mdiName
selectedIcon = icon ?: sortedEntities.firstOrNull { it.entityId == selectedEntityId }?.getIcon(app)
}

private fun updateExistingTileFields(currentTile: TileEntity) {
Expand All @@ -283,13 +256,7 @@ class ManageTilesViewModel @Inject constructor(
selectedShouldVibrate = currentTile.shouldVibrate
tileAuthRequired = currentTile.authRequired
selectIcon(
currentTile.iconId?.let {
if (::iconPack.isInitialized) {
iconPack.getIcon(it)
} else {
null
}
}
currentTile.iconName?.let { CommunityMaterial.getIconByMdiName(it) }
)
}

Expand All @@ -300,7 +267,7 @@ class ManageTilesViewModel @Inject constructor(
tileId = selectedTile.id,
serverId = selectedServerId,
added = selectedTileAdded,
iconId = selectedIconId,
iconName = selectedIconId,
entityId = selectedEntityId,
label = tileLabel,
subtitle = tileSubtitle,
Expand All @@ -315,11 +282,10 @@ class ManageTilesViewModel @Inject constructor(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !selectedTileAdded) {
val statusBarManager = app.getSystemService<StatusBarManager>()
val service = idToTileService[selectedTile.id] ?: Tile1Service::class.java
val icon = selectedIconDrawable?.let {
it.toBitmapOrNull(it.intrinsicWidth, it.intrinsicHeight)?.let { bitmap ->
android.graphics.drawable.Icon.createWithBitmap(bitmap)
}
} ?: android.graphics.drawable.Icon.createWithResource(app, commonR.drawable.ic_stat_ic_notification)
val icon = selectedIcon?.let {
val bitmap = IconicsDrawable(getApplication(), it).toBitmap()
Icon.createWithBitmap(bitmap)
} ?: Icon.createWithResource(app, commonR.drawable.ic_stat_ic_notification)

statusBarManager?.requestAddTileService(
ComponentName(app, service),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.homeassistant.companion.android.settings.qs.views

import android.os.Build
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
Expand Down Expand Up @@ -31,13 +30,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.graphics.drawable.toBitmap
import io.homeassistant.companion.android.common.R
import io.homeassistant.companion.android.settings.qs.ManageTilesViewModel
import io.homeassistant.companion.android.util.compose.ServerDropdownButton
Expand Down Expand Up @@ -160,12 +157,9 @@ fun ManageTilesView(
OutlinedButton(
onClick = { onShowIconDialog(viewModel.selectedTile.id) }
) {
val iconBitmap = remember(viewModel.selectedIconDrawable) {
viewModel.selectedIconDrawable?.toBitmap()?.asImageBitmap()
}
iconBitmap?.let {
Image(
iconBitmap,
viewModel.selectedIcon?.let { icon ->
com.mikepenz.iconics.compose.Image(
icon,
contentDescription = stringResource(id = R.string.tile_icon),
colorFilter = ColorFilter.tint(colorResource(R.color.colorAccent)),
modifier = Modifier.size(20.dp)
Expand Down
Loading

0 comments on commit d1b17aa

Please sign in to comment.