Fix icon null pointer exceptions

pull/2947/head
Alex Baker 3 months ago
parent 6700ce5aa1
commit 64ad4ca33a

@ -51,7 +51,7 @@ android {
defaultConfig { defaultConfig {
testApplicationId = "org.tasks.test" testApplicationId = "org.tasks.test"
applicationId = "org.tasks" applicationId = "org.tasks"
versionCode = 131100 versionCode = 131101
versionName = "13.11" versionName = "13.11"
targetSdk = libs.versions.android.targetSdk.get().toInt() targetSdk = libs.versions.android.targetSdk.get().toInt()
minSdk = libs.versions.android.minSdk.get().toInt() minSdk = libs.versions.android.minSdk.get().toInt()

@ -5,7 +5,6 @@ import android.view.View
import android.widget.CheckedTextView import android.widget.CheckedTextView
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.core.view.isVisible import androidx.core.view.isVisible
@ -15,7 +14,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import org.tasks.R import org.tasks.R
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.compose.components.imageVectorByName import org.tasks.compose.components.TasksIcon
import org.tasks.databinding.FilterAdapterRowBinding import org.tasks.databinding.FilterAdapterRowBinding
import org.tasks.extensions.formatNumber import org.tasks.extensions.formatNumber
import org.tasks.filters.CaldavFilter import org.tasks.filters.CaldavFilter
@ -50,10 +49,9 @@ class FilterViewHolder internal constructor(
it.icon.setContent { it.icon.setContent {
val label = icon.collectAsStateWithLifecycle().value val label = icon.collectAsStateWithLifecycle().value
val tint = color.collectAsStateWithLifecycle().value val tint = color.collectAsStateWithLifecycle().value
Icon( TasksIcon(
imageVector = imageVectorByName(label!!)!!, label = label,
contentDescription = label, tint = if (tint == 0) MaterialTheme.colorScheme.onSurface else Color(tint)
tint = if (tint == 0) MaterialTheme.colorScheme.onSurface else Color(tint),
) )
} }
size = it.size size = it.size

@ -7,7 +7,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
@ -15,7 +14,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@ -27,7 +25,7 @@ import kotlinx.coroutines.launch
import org.tasks.R import org.tasks.R
import org.tasks.compose.IconPickerActivity.Companion.launchIconPicker import org.tasks.compose.IconPickerActivity.Companion.launchIconPicker
import org.tasks.compose.IconPickerActivity.Companion.registerForIconPickerResult import org.tasks.compose.IconPickerActivity.Companion.registerForIconPickerResult
import org.tasks.compose.components.imageVectorByName import org.tasks.compose.components.TasksIcon
import org.tasks.dialogs.ColorPalettePicker import org.tasks.dialogs.ColorPalettePicker
import org.tasks.dialogs.ColorPalettePicker.Companion.newColorPalette import org.tasks.dialogs.ColorPalettePicker.Companion.newColorPalette
import org.tasks.dialogs.ColorPickerAdapter.Palette import org.tasks.dialogs.ColorPickerAdapter.Palette
@ -69,15 +67,9 @@ abstract class BaseListSettingsActivity : ThemedInjectingAppCompatActivity(), To
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
val name = selectedIcon.collectAsStateWithLifecycle().value TasksIcon(
val icon = imageVectorByName(name ?:defaultIcon) label = selectedIcon.collectAsStateWithLifecycle().value ?: defaultIcon
if (icon != null) {
Image(
imageVector = icon,
contentDescription = name ?: defaultIcon,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface),
) )
}
Spacer(modifier = Modifier.width(34.dp)) Spacer(modifier = Modifier.width(34.dp))
Text( Text(
text = "Icon", text = "Icon",

@ -75,7 +75,7 @@ class FilterSelectionActivity : AppCompatActivity() {
filters = if (searching) state.searchResults else state.filters, filters = if (searching) state.searchResults else state.filters,
query = state.query, query = state.query,
onQueryChange = { viewModel.onQueryChange(it) }, onQueryChange = { viewModel.onQueryChange(it) },
getIcon = { imageVectorByName(viewModel.getIcon(it))!! }, getIcon = { imageVectorByName(viewModel.getIcon(it)) },
getColor = { viewModel.getColor(it) }, getColor = { viewModel.getColor(it) },
selected = selected, selected = selected,
onClick = { filter -> onClick = { filter ->

@ -89,7 +89,7 @@ class FilterPickerViewModel @Inject constructor(
localBroadcastManager.broadcastRefreshList() localBroadcastManager.broadcastRefreshList()
} }
fun getIcon(filter: Filter): String = filter.getIcon(inventory) fun getIcon(filter: Filter): String? = filter.getIcon(inventory)
fun getColor(filter: Filter): Int { fun getColor(filter: Filter): Int {
if (filter.tint != 0) { if (filter.tint != 0) {

@ -3,7 +3,7 @@ package org.tasks.filters
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.themes.TasksIcons import org.tasks.themes.TasksIcons
fun Filter.getIcon(inventory: Inventory): String { fun Filter.getIcon(inventory: Inventory): String? {
if (inventory.hasPro) { if (inventory.hasPro) {
icon?.takeIf { it.isNotBlank() }?.let { return it } icon?.takeIf { it.isNotBlank() }?.let { return it }
} }
@ -14,6 +14,6 @@ fun Filter.getIcon(inventory: Inventory): String {
is CustomFilter -> TasksIcons.FILTER_LIST is CustomFilter -> TasksIcons.FILTER_LIST
is PlaceFilter -> TasksIcons.PLACE is PlaceFilter -> TasksIcons.PLACE
else -> icon!! else -> icon
} }
} }

@ -115,7 +115,8 @@ class TagPickerActivity : ThemedInjectingAppCompatActivity() {
return Color(getColor(R.color.icon_tint_with_alpha)) return Color(getColor(R.color.icon_tint_with_alpha))
} }
private fun getIcon(tagData: TagData): String = TagFilter(tagData).getIcon(inventory) private fun getIcon(tagData: TagData): String =
TagFilter(tagData).getIcon(inventory) ?: TasksIcons.LABEL
/* Copy of the TagPickerActivity's companion object */ /* Copy of the TagPickerActivity's companion object */
companion object { companion object {

@ -4,7 +4,6 @@ import android.content.Context
import android.widget.RemoteViews import android.widget.RemoteViews
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.IconicsDrawable
import org.tasks.icons.OutlinedGoogleMaterial
import com.todoroo.andlib.utility.DateUtilities import com.todoroo.andlib.utility.DateUtilities
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.BuildConfig import org.tasks.BuildConfig
@ -21,6 +20,7 @@ import org.tasks.filters.GtasksFilter
import org.tasks.filters.PlaceFilter import org.tasks.filters.PlaceFilter
import org.tasks.filters.TagFilter import org.tasks.filters.TagFilter
import org.tasks.filters.getIcon import org.tasks.filters.getIcon
import org.tasks.icons.OutlinedGoogleMaterial
import org.tasks.time.startOfDay import org.tasks.time.startOfDay
import org.tasks.ui.ChipListCache import org.tasks.ui.ChipListCache
import java.time.format.FormatStyle import java.time.format.FormatStyle
@ -113,16 +113,17 @@ class WidgetChipProvider @Inject constructor(
private fun newChip(filter: Filter, defaultIcon: Int) = private fun newChip(filter: Filter, defaultIcon: Int) =
newChip(filter.tint).apply { newChip(filter.tint).apply {
setTextViewText(R.id.chip_text, filter.title) setTextViewText(R.id.chip_text, filter.title)
if (filter.icon.isNullOrBlank()) { filter
setImageViewResource(R.id.chip_icon, defaultIcon) .getIcon(inventory)
} else { ?.let {
val icon = filter.getIcon(inventory) try {
val drawable = IconicsDrawable( OutlinedGoogleMaterial.getIcon("gmo_$it")
context, } catch (_: IllegalArgumentException) {
OutlinedGoogleMaterial.getIcon("gmo_$icon") null
) }
setImageViewBitmap(R.id.chip_icon, drawable.toBitmap())
} }
?.let { setImageViewBitmap(R.id.chip_icon, IconicsDrawable(context, it).toBitmap()) }
?: setImageViewResource(R.id.chip_icon, defaultIcon)
} }
private fun newChip(@ColorInt color: Int = 0) = RemoteViews(BuildConfig.APPLICATION_ID, R.layout.widget_chip).apply { private fun newChip(@ColorInt color: Int = 0) = RemoteViews(BuildConfig.APPLICATION_ID, R.layout.widget_chip).apply {

@ -1,14 +1,40 @@
package org.tasks.compose.components package org.tasks.compose.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import org.tasks.compose.pickers.label import org.tasks.compose.pickers.label
fun imageVectorByName(label: String): ImageVector? = @Composable
fun TasksIcon(
label: String?,
tint: Color = MaterialTheme.colorScheme.onSurface,
) {
Box(modifier = Modifier.size(24.dp)) {
Icon(
imageVector = remember (label) {
imageVectorByName(label)
} ?: return@Box,
contentDescription = label,
tint = tint,
)
}
}
fun imageVectorByName(label: String?): ImageVector? = label?.let {
try { try {
val cl = Class.forName("androidx.compose.material.icons.outlined.${label.label}Kt") val cl = Class.forName("androidx.compose.material.icons.outlined.${it.label}Kt")
val method = cl.declaredMethods.first() val method = cl.declaredMethods.first()
method.invoke(null, Icons.Outlined) as ImageVector method.invoke(null, Icons.Outlined) as ImageVector
} catch (_: Throwable) { } catch (_: Throwable) {
null null
} }
}

@ -5,7 +5,7 @@ import org.tasks.filters.NavigationDrawerSubheader
sealed interface DrawerItem { sealed interface DrawerItem {
data class Filter( data class Filter(
val title: String, val title: String,
val icon: String, val icon: String?,
val color: Int = 0, val color: Int = 0,
val count: Int = 0, val count: Int = 0,
val shareCount: Int = 0, val shareCount: Int = 0,

@ -19,7 +19,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.mandatorySystemGestures import androidx.compose.foundation.layout.mandatorySystemGestures
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
@ -56,7 +55,7 @@ import kotlinx.collections.immutable.ImmutableList
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
import org.tasks.compose.components.Chevron import org.tasks.compose.components.Chevron
import org.tasks.compose.components.SearchBar import org.tasks.compose.components.SearchBar
import org.tasks.compose.components.imageVectorByName import org.tasks.compose.components.TasksIcon
import org.tasks.kmp.formatNumber import org.tasks.kmp.formatNumber
import tasks.kmp.generated.resources.Res import tasks.kmp.generated.resources.Res
import tasks.kmp.generated.resources.help_and_feedback import tasks.kmp.generated.resources.help_and_feedback
@ -194,7 +193,13 @@ internal fun FilterItem(
.clickable(onClick = onClick), .clickable(onClick = onClick),
onClick = onClick, onClick = onClick,
) { ) {
DrawerIcon(icon = item.icon, color = item.color) TasksIcon(
label = item.icon,
tint = when (item.color) {
0 -> MaterialTheme.colorScheme.onSurface
else -> Color(color = item.color)
}
)
Spacer(modifier = Modifier.width(16.dp)) Spacer(modifier = Modifier.width(16.dp))
Text( Text(
text = item.title, text = item.title,
@ -225,19 +230,6 @@ internal fun FilterItem(
} }
} }
@Composable
private fun DrawerIcon(icon: String, color: Int = 0) {
Icon(
modifier = Modifier.size(24.dp),
imageVector = imageVectorByName(icon) ?: return,
contentDescription = null,
tint = when (color) {
0 -> MaterialTheme.colorScheme.onSurface
else -> Color(color)
}
)
}
@Composable @Composable
internal fun HeaderItem( internal fun HeaderItem(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,

@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Check import androidx.compose.material.icons.outlined.Check
@ -44,7 +45,7 @@ fun CheckableIconRow(
@Composable @Composable
fun CheckableIconRow( fun CheckableIconRow(
icon: ImageVector, icon: ImageVector?,
tint: Color, tint: Color,
selected: Boolean, selected: Boolean,
onClick: () -> Unit, onClick: () -> Unit,
@ -56,12 +57,20 @@ fun CheckableIconRow(
.fillMaxWidth() .fillMaxWidth()
.clickable { onClick() } .clickable { onClick() }
) { ) {
Box(
modifier = Modifier
.padding(start = 16.dp, end = 32.dp, top = 12.dp, bottom = 12.dp)
.size(24.dp),
contentAlignment = Alignment.Center,
) {
if (icon != null) {
Icon( Icon(
imageVector = icon, imageVector = icon,
contentDescription = null, contentDescription = null,
tint = tint, tint = tint,
modifier = Modifier.padding(start = 16.dp, end = 32.dp, top = 12.dp, bottom = 12.dp),
) )
}
}
Box(modifier = Modifier.weight(1f)) { Box(modifier = Modifier.weight(1f)) {
content() content()
} }

@ -1,7 +1,6 @@
package org.tasks.compose.pickers package org.tasks.compose.pickers
import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -22,7 +21,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.times import androidx.compose.ui.unit.times
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
@ -32,7 +30,7 @@ import org.jetbrains.compose.resources.stringResource
import org.tasks.compose.components.AnimatedBanner import org.tasks.compose.components.AnimatedBanner
import org.tasks.compose.components.Chevron import org.tasks.compose.components.Chevron
import org.tasks.compose.components.SearchBar import org.tasks.compose.components.SearchBar
import org.tasks.compose.components.imageVectorByName import org.tasks.compose.components.TasksIcon
import tasks.kmp.generated.resources.Res import tasks.kmp.generated.resources.Res
import tasks.kmp.generated.resources.requires_pro_subscription import tasks.kmp.generated.resources.requires_pro_subscription
import tasks.kmp.generated.resources.search import tasks.kmp.generated.resources.search
@ -150,14 +148,7 @@ fun LazyGridScope.iconGrid(
) { ) {
items(icons, key = { it.name }) { icon -> items(icons, key = { it.name }) { icon ->
IconButton(onClick = { onSelected(icon) }) { IconButton(onClick = { onSelected(icon) }) {
val imageVector = remember (icon.name) { TasksIcon(label = icon.name)
imageVectorByName(icon.name)
}
Image(
imageVector = imageVector ?: return@IconButton,
contentDescription = icon.name,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface),
)
} }
} }
} }

@ -37,7 +37,7 @@ fun SearchableFilterPicker(
onQueryChange: (String) -> Unit, onQueryChange: (String) -> Unit,
selected: Filter?, selected: Filter?,
onClick: (FilterListItem) -> Unit, onClick: (FilterListItem) -> Unit,
getIcon: @Composable (Filter) -> ImageVector, getIcon: @Composable (Filter) -> ImageVector?,
getColor: (Filter) -> Int, getColor: (Filter) -> Int,
) { ) {
LazyColumn( LazyColumn(

Loading…
Cancel
Save